Getting MCI Audio Player script to work?

Discussion in 'RGSSx Script Support' started by Arsist, Dec 23, 2018.

  1. Arsist

    Arsist Veteran Veteran

    Messages:
    140
    Likes Received:
    10
    Hello. I'm trying to use the MCI Audio Player script by ForeverZer0 at https://forum.chaos-project.com/index.php?topic=11778.0 but I can't seem to get it to play audio files, and he's no longer offering support for his scripts. I've looked at some posts on the web about it, but either find unanswered questions or instances where people ask for support, say they've figured it out, but don't reveal their solution.

    The script features functions that I deem important for proper screenplay (and I'm currently not using MV due to not knowing JavaScript and there being some scripts that haven't been ported), like the ability to play two tracks and effectively "fade one into the other" rather than jarringly having to abruptly switch from one song to another, to fade out a song and then play another, and the ability to fade in a song rather than just fade out.
    If it's far too complex for a simple hotfix, I'd might be willing to commission someone to get it to work for me, as the only workaround I could think of is using BGS but that would come with its own issues.

    There are a number of errors that I'll face and I feel it's necessary to show my troubleshooting process first.

    From the beginning, on a fresh project, I switch 'RPG_VERSION' to 2 on line 207 at
    and try running the game to test an event.
    I am met with an error
    So, I decided to edit the def open(filename) segment at line 241 to
    Code:
        def open(filename)
          #--
          puts "Filename: #{filename}"
          #--
          if File.exists?(filename)
            puts "exists"
            path = filename
          else
            #--
            puts "does not exist"
            #--
            path = Dir::glob(filename + '.*')[0]
            if path == nil
              directory = File.dirname(filename)
              if RTP.subfolder?(directory)
                file = File.basename(filename)
                path = RTP[directory].find {|f| File.basename(f, '.*') == file }
              end
            end
          end
          #--
          puts "Path: #{path}"
          #--
          @filepath = path.gsub(/\\/, '/')
          if MIDI_MODE && ['.mid', '.midi'].include?(File.extname(path))
            @midi = true
            cmd = "open \"#{path}\" type sequencer alias #{@name}"
          else
            cmd = "open \"#{path}\" type mpegvideo alias #{@name}"
          end
          MCISendString.call(cmd, nil, 0, 0)
          MCISendString.call("set #{@name} time format milliseconds", nil, 0, 0)
          MCISendString.call("set #{@name} seek exactly on", nil, 0, 0)
          @opened = true
        end
    which reads in the console as
    Meaning that the path is nil.
    I figure this might be due to the RTP module process near the bottom (line 830) since it might not account for Steam users, and to control, I eliminate the reading of RTP files so I can focus on at least getting custom files to work, and instead replace the def open(filename) segment with
    Code:
        def open(filename)
          #--
          puts "Filename: #{filename}"
          #--
          #my edit-- files are automatically considered not to "exist", so I'mm
          #having it so it doesn't check for it in the first place.
          #--
    #~       if File.exists?(filename)
    #~         path = filename
    #~       else
    #~         path = Dir::glob(filename + '.*')[0]
    #~         if path == nil
    #~           directory = File.dirname(filename)
    #~           if RTP.subfolder?(directory)
    #~             file = File.basename(filename)
    #~             path = RTP[directory].find {|f| File.basename(f, '.*') == file }
    #~           end
    #~         end
    #~       end
          path = filename
          #The game was trying to load a nil file upon game start to where it
          #would be impossible to gsub a blank string.
          #--
    #~       @filepath = path.gsub(/\\/, '/')
          #--
          puts "Path: #{path}"
          if path != nil
            @filepath = path.gsub(/\\/, '/')
          else
            @filepath = path
          end
          #--
          if MIDI_MODE && ['.mid', '.midi'].include?(File.extname(path))
            @midi = true
            cmd = "open \"#{path}\" type sequencer alias #{@name}"
          else
            cmd = "open \"#{path}\" type mpegvideo alias #{@name}"
          end
          MCISendString.call(cmd, nil, 0, 0)
          MCISendString.call("set #{@name} time format milliseconds", nil, 0, 0)
          MCISendString.call("set #{@name} seek exactly on", nil, 0, 0)
          @opened = true
        end
    As now I'm just going to exclusively test custom music.
    I place an mp3 file named 'myFile' in my BGM folder, and set it as the Title Screen theme.
    At this point, I can't even get custom music to play/pause.

    At def pause and resume at line 304 I replace the code with the following
    Code:
        def pause
          MCISendString.call("pause #{@name}", nil, 0, 0)
          #--
          puts "attempting to pause"
          #--
        end
        #---------------------------------------------------------------------------
        # * Resume playback on a paused mixer from previous position
        #---------------------------------------------------------------------------
        def resume
          MCISendString.call("resume #{@name}", nil, 0, 0)
          #--
          puts "attempting to resume"
          #--
        end
    I make an event that makes the script call:
    Code:
    Audio['myName'].play('Audio/BGM/myFile')
    20.times { Fiber.yield }
    if Audio['myName'].opened?; puts "opened"; else; puts "not opened"; end
    if Audio['myName'].playing?; puts "playing"; else; puts "not playing"; end
    20.times { Fiber.yield }
    Audio['myName'].pause
    20.times { Fiber.yield }
    if Audio['myName'].paused?; puts "paused"; else; puts "not paused"; end
    if Audio['myName'].playing?; puts "playing"; else; puts "not playing"; end
    Audio['myName'].resume
    20.times { Fiber.yield }
    if Audio['myName'].playing?; puts "playing"; else; puts "not playing"; end
    which leads the console to print
    I also tried a variation where I instead made the call Audio['myName'].play('myFile') (akin to example call included in the script's description)
    along with trying to play the song from the editor's Play BGM call, but still had the same issue. No sound is heard, the file is 'opened', readied to be played by the MCI player, but it is never actually played.

    I know that I have winmm.dll and the proper codecs on my computer (as I can play mp3/wav/ogg/midi files on a given audio player). I should also mention that I use Windows 10, if that means anything.

    I'm unsure whether it's an issue with the script or the MCI dll itself, nor do I know whether the original demo had a dll file included that was required to make the script work -- The download link is now invalid. I feel like I once had this script working back when I was using Windows XP and I don't remember having to use the demo file.

    Does anyone have any assistance they can offer on this one, or are you unable to replicate the issues I'm facing?
     
    Last edited: Dec 23, 2018
    #1
  2. Kes

    Kes Global Moderators Global Mod

    Messages:
    20,138
    Likes Received:
    10,301
    First Language:
    English
    Primarily Uses:
    RMVXA
    Scripts is where people who have written a script they want to share with the community can post it.

    I've moved this thread to RGSSx Script Support. Please be sure to post your threads in the correct forum next time. Thank you.

     
    #2
  3. Arsist

    Arsist Veteran Veteran

    Messages:
    140
    Likes Received:
    10
    I forgot about that. 'Been a while. Now I want to be able to edit the title and put [ACE] at the beginning.
     
    #3
  4. Sixth

    Sixth Veteran Veteran

    Messages:
    2,121
    Likes Received:
    788
    First Language:
    Hungarian
    Primarily Uses:
    RMVXA
    I wouldn't bother with it.
    I tried it, got it to work, but it won't play anything but mp3. No ogg audio type is played unless you have a specific audio driver for it, even if every other program can play ogg on your system, this audio player script will NOT play it unless that driver is installed (no, driver packs won't work).
    I gave up after realizing this, since I need ogg file type for the loop feature - mp3 won't loop the same way, not even with this script.

    If you use mp3 only in your project, you may have a chance of using this script, but even that way, the default audio event commands will NOT work, since this script expects file extensions to work correctly, meaning you will have to use "MyFileName.mp3" to make it work, simply using "MyFileName" will not work.
    You will also have to use audio files from the project's own folder only, because the RTP folder reading is NOT dynamic, so it only checks the default install direction for it, which means that if any player playing your game installed the RTP files in a different directory path, they will be greeted with a long silence instead of music.

    In short, if you want to use this script you will have to:
    • Use mp3 file type only. Well, I only tested with mp3, wav, and ogg, to be honest, so you can experiment with this if you plan to use another file type.
    • Use files from the project's own directory only.
    • Always use file extensions in your file names.
    • Always include the full path to the file, starting from your project's root folder (example: "Audio/BGM/MyFile.mp3").
    • Always use the script call to play audio files, the default audio event commands won't work (as far as my testings show it).
    In case you want to test it bearing all the above in mind, here is the edited script with my fixes:
    Code:
    #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    # MCI Audio Player
    # Author: ForeverZer0
    # Version: 1.3
    # Date: 7.5.2014
    #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    #                             VERSION HISTORY
    #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    # v.1.0   4.29.2012
    #   - Original Release
    #
    # v.1.1   4.29.2012
    #   - Fixed a typo
    #   - Added instructions for ensuring all channels stop when player closes game
    #   - Added RPG Maker VX compatibility
    #   - Added RPG Maker VX Ace compatibility
    #   - Fixed duration to ensure a minimum of 1 frame, else nothing happens
    #   - Added "start position" argument to "play" call. This ensures VXA
    #     functionality, as well as a minor improvement to the system
    #
    # v.1.2   7.8.2012
    #   - Fixed incorrect filepaths that mixed forward and backward slashes
    #   - Fixed a bug that would cause full paths to files not to play sometimes
    #   - Added an enumerator for iterating Audio mixers ("Audio.each_mixer")
    #   - Added methods to Game_System for memorizing/restoring audio
    #
    # v.1.3   7.5.2014
    #   - Fixed bug that would cause BGM/BGS to restart from beginning on transfer
    #     to new map with same BGM or BGS
    #
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #
    # v.1.4   31.07.2018.  ---- Fixes by Sixth!
    #   - Fixed some argument errors in the default ME and SE playing methods.
    #   - Added a way to setup a custom install folder location for the RTP files.
    #     If you didn't install those files in the default folder of the install
    #     package, this script will cause your game to crash the moment an audio
    #     file is requested.
    #
    #   - Note:
    #     If this script is installed, only mp3 files will play for some reason,
    #     even if you have codecs installed for other audio file types.
    #     K-Lite codec pack includes every audio codecs needed for any file types,
    #     yet this script refuses to play anything but mp3 files. -.-
    #
    #
    #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    #
    # Introduction:
    #
    #   This script acts a either a drop-in replacement or enhancement to the
    #   built-in Audio module. It contains a plethora of functions that the default
    #   player lacks, such as seek, pause, record, fade in/out, and many more. It
    #   works by totally bypassing the built-in audio library, and directly
    #   accessing Window's "winmm" library using Ruby's Win32API. This library is
    #   home to the Media Control Interface, or MCI, which provides functions for
    #   manipulating audio and video. This allows for much more control over sound,
    #   and the ability for RPG Maker to play additional audio formats, since all
    #   that is needed is to distribute the appropriate codecs along with your game
    #   in order to use them.
    #
    # Features:
    #
    #   - Full seek functions, accurate to the millisecond
    #   - Ability to read file lengths
    #   - Ability to pause and resume a playing file
    #   - Functions for transitioning from one volume to another over a given number
    #     frames, to both lower and higher volumes
    #   - Can create as many mixers as needed, which allows to play multiple sounds
    #     simultaneously, which gives the ability to play more than one BGM or BGS
    #     at a time
    #   - Record function to capture input from the user (.wav format only)
    #   - Ability to set volume to left and right speaker independently
    #   - Mute functions
    #   - Can set the speed of playback
    #   - Ability to set treble and bass (Not all devices support this)
    #   - Searches RTP files and uses them automatically if file is not local
    #   - Easy access for additional calls to the Media Control Interface
    #   - No porting external libraries with your game, all functioning is done
    #     within the script and the operating system
    #   - Can use any audio format you wish, so long as the appropriate codec is
    #     installed on the host computer
    #   - Full compatibility with RMXP, RMVX, and RMVX Ace
    #   - Memorize and restore all/some channels
    #
    # Instructions:
    #
    #   - Scroll below to make the setting for what RPG Maker version you are using
    #   - Place script anywhere above Main
    #   - Only one setting (below) to set the MCI player as default audio player,
    #     which is by default "true". If false, the MCI player will only be used for
    #     special circumstances via script calls
    #   - There are a whole bunch of new script calls available for the Audio module
    #     There is full documentation if you look below, which I reccomend that you
    #     look at if you choose to use this script, but most are self-explanatory.
    #     Here is a partial list, words in caps are arguments that need replaced.
    #
    #     - play(FILENAME, VOLUME, SPEED, REPEAT)
    #     - pause
    #     - restart
    #     - pause
    #     - resume
    #     - stop
    #     - close
    #     - volume/volume=
    #     - set_volume_left(VOLUME)
    #     - set_volume_right(VOLUME)
    #     - mute
    #     - fade(DURATION, FRAMES)
    #     - speed/speed=
    #     - duration
    #     - position/position=
    #     - seek(MILLISECOND)
    #     - playing?
    #     - paused?
    #     - recording?
    #     - treble/treble=
    #     - bass/bass=
    #     - status
    #     - record(BITS_PER_SAMPLE, SAMPLERATE, CHANNELS)
    #     - save(FILENAME)
    #    
    #     To play a sound on a mixer is pretty straightforward, you don't even need
    #     to create a mixer first, that is done automatically. All you need to do
    #     is use a unique name for each mixer you want to control, and use the
    #     above methods on it. For example, to play a file:
    #
    #     Audio['myName'].play('myFile.mp3')
    #
    #     From here on, you can access the same mixer using 'myName' as the key to
    #     perform further actions. Such as...
    #
    #     Audio['myName'].pause            - Pauses playback
    #     Audio['myName'].resume           - Resumes playback
    #     Audio['myName'].volume = 50      - Sets volume to fifty
    #     Audio['myName'].fade(80, 0)      - Fades volume to 0 in 80 frames
    #     Audio['myName'].fade(240, 100)   - Transitions volume to 100 in 6 seconds
    #     Audio['myName'].record           - Begins recording
    #     Audio['myName'].save('file.wav') - Saves recorded file
    #     Audio['myName'].seek(4500)       - Sets playback position to 4.5 seconds
    #
    #     As you can see, all mixers are accessed in a hash-like way via the Audio
    #     module. By default, 'BGM', 'BGS', 'ME', and 'SE' are mixers used as
    #     replacements for the built-in audio library, so use names other them if
    #     creating additional mixers.
    #
    #     There is also a script call to make a call using mciSendString if you are
    #     familiar with the library at all. I simplified it down a bit, but it can
    #     be used with the following snippet:
    #
    #         Audio.mci_eval(MCI_COMMAND)
    #
    #     For more information about using MCI commands, please see the full
    #     documentation at MSDN.
    #
    # http://msdn.microsoft.com/en-us/library/windows/desktop/dd743572(v=vs.85).aspx
    #
    #     There are also a few methods available via the Game_System class for
    #     memorizing/restoring audio at its current position.
    #
    #     ex.
    #
    #     $game_system.memorize_audio               - Memorize all channels
    #     $game_system.memorize_audio('BGM')        - Memorize BGM channel
    #     $game_system.memorize_audio('BGM', 'BGS') - Memorize BGM and BGS channels
    #     $game_system.restore_audio                - Restore all channels
    #     $game_system.restore_audio('BGM')         - Restore BGM channel
    #     $game_system.restore_audio('BGM', 'BGS')  - Restore BGM and BGS channels
    #
    #     You can use as many arguments for each method as you wish. Omitting
    #     arguments will simply have it memorize/restore all channels.
    #
    # Compatibility:
    #
    #   - Not tested on Linux running under Wine. If anyone tests this, let me know.
    #   - Not compatible with DREAM.
    #
    # Credits/Thanks:
    #
    #   - ForeverZer0, for the script
    #
    # Authors Notes:
    #
    #   - Changing pitch will be different. MCI does not have a function for this,
    #     so I am changing the speed as a generic substitute. Changing the speed
    #     does change the pitch, but it true sound pitch alters the sampling rate
    #     as well, which this player does not. You have a couple alternatives if
    #     you absolutely need the pitch change:
    #
    #     1. Edit the files using an external editor and import it
    #     2. Use the default system to play such sounds using the alias names.
    #
    #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
    
    #===============================================================================
    # ** Audio
    #===============================================================================
    
    module Audio
    
      # Set true/false if the MCI Player should be the default audio controller. It
      # will maintain all the same functionality as the standard control, and all
      # normal calls to the Audio module will work as normal, but it will also
      # extend the its functionality. If false, RMXP's standard library will be used
      # and this player will only be used for custom mixers.
      MCI_DEFAULT = true
     
      # Due to vast differences in how the format is handled, some of this player's
      # features do not work with MIDI, most notably volume control, which also
      # effects fading. I have created alternate controls to control MIDI volume,
      # but they can only work with the sacrifice of many other functions, and the
      # volume applies to ALL playing MIDIs, not just the one the volume is applied
      # to. I decided this was not worth it, so I omitted volume control. If you
      # are willing to make them sacrifices, you can enable this mode by setting
      # MIDI_MODE to true. If you your game relies heavily on MIDI, but you still
      # would like to use this script, there are conversion programs available,
      # which I can assist you with if need be.
      MIDI_MODE = false
     
      # Enter the code for the version of RPG Maker you are using this script for.
      #   RMXP  = 0
      #   RMVX  = 1
      #   RMVXA = 2
      RPG_VERSION = 2
     
      # Added by Sixth!
      # If you installed the RTP files in non-default folder, you must setup the
      # install location you choose here, otherwise your game will crash the moment
      # any kind of audio file is requested!
      # If you installed it in the default install directory, you can set this to
      # nil, and that directory will be read automatically.
      RTP_FOLDER = "D:/RPG Maker VX Ace/RTP Resources/RPGVXAce"
     
    #===============================================================================
    # ** Mixer
    #-------------------------------------------------------------------------------
    # This class acts a wrapper for Window's Media Control Interface (MCI).
    #===============================================================================
    
      MCISendString = Win32API.new('winmm', 'mciSendString', 'PPLL', 'L')
      MIDIOutSetVolume = Win32API.new('winmm', 'midiOutSetVolume', 'LL', 'L')
    
      class Mixer
       
        attr_reader :name           # The arbitrary name of the mixer
        attr_reader :filepath       # The path of the currently loaded file
        attr_reader :repeat         # Flag if playback is set to repeat
        attr_reader :filename       # Name of the file being played
       
        #---------------------------------------------------------------------------
        # * Object Initialization
        #     name    : The unique name of the mixer
        #---------------------------------------------------------------------------
        def initialize(name)
          @name = name
          @fade_duration = 0
          @fade_volume = 0
          @opened = false
          @repeat = false
          @midi = false
        end
        #---------------------------------------------------------------------------
        # * Load a file and prepare for playback
        #     filename  : The path to the file.
        #---------------------------------------------------------------------------
        def open(filename)
          if File.exists?(filename)
            path = filename
          else
            path = Dir::glob(filename + '.*')[0]
            if path == nil
              directory = File.dirname(filename)
              if RTP.subfolder?(directory)
                file = File.basename(filename)
                path = RTP[directory].find {|f| File.basename(f, '.*') == file }
              end
            end
          end
          @filepath = path.gsub(/\\/, '/')
          #p "path: #{@filepath}"
          if MIDI_MODE && ['.mid', '.midi'].include?(File.extname(path))
            @midi = true
            cmd = "open \"#{path}\" type sequencer alias #{@name}"
          else
            cmd = "open \"#{path}\" type mpegvideo alias #{@name}"
          end
          MCISendString.call(cmd, nil, 0, 0)
          MCISendString.call("set #{@name} time format milliseconds", nil, 0, 0)
          MCISendString.call("set #{@name} seek exactly on", nil, 0, 0)
          @opened = true
        end
        #---------------------------------------------------------------------------
        # * Began playback on the mixer
        #     filename  : The name of the file to play
        #     volume    : The volume to play the file at
        #     speed     : The speed to play the the fule at
        #     repeat    : Flag if playback should loop after done playing
        #     start     : The position, in milliseconds, to begin playback at
        #---------------------------------------------------------------------------
        def play(filename, volume = 100, speed = 100, repeat = false, start = 0)
          @filename = filename
          #p "filename: #{filename}"
          self.close
          self.open(filename)
          self.speed = speed
          if MIDI_MODE && @midi
            @midi_master = @midi_right = @midi_left = volume * 10
            self.set_volume(volume)
            MCISendString.call("play #{@name} from 0", nil, 0, 0)
            return
          end
          self.set_volume(volume)
          @repeat = repeat
          if start != 0
            MCISendString.call("seek #{@name} to #{start}", nil, 0, 0)
          else
            MCISendString.call("seek #{@name} to start", nil, 0, 0)
          end
          MCISendString.call("play #{@name}#{repeat ? ' repeat' : ''}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Restarts playback of the currently loaded file from the beginning
        #---------------------------------------------------------------------------
        def restart
          MCISendString.call("seek #{@name} to start", nil, 0, 0)
          MCISendString.call("play #{@name}#{repeat ? ' repeat' : ''}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Pause playback and maintain current position
        #---------------------------------------------------------------------------
        def pause
          MCISendString.call("pause #{@name}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Resume playback on a paused mixer from previous position
        #---------------------------------------------------------------------------
        def resume
          MCISendString.call("resume #{@name}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Stops playback, setting position back to the start
        #---------------------------------------------------------------------------
        def stop
          MCISendString.call("seek #{@name} to start", nil, 0, 0)
          MCISendString.call("stop #{@name}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Closes the opened file and frees it from memory
        #---------------------------------------------------------------------------
        def close
          MCISendString.call("close #{@name}", nil, 0, 0)
          @filepath = nil
          @opened = false
        end
        #---------------------------------------------------------------------------
        # * Gets the actual volume of the mixer, an integer from 0 to 1000
        #---------------------------------------------------------------------------
        def real_volume
          if MIDI_MODE && @midi
            return @midi_master
          else
            data = "\0" * 128
            MCISendString.call("status #{@name} volume", data, 128, 0)
            return data.delete("\0").to_i
          end
        end
        #---------------------------------------------------------------------------
        # * Sets the actual volume of the mixer
        #     value    : The volume level, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def real_volume=(value)
          self.set_real_volume(value)
        end
        #---------------------------------------------------------------------------
        # * Sets the actual volume of the mixer
        #     value    : The volume level, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def set_real_volume(value)
          vol = [0, [value, 1000].min].max
          if MIDI_MODE && @midi
            @midi_master = value
            self.set_midi_volume(value, value)
          else
            MCISendString.call("setaudio #{@name} volume to #{vol}", nil, 0, 0)
          end
        end
        #---------------------------------------------------------------------------
        # * Returns the volume of the mixer
        #---------------------------------------------------------------------------
        def volume
          return self.real_volume / 10  
        end
        #---------------------------------------------------------------------------
        # * Sets the volume of the mixer
        #     value    : The volume level, integer between 0 and 100
        #---------------------------------------------------------------------------
        def volume=(value)
          value = [0, [value, 100].min].max
          self.set_real_volume(value * 10)
        end
        #---------------------------------------------------------------------------
        # * Sets the volume of the mixer
        #     value    : The volume level, integer between 0 and 100
        #---------------------------------------------------------------------------
        def set_volume(value)
          value = [0, [value, 100].min].max
          self.set_real_volume(value * 10)
        end
        #---------------------------------------------------------------------------
        # * Set the volume of the mixer in the left speaker only
        #     value   : Volume level, integer between 0 and 100
        #---------------------------------------------------------------------------
        def set_volume_left(value)
          vol = [0, [value * 10, 1000].min].max
          if MIDI_MODE && @midi
            self.set_midi_volume(value, @midi_right)
          else
            MCISendString.call("setaudio #{@name} left volume to #{vol}", nil, 0, 0)
          end
        end
        #---------------------------------------------------------------------------
        # * Set the volume of the mixer in the right speaker only
        #     value   : Volume level, integer between 0 and 100
        #---------------------------------------------------------------------------
        def set_volume_right(value)
          vol = [0, [value * 10, 1000].min].max
          if MIDI_MODE && @midi
            self.set_midi_volume(@midi_left, value)
          else
            MCISendString.call("setaudio #{@name} right volume to #{vol}", nil, 0, 0)
          end
        end
        #---------------------------------------------------------------------------
        # * Special handling for adjusting MIDI volume. MCI cannot handle the volume
        #   for this format directly, so we need to precalculate the channels and
        #   make the call to the MIDI synthesizer ourselves.
        #     left    : The volume of the left channel
        #     right   : The volume of the right channel
        #
        #   NOTE:
        #   It is recommended that you do not call this method directly.
        #---------------------------------------------------------------------------
        def set_midi_volume(left, right)
          @midi_left, @midi_right = left, right
          left = (0xFFFF * (left / 1000.0)).round
          right = (0xFFFF * (right / 1000.0)).round
          vol = right.to_s(16) + left.to_s(16)
          MIDIOutSetVolume.call(0, vol.to_i(16))
        end
        #---------------------------------------------------------------------------
        # * Mutes sound from the mixer
        #     bool    : True/false flag to mute/unmute sound
        #---------------------------------------------------------------------------
        def mute(bool)
          MCISendString.call("setaudio #{@name} #{bool ? 'on' : 'off'}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Transition volume to another volume
        #     duration    : The number of frames the transition will take
        #     target      : The target volume to transition to
        #---------------------------------------------------------------------------
        def fade(duration, target = 0)
          @fade_volume = target * 10
          @fade_duration = [duration, 1].max
        end
        #---------------------------------------------------------------------------
        # * Returns the current speed of playback  (100 = normal)
        #---------------------------------------------------------------------------
        def speed
          data = "\0" * 256
          MCISendString.call("status #{@name} speed", data, 256, 0)
          data.delete!("\0")
          return data.to_i / 10
        end
        #---------------------------------------------------------------------------
        # * Set the current speed of playback
        #     value   : The rate of playback to set
        #---------------------------------------------------------------------------
        def speed=(value)
          set_speed(value)
        end
        #---------------------------------------------------------------------------
        # * Set the current speed of playback
        #     value   : The rate of playback to set
        #---------------------------------------------------------------------------
        def set_speed(value)
          value = [0, [2000, value * 10].min].max
          MCISendString.call("set #{@name} speed #{value}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Gets the length of the loaded file in milliseconds
        #---------------------------------------------------------------------------
        def duration
          if self.playing?
            length = "\0" * 256
            MCISendString.call("status #{@name} length", length, 256, 0)
            length.delete!("\0")
            return length.to_i
          end
          return 0
        end
        #---------------------------------------------------------------------------
        # * Returns duration as a string in normal MM:SS format
        #---------------------------------------------------------------------------
        def duration_string
          seconds = self.duration / 1000
          return sprintf('%2d:%02d', seconds / 60, seconds % 60)
        end
        #---------------------------------------------------------------------------
        # * Returns the current position of playback, in milliseconds
        #---------------------------------------------------------------------------
        def position
          pos = "\0" * 256
          MCISendString.call("status #{@name} position", pos, 256, 0)
          pos.delete!("\0")
          return pos.to_i
        end
        #---------------------------------------------------------------------------
        # * Returns current position as a string in normal MM:SS format
        #---------------------------------------------------------------------------
        def position_string
          seconds = self.position / 1000
          return sprintf('%2d:%02d', seconds / 60, seconds % 60)
        end
        #---------------------------------------------------------------------------
        # * Sets the current playback position
        #     value   : The time in milliseconds to set current playback
        #---------------------------------------------------------------------------
        def position=(value)
          self.seek(value)
        end
        #---------------------------------------------------------------------------
        # * Sets the current playback position
        #     value   : The time in milliseconds to set current playback
        #---------------------------------------------------------------------------
        def seek(value)
          cmd = "#{self.playing? ? 'play' : 'seek'} #{@name} from #{value}"
          MCISendString.call(cmd, nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Returns tha "playing" status of the mixer
        #---------------------------------------------------------------------------
        def playing?
          return self.status == 'playing'
        end
        #---------------------------------------------------------------------------
        # * Returns tha "paused" status of the mixer
        #---------------------------------------------------------------------------
        def paused?
          return self.status == 'paused'
        end
        #---------------------------------------------------------------------------
        # * Returns true/false if file is currently loaded for playback
        #---------------------------------------------------------------------------
        def opened?
          return @opened
        end
        #---------------------------------------------------------------------------
        # * Returns true/false if mixer is currently recording
        #---------------------------------------------------------------------------
        def recording?
          return self.status == 'recording'
        end
        #---------------------------------------------------------------------------
        # * Returns the mixer's bass value
        #---------------------------------------------------------------------------
        def treble
          data = "\0" * 128
          MCISendString.call("status #{@name} treble", data, 128, 0)
          data.delete!("\0")
          return data.to_i
        end
        #---------------------------------------------------------------------------
        # * Set mixer treble
        #     value   : Treble value, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def treble=(value)
          set_treble(value)
        end
        #---------------------------------------------------------------------------
        # * Set mixer treble
        #     value   : Treble value, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def set_treble(value)
          value = [0, [value, 1000].min].max
          MCISendString.call("setaudio #{@name} treble to #{value}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Returns the mixer's bass value
        #---------------------------------------------------------------------------
        def bass
          data = "\0" * 128
          MCISendString.call("status #{@name} bass", data, 128, 0)
          data.delete!("\0")
          return data.to_i
        end
        #---------------------------------------------------------------------------
        # * Set mixer bass
        #     value   : Bass value, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def bass=(value)
          set_bass(value)
        end
        #---------------------------------------------------------------------------
        # * Set mixer bass
        #     value   : Bass value, integer between 0 and 1000
        #---------------------------------------------------------------------------
        def set_bass(value)
          value = [0, [value, 1000].min].max
          MCISendString.call("setaudio #{@name} bass to #{value}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Gets the current status
        #---------------------------------------------------------------------------
        def status
          data = "\0" * 256
          MCISendString.call("status #{@name} mode", data, 256, 0)
          return data.delete("\0")
        end
        #---------------------------------------------------------------------------
        # * Begins recording from the input, typically the PC's microphone
        #     bits_ps     : Bits per sample the file will be recorded at
        #     sample_rate : Sample rate the the file will be recorded at
        #     channels    : Number of channels that will be opened for recording
        #
        #   * WARNING *
        #     Make sure that "stop", "close" or "save" is performed on this mixer
        #     within a reasonable of amount of time. While the mixer is recording,
        #     the file is held in RAM, which will become very large if left without
        #     closing it, and eventually slow down the PC and/or crash the game.
        #     Basically I'm just saying "don't forget you are recording"
        #---------------------------------------------------------------------------
        def record(bits_ps = 16, sample_rate = 44100, channels = 2)
          self.close
          MCISendString.call("open new type waveaudio alias #{@name}", nil, 0, 0)
          MCISendString.call("set #{@name} bitspersample #{bits_ps}", nil, 0, 0)
          MCISendString.call("set #{@name} samplespersec #{sample_rate}", nil, 0, 0)
          MCISendString.call("set #{@name} channels #{channels}", nil, 0, 0)
          MCISendString.call("record #{@name}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Saves a recording into WAV format
        #     filename  : The path of the file t save, must have '.wav' extension
        #---------------------------------------------------------------------------
        def save(filename)
          if self.recording?
            MCISendString.call("stop #{@name}", nil, 0, 0)
          end
          if File.extname(filename) != '.wav'
            filename += '.wav'
          end
          MCISendString.call("save #{@name} #{filename}", nil, 0, 0)
          MCISendString.call("close #{@name}", nil, 0, 0)
        end
        #---------------------------------------------------------------------------
        # * Frame update
        #---------------------------------------------------------------------------
        def update
          if @fade_duration >= 1
            d = @fade_duration
            self.set_real_volume((self.real_volume * (d - 1) + @fade_volume) / d)
            @fade_duration -= 1
          end
        end
      end
    
    #===============================================================================
    # ** Audio
    #-------------------------------------------------------------------------------
    # The metaclass of the Audio module. This class is a wrapper between the default
    # audio controls and the MCI Player controls.
    #===============================================================================
     
      class << self
        #---------------------------------------------------------------------------
        # * Use MCI Player play function
        #---------------------------------------------------------------------------
        alias mci_bgm_play bgm_play
        def bgm_play(filename, volume = 100, pitch = 100, start = 0)
          if MCI_DEFAULT
            mixer_play('BGM', filename, volume, pitch, true, start)
          elsif RPG_VERSION == 2
            mci_bgm_play(filename, volume, pitch, start)
          else
            mci_bgm_play(filename, volume, pitch)
          end
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player play function
        #---------------------------------------------------------------------------
        alias mci_bgs_play bgs_play
        def bgs_play(filename, volume = 100, pitch = 100, start = 0)
          if MCI_DEFAULT
            mixer_play('BGS', filename, volume, pitch, true, start)
          elsif RPG_VERSION == 2
            mci_bgs_play(filename, volume, pitch, start)
          else
            mci_bgs_play(filename, volume, pitch)
          end
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player play function
        #
        # - Edited by Sixth!
        #   Fixed the argument error when calling the default method.
        #---------------------------------------------------------------------------
        alias mci_me_play me_play
        def me_play(filename, volume = 100, pitch = 100, start = 0)
          if MCI_DEFAULT
            mixer_play('ME', filename, volume, pitch, false, start)
          elsif RPG_VERSION == 2
            mci_me_play(filename, volume, pitch) #, start)
          else
            mci_me_play(filename, volume, pitch)
          end
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player play function
        #
        # - Edited by Sixth!
        #   Fixed the argument error when calling the default method.
        #---------------------------------------------------------------------------
        alias mci_se_play se_play
        def se_play(filename, volume = 100, pitch = 100, start = 0)
          if MCI_DEFAULT
            mixer_play('SE', filename, volume, pitch, false, start)
          elsif RPG_VERSION == 2
            mci_se_play(filename, volume, pitch) #, start)
          else
            mci_se_play(filename, volume, pitch) #, start)
          end
        end
        #---------------------------------------------------------------------------
        # Use MCI Player to play a file on a mixer using given parameters
        #---------------------------------------------------------------------------
        def mixer_play(mixer_name, filename, volume, pitch, repeat, start = 0)
          if ['BGM', 'BGS'].include?(mixer_name)
            return if self[mixer_name].filename == filename
          end
          self[mixer_name].play(filename, volume, pitch, repeat, start)
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player stop
        #---------------------------------------------------------------------------
        alias mci_bgm_stop bgm_stop
        def bgm_stop
          MCI_DEFAULT ? @mixers['BGM'].stop : mci_bgm_stop
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player stop
        #---------------------------------------------------------------------------
        alias mci_bgs_stop bgs_stop
        def bgs_stop
          MCI_DEFAULT ? @mixers['BGS'].stop : mci_bgs_stop
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player stop
        #---------------------------------------------------------------------------
        alias mci_me_stop me_stop
        def me_stop
          MCI_DEFAULT ? @mixers['ME'].stop : mci_me_stop
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player stop
        #---------------------------------------------------------------------------
        alias mci_se_stop se_stop
        def se_stop
          MCI_DEFAULT ? @mixers['SE'].stop : mci_se_stop
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player fade
        #---------------------------------------------------------------------------
        alias mci_bgm_fade bgm_fade
        def bgm_fade(time)
          rate = RPG_VERSION == 0 ? 40 : 60
          MCI_DEFAULT ? @mixers['BGM'].fade((time / 1000) * rate) :
            mci_bgm_fade(time)
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player fade
        #---------------------------------------------------------------------------
        alias mci_bgs_fade bgs_fade
        def bgs_fade(time)
          rate = RPG_VERSION == 0 ? 40 : 60
          MCI_DEFAULT ? @mixers['BGS'].fade((time / 1000) * rate) :
            mci_bgs_fade(time)
        end
        #---------------------------------------------------------------------------
        # * Use MCI Player fade
        #---------------------------------------------------------------------------
        alias mci_me_fade me_fade
        def me_fade(time)
          rate = RPG_VERSION == 0 ? 40 : 60
          MCI_DEFAULT ? @mixers['ME'].fade((time / 1000) * rate) :
            mci_me_fade(time)
        end
      end
      #-----------------------------------------------------------------------------
      # * Gives hash-type access of the mixers of the module
      #-----------------------------------------------------------------------------
      def self.[](mixer_name)
        unless @mixers.has_key?(mixer_name)
          @mixers[mixer_name] = Mixer.new(mixer_name)
        end
        return @mixers[mixer_name]
      end
      #-----------------------------------------------------------------------------
      # * Frame update
      #-----------------------------------------------------------------------------
      def self.update
        @mixers.each_value {|mixer| mixer.update }
      end
      #-----------------------------------------------------------------------------
      # * Simplified method for making calls directly to the Media Command Interface
      #-----------------------------------------------------------------------------
      def self.mci_eval(command)
        data = "\0" * 256
        MCISendString.call(command, data, 256, 0)
        return data.delete("\0")
      end
      #-----------------------------------------------------------------------------
      # * Iterator for the Audio mixers
      #-----------------------------------------------------------------------------
      def self.each_mixer
        @mixers.each_value {|mixer| yield mixer }
      end
      #-----------------------------------------------------------------------------
      # * Object initialization
      #-----------------------------------------------------------------------------
      def self.init
        if @mixers != nil
          # Don't remove this, it prevents memory leaks when F12 us used to restart
          MCISendString.call('close all', nil, 0, 0)
        end
        @mixers = {}
        ['BGM', 'BGS', 'ME', 'SE'].each {|name| @mixers[name] = Mixer.new(name) }
      end
    end
    
    #===============================================================================
    # ** Graphics
    #-------------------------------------------------------------------------------
    # Syncs the audio update used for fade effects with the frame update
    #===============================================================================
    module Graphics
     
      class << self
       
        alias mci_player_update update
        def update
          mci_player_update
          Audio.update
        end
      end
    end
    
    #===============================================================================
    # ** RTP
    #-------------------------------------------------------------------------------
    # Provides functions for getting the games RTP path(s) and files
    #===============================================================================
    
    module RTP
     
      # RMXP
      if Audio::RPG_VERSION == 0
        SUBFOLDERS = [
          'Graphics/Animations', 'Graphics/Autotiles', 'Graphics/Battlebacks',
          'Graphics/Battlers', 'Graphics/Characters', 'Graphics/Fogs',
          'Graphics/Gameovers', 'Graphics/Icons', 'Graphics/Panoramas',
          'Graphics/Pictures', 'Graphics/Tilesets', 'Graphics/Titles',
          'Graphics/Transitions', 'Graphics/Windowskins', 'Audio/BGM',
          'Audio/BGS', 'Audio/ME', 'Audio/SE'
        ]
      # RMVX
      elsif Audio::RPG_VERSION == 1
        SUBFOLDERS = [
          'Graphics/Animations', 'Graphics/Battlers', 'Graphics/Characters',
          'Graphics/Faces', 'Graphics/Parallaxes', 'Graphics/Pictures',
          'Graphics/System', 'Audio/BGM', 'Audio/BGS', 'Audio/ME', 'Audio/SE'
        ]
      # RMVXA
      elsif Audio::RPG_VERSION == 2
        SUBFOLDERS = [
          'Graphics/Animations', 'Graphics/Battlers', 'Graphics/Characters',
          'Graphics/Faces', 'Graphics/Parallaxes', 'Graphics/Pictures',
          'Graphics/System', 'Audio/BGM', 'Audio/BGS', 'Audio/ME', 'Audio/SE'
        ]
      end
      #-----------------------------------------------------------------------------
      # * Object initialization
      #-----------------------------------------------------------------------------
      def self.init
        @ini = Win32API.new('kernel32', 'GetPrivateProfileStringA', 'PPPPLP', 'L')
        @library = "\0" * 256
        @ini.call('Game', 'Library', '', @library, 256, '.\\Game.ini')
        @library.delete!("\0")
        @rtp_path = Win32API.new(@library, 'RGSSGetRTPPath', 'L', 'L')
        @path_with_rtp = Win32API.new(@library, 'RGSSGetPathWithRTP', 'L', 'P')
        @directories = {}
        SUBFOLDERS.each {|folder| @directories[folder] = entries(folder) }
        @initialized = true
      end
      #-----------------------------------------------------------------------------
      # * Returns an array of the full paths of all the game's installed RTPs
      #
      # - Edited by Sixth!
      #   Fixed custom install folders for RTP files.
      #-----------------------------------------------------------------------------
      def self.paths
        paths = [1, 2, 3].collect {|id| @path_with_rtp.call(@rtp_path.call(id)) }
        paths = paths.find_all {|path| path != '' }
        # This is kind of a crappy way of doing this until the RMVX call works...
        if Audio::RTP_FOLDER # Added by Sixth! - Custom install folder fix for RTP!
          common = Audio::RTP_FOLDER
        else
          common = File.join(ENV['CommonProgramFiles'], 'Enterbrain')
          common = case Audio::RPG_VERSION
          when 0 then File.join(common, 'RGSS', 'Standard')
          when 1 then File.join(common, 'RGSS2', 'RPGVX')
          when 2 then File.join(common, 'RGSS3', 'RPGVXAce')
          end
        end
        if !paths.include?(common) && File.directory?(common)
          paths.push(common)
        end
        return paths
      end
      #-----------------------------------------------------------------------------
      # * Gives hash-like access to the RTP subfolders
      #-----------------------------------------------------------------------------
      def self.[](folder)
        return subfolder?(folder) ? @directories[folder] : []
      end
      #-----------------------------------------------------------------------------
      # * Returns true/false if the given subfolder exists
      #-----------------------------------------------------------------------------
      def self.subfolder?(folder)
        return @directories.has_key?(folder)
      end
      #-----------------------------------------------------------------------------
      # * Get a complete list of full paths of files found in the given subfolder
      #   subfolder   : The RTP folder whose files you want to get
      #
      # Edited by Sixth: Used simple / instead of \\. There seemed to be an issue
      #                  with the \\ way.
      #-----------------------------------------------------------------------------
      def self.entries(subfolder)
        files = []
        paths.each {|path|
          #dir = path + '\\' + subfolder
          dir = path + '/' + subfolder
          if File.directory?(dir)
            #files = (Dir.entries(dir) - ['.', '..']).collect {|f| dir + '\\' + f }
            files = (Dir.entries(dir) - ['.', '..']).collect {|f| dir + '/' + f }
          end
        }
        return files
      end
    end
    
    #===============================================================================
    # ** Game_System
    #===============================================================================
    
    class Game_System
      #-----------------------------------------------------------------------------
      # * Public Instance Variables
      #-----------------------------------------------------------------------------
      attr_accessor :memorized_audio
      #-----------------------------------------------------------------------------
      # * Memorize Audio
      #      channels : Names of channels, or omit argument to memorize all channels
      #-----------------------------------------------------------------------------
      def memorize_audio(*channels)
        @audio_memory = {}
        if channels.empty?
          Audio.each_mixer {|m|
            data = [m.filepath, m.volume, m.speed, m.repeat, m.position]
            @audio_memory[m.name] = data
          }
        else
          channels.each {|channel|
            m = Audio[channel]
            data = [m.filepath, m.volume, m.speed, m.repeat, m.position]
            @audio_memory[channel] = data
          }
        end
      end
      #-----------------------------------------------------------------------------
      # * Restore Audio
      #      channels : Names of channels, or omit argument to restore all channels
      #-----------------------------------------------------------------------------
      def restore_audio(*channels)
        unless @audio_memory == nil || @audio_memory.empty?
          keys = channels.empty? ? @audio_memory.keys : channels
          keys.each {|name|
            data = @audio_memory[name]
            if data != nil && data[0] != nil
              Audio[name].play(data[0], data[1], data[2], data[3])
              Audio[name].seek(data[4])
              @audio_memory.delete(name)
            end
          }
        end
      end
    end
    
    # Intialize the MCI Player
    RTP.init
    Audio.init
    I added a new setting for manually setting up the RTP folder for testing purposes, so edit that if you need. Note that it's there for testing only, you can NOT use files from the RTP folder in your published projects!
     
    #4

Share This Page