SHARE
TWEET

[XP][VX][VXA] MCI Audio Player

ForeverZer0 Jul 6th, 2014 193 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  2. # MCI Audio Player
  3. # Author: ForeverZer0
  4. # Version: 1.3
  5. # Date: 7.5.2014
  6. #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  7. #                             VERSION HISTORY
  8. #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  9. # v.1.0   4.29.2012
  10. #   - Original Release
  11. #
  12. # v.1.1   4.29.2012
  13. #   - Fixed a typo
  14. #   - Added instructions for ensuring all channels stop when player closes game
  15. #   - Added RPG Maker VX compatibility
  16. #   - Added RPG Maker VX Ace compatibility
  17. #   - Fixed duration to ensure a minimum of 1 frame, else nothing happens
  18. #   - Added "start position" argument to "play" call. This ensures VXA
  19. #     functionality, as well as a minor improvement to the system
  20. #
  21. # v.1.2   7.8.2012
  22. #   - Fixed incorrect filepaths that mixed forward and backward slashes
  23. #   - Fixed a bug that would cause full paths to files not to play sometimes
  24. #   - Added an enumerator for iterating Audio mixers ("Audio.each_mixer")
  25. #   - Added methods to Game_System for memorizing/restoring audio
  26. #
  27. # v.1.3   7.5.2014
  28. #   - Fixed bug that would cause BGM/BGS to restart from beginning on transfer
  29. #     to new map with same BGM or BGS
  30. #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  31. #
  32. # Introduction:
  33. #
  34. #   This script acts a either a drop-in replacement or enhancement to the
  35. #   built-in Audio module. It contains a plethora of functions that the default
  36. #   player lacks, such as seek, pause, record, fade in/out, and many more. It
  37. #   works by totally bypassing the built-in audio library, and directly
  38. #   accessing Window's "winmm" library using Ruby's Win32API. This library is
  39. #   home to the Media Control Interface, or MCI, which provides functions for
  40. #   manipulating audio and video. This allows for much more control over sound,
  41. #   and the ability for RPG Maker to play additional audio formats, since all
  42. #   that is needed is to distribute the appropriate codecs along with your game
  43. #   in order to use them.
  44. #
  45. # Features:
  46. #
  47. #   - Full seek functions, accurate to the millisecond
  48. #   - Ability to read file lengths
  49. #   - Ability to pause and resume a playing file
  50. #   - Functions for transitioning from one volume to another over a given number
  51. #     frames, to both lower and higher volumes
  52. #   - Can create as many mixers as needed, which allows to play multiple sounds
  53. #     simultaneously, which gives the ability to play more than one BGM or BGS
  54. #     at a time
  55. #   - Record function to capture input from the user (.wav format only)
  56. #   - Ability to set volume to left and right speaker independently
  57. #   - Mute functions
  58. #   - Can set the speed of playback
  59. #   - Ability to set treble and bass (Not all devices support this)
  60. #   - Searches RTP files and uses them automatically if file is not local
  61. #   - Easy access for additional calls to the Media Control Interface
  62. #   - No porting external libraries with your game, all functioning is done
  63. #     within the script and the operating system
  64. #   - Can use any audio format you wish, so long as the appropriate codec is
  65. #     installed on the host computer
  66. #   - Full compatibility with RMXP, RMVX, and RMVX Ace
  67. #   - Memorize and restore all/some channels
  68. #
  69. # Instructions:
  70. #
  71. #   - Scroll below to make the setting for what RPG Maker version you are using
  72. #   - Place script anywhere above Main
  73. #   - Only one setting (below) to set the MCI player as default audio player,
  74. #     which is by default "true". If false, the MCI player will only be used for
  75. #     special circumstances via script calls
  76. #   - There are a whole bunch of new script calls available for the Audio module
  77. #     There is full documentation if you look below, which I reccomend that you
  78. #     look at if you choose to use this script, but most are self-explanatory.
  79. #     Here is a partial list, words in caps are arguments that need replaced.
  80. #  
  81. #     - play(FILENAME, VOLUME, SPEED, REPEAT)
  82. #     - pause
  83. #     - restart
  84. #     - pause
  85. #     - resume
  86. #     - stop
  87. #     - close
  88. #     - volume/volume=
  89. #     - set_volume_left(VOLUME)
  90. #     - set_volume_right(VOLUME)
  91. #     - mute
  92. #     - fade(DURATION, FRAMES)
  93. #     - speed/speed=
  94. #     - duration
  95. #     - position/position=
  96. #     - seek(MILLISECOND)
  97. #     - playing?
  98. #     - paused?
  99. #     - recording?
  100. #     - treble/treble=
  101. #     - bass/bass=
  102. #     - status
  103. #     - record(BITS_PER_SAMPLE, SAMPLERATE, CHANNELS)
  104. #     - save(FILENAME)
  105. #    
  106. #     To play a sound on a mixer is pretty straightforward, you don't even need
  107. #     to create a mixer first, that is done automatically. All you need to do
  108. #     is use a unique name for each mixer you want to control, and use the
  109. #     above methods on it. For example, to play a file:
  110. #
  111. #     Audio['myName'].play('myFile.mp3')
  112. #
  113. #     From here on, you can access the same mixer using 'myName' as the key to
  114. #     perform further actions. Such as...
  115. #
  116. #     Audio['myName'].pause            - Pauses playback
  117. #     Audio['myName'].resume           - Resumes playback
  118. #     Audio['myName'].volume = 50      - Sets volume to fifty
  119. #     Audio['myName'].fade(80, 0)      - Fades volume to 0 in 80 frames
  120. #     Audio['myName'].fade(240, 100)   - Transitions volume to 100 in 6 seconds
  121. #     Audio['myName'].record           - Begins recording
  122. #     Audio['myName'].save('file.wav') - Saves recorded file
  123. #     Audio['myName'].seek(4500)       - Sets playback position to 4.5 seconds
  124. #
  125. #     As you can see, all mixers are accessed in a hash-like way via the Audio
  126. #     module. By default, 'BGM', 'BGS', 'ME', and 'SE' are mixers used as
  127. #     replacements for the built-in audio library, so use names other them if
  128. #     creating additional mixers.
  129. #
  130. #     There is also a script call to make a call using mciSendString if you are
  131. #     familiar with the library at all. I simplified it down a bit, but it can
  132. #     be used with the following snippet:
  133. #
  134. #         Audio.mci_eval(MCI_COMMAND)
  135. #
  136. #     For more information about using MCI commands, please see the full
  137. #     documentation at MSDN.
  138. #
  139. # http://msdn.microsoft.com/en-us/library/windows/desktop/dd743572(v=vs.85).aspx
  140. #
  141. #     There are also a few methods available via the Game_System class for
  142. #     memorizing/restoring audio at its current position.
  143. #
  144. #     ex.
  145. #
  146. #     $game_system.memorize_audio               - Memorize all channels
  147. #     $game_system.memorize_audio('BGM')        - Memorize BGM channel
  148. #     $game_system.memorize_audio('BGM', 'BGS') - Memorize BGM and BGS channels
  149. #     $game_system.restore_audio                - Restore all channels
  150. #     $game_system.restore_audio('BGM')         - Restore BGM channel
  151. #     $game_system.restore_audio('BGM', 'BGS')  - Restore BGM and BGS channels
  152. #
  153. #     You can use as many arguments for each method as you wish. Omitting
  154. #     arguments will simply have it memorize/restore all channels.
  155. #
  156. # Compatibility:
  157. #
  158. #   - Not tested on Linux running under Wine. If anyone tests this, let me know.
  159. #   - Not compatible with DREAM.
  160. #
  161. # Credits/Thanks:
  162. #
  163. #   - ForeverZer0, for the script
  164. #
  165. # Authors Notes:
  166. #
  167. #   - Changing pitch will be different. MCI does not have a function for this,
  168. #     so I am changing the speed as a generic substitute. Changing the speed
  169. #     does change the pitch, but it true sound pitch alters the sampling rate
  170. #     as well, which this player does not. You have a couple alternatives if
  171. #     you absolutely need the pitch change:
  172. #
  173. #     1. Edit the files using an external editor and import it
  174. #     2. Use the default system to play such sounds using the alias names.
  175. #
  176. #=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  177.  
  178. #===============================================================================
  179. # ** Audio
  180. #===============================================================================
  181.  
  182. module Audio
  183.  
  184.   # Set true/false if the MCI Player should be the default audio controller. It
  185.   # will maintain all the same functionality as the standard control, and all
  186.   # normal calls to the Audio module will work as normal, but it will also
  187.   # extend the its functionality. If false, RMXP's standard library will be used
  188.   # and this player will only be used for custom mixers.
  189.   MCI_DEFAULT = true
  190.  
  191.   # Due to vast differences in how the format is handled, some of this player's
  192.   # features do not work with MIDI, most notably volume control, which also
  193.   # effects fading. I have created alternate controls to control MIDI volume,
  194.   # but they can only work with the sacrifice of many other functions, and the
  195.   # volume applies to ALL playing MIDIs, not just the one the volume is applied
  196.   # to. I decided this was not worth it, so I omitted volume control. If you
  197.   # are willing to make them sacrifices, you can enable this mode by setting
  198.   # MIDI_MODE to true. If you your game relies heavily on MIDI, but you still
  199.   # would like to use this script, there are conversion programs available,
  200.   # which I can assist you with if need be.
  201.   MIDI_MODE = false
  202.  
  203.   # Enter the code for the version of RPG Maker you are using this script for.
  204.   #   RMXP  = 0
  205.   #   RMVX  = 1
  206.   #   RMVXA = 2
  207.   RPG_VERSION = 0
  208.  
  209. #===============================================================================
  210. # ** Mixer
  211. #-------------------------------------------------------------------------------
  212. # This class acts a wrapper for Window's Media Control Interface (MCI).
  213. #===============================================================================
  214.  
  215.   MCISendString = Win32API.new('winmm', 'mciSendString', 'PPLL', 'L')
  216.   MIDIOutSetVolume = Win32API.new('winmm', 'midiOutSetVolume', 'LL', 'L')
  217.  
  218.   class Mixer
  219.    
  220.     attr_reader :name           # The arbitrary name of the mixer
  221.     attr_reader :filepath       # The path of the currently loaded file
  222.     attr_reader :repeat         # Flag if playback is set to repeat
  223.     attr_reader :filename       # Name of the file being played
  224.    
  225.     #---------------------------------------------------------------------------
  226.     # * Object Initialization
  227.     #     name    : The unique name of the mixer
  228.     #---------------------------------------------------------------------------
  229.     def initialize(name)
  230.       @name = name
  231.       @fade_duration = 0
  232.       @fade_volume = 0
  233.       @opened = false
  234.       @repeat = false
  235.       @midi = false
  236.     end
  237.     #---------------------------------------------------------------------------
  238.     # * Load a file and prepare for playback
  239.     #     filename  : The path to the file.
  240.     #---------------------------------------------------------------------------
  241.     def open(filename)
  242.       if File.exists?(filename)
  243.         path = filename
  244.       else
  245.         path = Dir::glob(filename + '.*')[0]
  246.         if path == nil
  247.           directory = File.dirname(filename)
  248.           if RTP.subfolder?(directory)
  249.             file = File.basename(filename)
  250.             path = RTP[directory].find {|f| File.basename(f, '.*') == file }
  251.           end
  252.         end
  253.       end
  254.       @filepath = path.gsub(/\\/, '/')
  255.       if MIDI_MODE && ['.mid', '.midi'].include?(File.extname(path))
  256.         @midi = true
  257.         cmd = "open \"#{path}\" type sequencer alias #{@name}"
  258.       else
  259.         cmd = "open \"#{path}\" type mpegvideo alias #{@name}"
  260.       end
  261.       MCISendString.call(cmd, nil, 0, 0)
  262.       MCISendString.call("set #{@name} time format milliseconds", nil, 0, 0)
  263.       MCISendString.call("set #{@name} seek exactly on", nil, 0, 0)
  264.       @opened = true
  265.     end
  266.     #---------------------------------------------------------------------------
  267.     # * Began playback on the mixer
  268.     #     filename  : The name of the file to play
  269.     #     volume    : The volume to play the file at
  270.     #     speed     : The speed to play the the fule at
  271.     #     repeat    : Flag if playback should loop after done playing
  272.     #     start     : The position, in milliseconds, to begin playback at
  273.     #---------------------------------------------------------------------------
  274.     def play(filename, volume = 100, speed = 100, repeat = false, start = 0)
  275.       @filename = filename
  276.       self.close
  277.       self.open(filename)
  278.       self.speed = speed
  279.       if MIDI_MODE && @midi
  280.         @midi_master = @midi_right = @midi_left = volume * 10
  281.         self.set_volume(volume)
  282.         MCISendString.call("play #{@name} from 0", nil, 0, 0)
  283.         return
  284.       end
  285.       self.set_volume(volume)
  286.       @repeat = repeat
  287.       if start != 0
  288.         MCISendString.call("seek #{@name} to #{start}", nil, 0, 0)
  289.       else
  290.         MCISendString.call("seek #{@name} to start", nil, 0, 0)
  291.       end
  292.       MCISendString.call("play #{@name}#{repeat ? ' repeat' : ''}", nil, 0, 0)
  293.     end
  294.     #---------------------------------------------------------------------------
  295.     # * Restarts playback of the currently loaded file from the beginning
  296.     #---------------------------------------------------------------------------
  297.     def restart
  298.       MCISendString.call("seek #{@name} to start", nil, 0, 0)
  299.       MCISendString.call("play #{@name}#{repeat ? ' repeat' : ''}", nil, 0, 0)
  300.     end
  301.     #---------------------------------------------------------------------------
  302.     # * Pause playback and maintain current position
  303.     #---------------------------------------------------------------------------
  304.     def pause
  305.       MCISendString.call("pause #{@name}", nil, 0, 0)
  306.     end
  307.     #---------------------------------------------------------------------------
  308.     # * Resume playback on a paused mixer from previous position
  309.     #---------------------------------------------------------------------------
  310.     def resume
  311.       MCISendString.call("resume #{@name}", nil, 0, 0)
  312.     end
  313.     #---------------------------------------------------------------------------
  314.     # * Stops playback, setting position back to the start
  315.     #---------------------------------------------------------------------------
  316.     def stop
  317.       MCISendString.call("seek #{@name} to start", nil, 0, 0)
  318.       MCISendString.call("stop #{@name}", nil, 0, 0)
  319.     end
  320.     #---------------------------------------------------------------------------
  321.     # * Closes the opened file and frees it from memory
  322.     #---------------------------------------------------------------------------
  323.     def close
  324.       MCISendString.call("close #{@name}", nil, 0, 0)
  325.       @filepath = nil
  326.       @opened = false
  327.     end
  328.     #---------------------------------------------------------------------------
  329.     # * Gets the actual volume of the mixer, an integer from 0 to 1000
  330.     #---------------------------------------------------------------------------
  331.     def real_volume
  332.       if MIDI_MODE && @midi
  333.         return @midi_master
  334.       else
  335.         data = "\0" * 128
  336.         MCISendString.call("status #{@name} volume", data, 128, 0)
  337.         return data.delete("\0").to_i
  338.       end
  339.     end
  340.     #---------------------------------------------------------------------------
  341.     # * Sets the actual volume of the mixer
  342.     #     value    : The volume level, integer between 0 and 1000
  343.     #---------------------------------------------------------------------------
  344.     def real_volume=(value)
  345.       self.set_real_volume(value)
  346.     end
  347.     #---------------------------------------------------------------------------
  348.     # * Sets the actual volume of the mixer
  349.     #     value    : The volume level, integer between 0 and 1000
  350.     #---------------------------------------------------------------------------
  351.     def set_real_volume(value)
  352.       vol = [0, [value, 1000].min].max
  353.       if MIDI_MODE && @midi
  354.         @midi_master = value
  355.         self.set_midi_volume(value, value)
  356.       else
  357.         MCISendString.call("setaudio #{@name} volume to #{vol}", nil, 0, 0)
  358.       end
  359.     end
  360.     #---------------------------------------------------------------------------
  361.     # * Returns the volume of the mixer
  362.     #---------------------------------------------------------------------------
  363.     def volume
  364.       return self.real_volume / 10    
  365.     end
  366.     #---------------------------------------------------------------------------
  367.     # * Sets the volume of the mixer
  368.     #     value    : The volume level, integer between 0 and 100
  369.     #---------------------------------------------------------------------------
  370.     def volume=(value)
  371.       value = [0, [value, 100].min].max
  372.       self.set_real_volume(value * 10)
  373.     end
  374.     #---------------------------------------------------------------------------
  375.     # * Sets the volume of the mixer
  376.     #     value    : The volume level, integer between 0 and 100
  377.     #---------------------------------------------------------------------------
  378.     def set_volume(value)
  379.       value = [0, [value, 100].min].max
  380.       self.set_real_volume(value * 10)
  381.     end
  382.     #---------------------------------------------------------------------------
  383.     # * Set the volume of the mixer in the left speaker only
  384.     #     value   : Volume level, integer between 0 and 100
  385.     #---------------------------------------------------------------------------
  386.     def set_volume_left(value)
  387.       vol = [0, [value * 10, 1000].min].max
  388.       if MIDI_MODE && @midi
  389.         self.set_midi_volume(value, @midi_right)
  390.       else
  391.         MCISendString.call("setaudio #{@name} left volume to #{vol}", nil, 0, 0)
  392.       end
  393.     end
  394.     #---------------------------------------------------------------------------
  395.     # * Set the volume of the mixer in the right speaker only
  396.     #     value   : Volume level, integer between 0 and 100
  397.     #---------------------------------------------------------------------------
  398.     def set_volume_right(value)
  399.       vol = [0, [value * 10, 1000].min].max
  400.       if MIDI_MODE && @midi
  401.         self.set_midi_volume(@midi_left, value)
  402.       else
  403.         MCISendString.call("setaudio #{@name} right volume to #{vol}", nil, 0, 0)
  404.       end
  405.     end
  406.     #---------------------------------------------------------------------------
  407.     # * Special handling for adjusting MIDI volume. MCI cannot handle the volume
  408.     #   for this format directly, so we need to precalculate the channels and
  409.     #   make the call to the MIDI synthesizer ourselves.
  410.     #     left    : The volume of the left channel
  411.     #     right   : The volume of the right channel
  412.     #
  413.     #   NOTE:
  414.     #   It is recommended that you do not call this method directly.
  415.     #---------------------------------------------------------------------------
  416.     def set_midi_volume(left, right)
  417.       @midi_left, @midi_right = left, right
  418.       left = (0xFFFF * (left / 1000.0)).round
  419.       right = (0xFFFF * (right / 1000.0)).round
  420.       vol = right.to_s(16) + left.to_s(16)
  421.       MIDIOutSetVolume.call(0, vol.to_i(16))
  422.     end
  423.     #---------------------------------------------------------------------------
  424.     # * Mutes sound from the mixer
  425.     #     bool    : True/false flag to mute/unmute sound
  426.     #---------------------------------------------------------------------------
  427.     def mute(bool)
  428.       MCISendString.call("setaudio #{@name} #{bool ? 'on' : 'off'}", nil, 0, 0)
  429.     end
  430.     #---------------------------------------------------------------------------
  431.     # * Transition volume to another volume
  432.     #     duration    : The number of frames the transition will take
  433.     #     target      : The target volume to transition to
  434.     #---------------------------------------------------------------------------
  435.     def fade(duration, target = 0)
  436.       @fade_volume = target * 10
  437.       @fade_duration = [duration, 1].max
  438.     end
  439.     #---------------------------------------------------------------------------
  440.     # * Returns the current speed of playback  (100 = normal)
  441.     #---------------------------------------------------------------------------
  442.     def speed
  443.       data = "\0" * 256
  444.       MCISendString.call("status #{@name} speed", data, 256, 0)
  445.       data.delete!("\0")
  446.       return data.to_i / 10
  447.     end
  448.     #---------------------------------------------------------------------------
  449.     # * Set the current speed of playback
  450.     #     value   : The rate of playback to set
  451.     #---------------------------------------------------------------------------
  452.     def speed=(value)
  453.       set_speed(value)
  454.     end
  455.     #---------------------------------------------------------------------------
  456.     # * Set the current speed of playback
  457.     #     value   : The rate of playback to set
  458.     #---------------------------------------------------------------------------
  459.     def set_speed(value)
  460.       value = [0, [2000, value * 10].min].max
  461.       MCISendString.call("set #{@name} speed #{value}", nil, 0, 0)
  462.     end
  463.     #---------------------------------------------------------------------------
  464.     # * Gets the length of the loaded file in milliseconds
  465.     #---------------------------------------------------------------------------
  466.     def duration
  467.       if self.playing?
  468.         length = "\0" * 256
  469.         MCISendString.call("status #{@name} length", length, 256, 0)
  470.         length.delete!("\0")
  471.         return length.to_i
  472.       end
  473.       return 0
  474.     end
  475.     #---------------------------------------------------------------------------
  476.     # * Returns duration as a string in normal MM:SS format
  477.     #---------------------------------------------------------------------------
  478.     def duration_string
  479.       seconds = self.duration / 1000
  480.       return sprintf('%2d:%02d', seconds / 60, seconds % 60)
  481.     end
  482.     #---------------------------------------------------------------------------
  483.     # * Returns the current position of playback, in milliseconds
  484.     #---------------------------------------------------------------------------
  485.     def position
  486.       pos = "\0" * 256
  487.       MCISendString.call("status #{@name} position", pos, 256, 0)
  488.       pos.delete!("\0")
  489.       return pos.to_i
  490.     end
  491.     #---------------------------------------------------------------------------
  492.     # * Returns current position as a string in normal MM:SS format
  493.     #---------------------------------------------------------------------------
  494.     def position_string
  495.       seconds = self.position / 1000
  496.       return sprintf('%2d:%02d', seconds / 60, seconds % 60)
  497.     end
  498.     #---------------------------------------------------------------------------
  499.     # * Sets the current playback position
  500.     #     value   : The time in milliseconds to set current playback
  501.     #---------------------------------------------------------------------------
  502.     def position=(value)
  503.       self.seek(value)
  504.     end
  505.     #---------------------------------------------------------------------------
  506.     # * Sets the current playback position
  507.     #     value   : The time in milliseconds to set current playback
  508.     #---------------------------------------------------------------------------
  509.     def seek(value)
  510.       cmd = "#{self.playing? ? 'play' : 'seek'} #{@name} from #{value}"
  511.       MCISendString.call(cmd, nil, 0, 0)
  512.     end
  513.     #---------------------------------------------------------------------------
  514.     # * Returns tha "playing" status of the mixer
  515.     #---------------------------------------------------------------------------
  516.     def playing?
  517.       return self.status == 'playing'
  518.     end
  519.     #---------------------------------------------------------------------------
  520.     # * Returns tha "paused" status of the mixer
  521.     #---------------------------------------------------------------------------
  522.     def paused?
  523.       return self.status == 'paused'
  524.     end
  525.     #---------------------------------------------------------------------------
  526.     # * Returns true/false if file is currently loaded for playback
  527.     #---------------------------------------------------------------------------
  528.     def opened?
  529.       return @opened
  530.     end
  531.     #---------------------------------------------------------------------------
  532.     # * Returns true/false if mixer is currently recording
  533.     #---------------------------------------------------------------------------
  534.     def recording?
  535.       return self.status == 'recording'
  536.     end
  537.     #---------------------------------------------------------------------------
  538.     # * Returns the mixer's bass value
  539.     #---------------------------------------------------------------------------
  540.     def treble
  541.       data = "\0" * 128
  542.       MCISendString.call("status #{@name} treble", data, 128, 0)
  543.       data.delete!("\0")
  544.       return data.to_i
  545.     end
  546.     #---------------------------------------------------------------------------
  547.     # * Set mixer treble
  548.     #     value   : Treble value, integer between 0 and 1000
  549.     #---------------------------------------------------------------------------
  550.     def treble=(value)
  551.       set_treble(value)
  552.     end
  553.     #---------------------------------------------------------------------------
  554.     # * Set mixer treble
  555.     #     value   : Treble value, integer between 0 and 1000
  556.     #---------------------------------------------------------------------------
  557.     def set_treble(value)
  558.       value = [0, [value, 1000].min].max
  559.       MCISendString.call("setaudio #{@name} treble to #{value}", nil, 0, 0)
  560.     end
  561.     #---------------------------------------------------------------------------
  562.     # * Returns the mixer's bass value
  563.     #---------------------------------------------------------------------------
  564.     def bass
  565.       data = "\0" * 128
  566.       MCISendString.call("status #{@name} bass", data, 128, 0)
  567.       data.delete!("\0")
  568.       return data.to_i
  569.     end
  570.     #---------------------------------------------------------------------------
  571.     # * Set mixer bass
  572.     #     value   : Bass value, integer between 0 and 1000
  573.     #---------------------------------------------------------------------------
  574.     def bass=(value)
  575.       set_bass(value)
  576.     end
  577.     #---------------------------------------------------------------------------
  578.     # * Set mixer bass
  579.     #     value   : Bass value, integer between 0 and 1000
  580.     #---------------------------------------------------------------------------
  581.     def set_bass(value)
  582.       value = [0, [value, 1000].min].max
  583.       MCISendString.call("setaudio #{@name} bass to #{value}", nil, 0, 0)
  584.     end
  585.     #---------------------------------------------------------------------------
  586.     # * Gets the current status
  587.     #---------------------------------------------------------------------------
  588.     def status
  589.       data = "\0" * 256
  590.       MCISendString.call("status #{@name} mode", data, 256, 0)
  591.       return data.delete("\0")
  592.     end
  593.     #---------------------------------------------------------------------------
  594.     # * Begins recording from the input, typically the PC's microphone
  595.     #     bits_ps     : Bits per sample the file will be recorded at
  596.     #     sample_rate : Sample rate the the file will be recorded at
  597.     #     channels    : Number of channels that will be opened for recording
  598.     #
  599.     #   * WARNING *
  600.     #     Make sure that "stop", "close" or "save" is performed on this mixer
  601.     #     within a reasonable of amount of time. While the mixer is recording,
  602.     #     the file is held in RAM, which will become very large if left without
  603.     #     closing it, and eventually slow down the PC and/or crash the game.
  604.     #     Basically I'm just saying "don't forget you are recording"
  605.     #---------------------------------------------------------------------------
  606.     def record(bits_ps = 16, sample_rate = 44100, channels = 2)
  607.       self.close
  608.       MCISendString.call("open new type waveaudio alias #{@name}", nil, 0, 0)
  609.       MCISendString.call("set #{@name} bitspersample #{bits_ps}", nil, 0, 0)
  610.       MCISendString.call("set #{@name} samplespersec #{sample_rate}", nil, 0, 0)
  611.       MCISendString.call("set #{@name} channels #{channels}", nil, 0, 0)
  612.       MCISendString.call("record #{@name}", nil, 0, 0)
  613.     end
  614.     #---------------------------------------------------------------------------
  615.     # * Saves a recording into WAV format
  616.     #     filename  : The path of the file t save, must have '.wav' extension
  617.     #---------------------------------------------------------------------------
  618.     def save(filename)
  619.       if self.recording?
  620.         MCISendString.call("stop #{@name}", nil, 0, 0)
  621.       end
  622.       if File.extname(filename) != '.wav'
  623.         filename += '.wav'
  624.       end
  625.       MCISendString.call("save #{@name} #{filename}", nil, 0, 0)
  626.       MCISendString.call("close #{@name}", nil, 0, 0)
  627.     end
  628.     #---------------------------------------------------------------------------
  629.     # * Frame update
  630.     #---------------------------------------------------------------------------
  631.     def update
  632.       if @fade_duration >= 1
  633.         d = @fade_duration
  634.         self.set_real_volume((self.real_volume * (d - 1) + @fade_volume) / d)
  635.         @fade_duration -= 1
  636.       end
  637.     end
  638.   end
  639.  
  640. #===============================================================================
  641. # ** Audio
  642. #-------------------------------------------------------------------------------
  643. # The metaclass of the Audio module. This class is a wrapper between the default
  644. # audio controls and the MCI Player controls.
  645. #===============================================================================
  646.  
  647.   class << self
  648.     #---------------------------------------------------------------------------
  649.     # * Use MCI Player play function
  650.     #---------------------------------------------------------------------------
  651.     alias mci_bgm_play bgm_play
  652.     def bgm_play(filename, volume = 100, pitch = 100, start = 0)
  653.       if MCI_DEFAULT
  654.         mixer_play('BGM', filename, volume, pitch, true, start)
  655.       elsif RPG_VERSION == 2
  656.         mci_bgm_play(filename, volume, pitch, start)
  657.       else
  658.         mci_bgm_play(filename, volume, pitch)
  659.       end
  660.     end
  661.     #---------------------------------------------------------------------------
  662.     # * Use MCI Player play function
  663.     #---------------------------------------------------------------------------
  664.     alias mci_bgs_play bgs_play
  665.     def bgs_play(filename, volume = 100, pitch = 100, start = 0)
  666.       if MCI_DEFAULT
  667.         mixer_play('BGS', filename, volume, pitch, true, start)
  668.       elsif RPG_VERSION == 2
  669.         mci_bgs_play(filename, volume, pitch, start)
  670.       else
  671.         mci_bgs_play(filename, volume, pitch)
  672.       end
  673.     end
  674.     #---------------------------------------------------------------------------
  675.     # * Use MCI Player play function
  676.     #---------------------------------------------------------------------------
  677.     alias mci_me_play me_play
  678.     def me_play(filename, volume = 100, pitch = 100, start = 0)
  679.       if MCI_DEFAULT
  680.         mixer_play('ME', filename, volume, pitch, false, start)
  681.       elsif RPG_VERSION == 2
  682.         mci_me_play(filename, volume, pitch, start)
  683.       else
  684.         mci_me_play(filename, volume, pitch)
  685.       end
  686.     end
  687.     #---------------------------------------------------------------------------
  688.     # * Use MCI Player play function
  689.     #---------------------------------------------------------------------------
  690.     alias mci_se_play se_play
  691.     def se_play(filename, volume = 100, pitch = 100, start = 0)
  692.       if MCI_DEFAULT
  693.         mixer_play('SE', filename, volume, pitch, false, start)
  694.       elsif RPG_VERSION == 2
  695.         mci_se_play(filename, volume, pitch, start)
  696.       else
  697.         mci_se_play(filename, volume, pitch, start)
  698.       end
  699.     end
  700.     #---------------------------------------------------------------------------
  701.     # Use MCI Player to play a file on a mixer using given parameters
  702.     #---------------------------------------------------------------------------
  703.     def mixer_play(mixer_name, filename, volume, pitch, repeat, start = 0)
  704.       if ['BGM', 'BGS'].include?(mixer_name)
  705.         return if self[mixer_name].filename == filename
  706.       end
  707.       self[mixer_name].play(filename, volume, pitch, repeat, start)
  708.     end
  709.     #---------------------------------------------------------------------------
  710.     # * Use MCI Player stop
  711.     #---------------------------------------------------------------------------
  712.     alias mci_bgm_stop bgm_stop
  713.     def bgm_stop
  714.       MCI_DEFAULT ? @mixers['BGM'].stop : mci_bgm_stop
  715.     end
  716.     #---------------------------------------------------------------------------
  717.     # * Use MCI Player stop
  718.     #---------------------------------------------------------------------------
  719.     alias mci_bgs_stop bgs_stop
  720.     def bgs_stop
  721.       MCI_DEFAULT ? @mixers['BGS'].stop : mci_bgs_stop
  722.     end
  723.     #---------------------------------------------------------------------------
  724.     # * Use MCI Player stop
  725.     #---------------------------------------------------------------------------
  726.     alias mci_me_stop me_stop
  727.     def me_stop
  728.       MCI_DEFAULT ? @mixers['ME'].stop : mci_me_stop
  729.     end
  730.     #---------------------------------------------------------------------------
  731.     # * Use MCI Player stop
  732.     #---------------------------------------------------------------------------
  733.     alias mci_se_stop se_stop
  734.     def se_stop
  735.       MCI_DEFAULT ? @mixers['SE'].stop : mci_se_stop
  736.     end
  737.     #---------------------------------------------------------------------------
  738.     # * Use MCI Player fade
  739.     #---------------------------------------------------------------------------
  740.     alias mci_bgm_fade bgm_fade
  741.     def bgm_fade(time)
  742.       rate = RPG_VERSION == 0 ? 40 : 60
  743.       MCI_DEFAULT ? @mixers['BGM'].fade((time / 1000) * rate) :
  744.         mci_bgm_fade(time)
  745.     end
  746.     #---------------------------------------------------------------------------
  747.     # * Use MCI Player fade
  748.     #---------------------------------------------------------------------------
  749.     alias mci_bgs_fade bgs_fade
  750.     def bgs_fade(time)
  751.       rate = RPG_VERSION == 0 ? 40 : 60
  752.       MCI_DEFAULT ? @mixers['BGS'].fade((time / 1000) * rate) :
  753.         mci_bgs_fade(time)
  754.     end
  755.     #---------------------------------------------------------------------------
  756.     # * Use MCI Player fade
  757.     #---------------------------------------------------------------------------
  758.     alias mci_me_fade me_fade
  759.     def me_fade(time)
  760.       rate = RPG_VERSION == 0 ? 40 : 60
  761.       MCI_DEFAULT ? @mixers['ME'].fade((time / 1000) * rate) :
  762.         mci_me_fade(time)
  763.     end
  764.   end
  765.   #-----------------------------------------------------------------------------
  766.   # * Gives hash-type access of the mixers of the module
  767.   #-----------------------------------------------------------------------------
  768.   def self.[](mixer_name)
  769.     unless @mixers.has_key?(mixer_name)
  770.       @mixers[mixer_name] = Mixer.new(mixer_name)
  771.     end
  772.     return @mixers[mixer_name]
  773.   end
  774.   #-----------------------------------------------------------------------------
  775.   # * Frame update
  776.   #-----------------------------------------------------------------------------
  777.   def self.update
  778.     @mixers.each_value {|mixer| mixer.update }
  779.   end
  780.   #-----------------------------------------------------------------------------
  781.   # * Simplified method for making calls directly to the Media Command Interface
  782.   #-----------------------------------------------------------------------------
  783.   def self.mci_eval(command)
  784.     data = "\0" * 256
  785.     MCISendString.call(command, data, 256, 0)
  786.     return data.delete("\0")
  787.   end
  788.   #-----------------------------------------------------------------------------
  789.   # * Iterator for the Audio mixers
  790.   #-----------------------------------------------------------------------------
  791.   def self.each_mixer
  792.     @mixers.each_value {|mixer| yield mixer }
  793.   end
  794.   #-----------------------------------------------------------------------------
  795.   # * Object initialization
  796.   #-----------------------------------------------------------------------------
  797.   def self.init
  798.     if @mixers != nil
  799.       # Don't remove this, it prevents memory leaks when F12 us used to restart
  800.       MCISendString.call('close all', nil, 0, 0)
  801.     end
  802.     @mixers = {}
  803.     ['BGM', 'BGS', 'ME', 'SE'].each {|name| @mixers[name] = Mixer.new(name) }
  804.   end  
  805. end
  806.  
  807. #===============================================================================
  808. # ** Graphics
  809. #-------------------------------------------------------------------------------
  810. # Syncs the audio update used for fade effects with the frame update
  811. #===============================================================================
  812. module Graphics
  813.  
  814.   class << self
  815.    
  816.     alias mci_player_update update
  817.     def update
  818.       mci_player_update
  819.       Audio.update
  820.     end
  821.   end
  822. end
  823.  
  824. #===============================================================================
  825. # ** RTP
  826. #-------------------------------------------------------------------------------
  827. # Provides functions for getting the games RTP path(s) and files
  828. #===============================================================================
  829.  
  830. module RTP
  831.  
  832.   # RMXP
  833.   if Audio::RPG_VERSION == 0
  834.     SUBFOLDERS = [
  835.       'Graphics/Animations', 'Graphics/Autotiles', 'Graphics/Battlebacks',
  836.       'Graphics/Battlers', 'Graphics/Characters', 'Graphics/Fogs',
  837.       'Graphics/Gameovers', 'Graphics/Icons', 'Graphics/Panoramas',
  838.       'Graphics/Pictures', 'Graphics/Tilesets', 'Graphics/Titles',
  839.       'Graphics/Transitions', 'Graphics/Windowskins', 'Audio/BGM',
  840.       'Audio/BGS', 'Audio/ME', 'Audio/SE'
  841.     ]
  842.   # RMVX
  843.   elsif Audio::RPG_VERSION == 1
  844.     SUBFOLDERS = [
  845.       'Graphics/Animations', 'Graphics/Battlers', 'Graphics/Characters',
  846.       'Graphics/Faces', 'Graphics/Parallaxes', 'Graphics/Pictures',
  847.       'Graphics/System', 'Audio/BGM', 'Audio/BGS', 'Audio/ME', 'Audio/SE'
  848.     ]
  849.   # RMVXA
  850.   elsif Audio::RPG_VERSION == 2
  851.     SUBFOLDERS = [
  852.       'Graphics/Animations', 'Graphics/Battlers', 'Graphics/Characters',
  853.       'Graphics/Faces', 'Graphics/Parallaxes', 'Graphics/Pictures',
  854.       'Graphics/System', 'Audio/BGM', 'Audio/BGS', 'Audio/ME', 'Audio/SE'
  855.     ]
  856.   end
  857.   #-----------------------------------------------------------------------------
  858.   # * Object initialization
  859.   #-----------------------------------------------------------------------------
  860.   def self.init
  861.     @ini = Win32API.new('kernel32', 'GetPrivateProfileStringA', 'PPPPLP', 'L')
  862.     @library = "\0" * 256
  863.     @ini.call('Game', 'Library', '', @library, 256, '.\\Game.ini')
  864.     @library.delete!("\0")
  865.     @rtp_path = Win32API.new(@library, 'RGSSGetRTPPath', 'L', 'L')
  866.     @path_with_rtp = Win32API.new(@library, 'RGSSGetPathWithRTP', 'L', 'P')
  867.     @directories = {}
  868.     SUBFOLDERS.each {|folder| @directories[folder] = entries(folder) }
  869.     @initialized = true
  870.   end
  871.   #-----------------------------------------------------------------------------
  872.   # * Returns an array of the full paths of all the game's installed RTPs
  873.   #-----------------------------------------------------------------------------
  874.   def self.paths
  875.     paths = [1, 2, 3].collect {|id| @path_with_rtp.call(@rtp_path.call(id)) }
  876.     paths = paths.find_all {|path| path != '' }
  877.     # This is kind of a crappy way of doing this until the RMVX call works...
  878.     common = File.join(ENV['CommonProgramFiles'], 'Enterbrain')
  879.     common = case Audio::RPG_VERSION
  880.     when 0 then File.join(common, 'RGSS', 'Standard')
  881.     when 1 then File.join(common, 'RGSS2', 'RPGVX')
  882.     when 2 then File.join(common, 'RGSS3', 'RPGVXAce')
  883.     end
  884.     if !paths.include?(common) && File.directory?(common)
  885.       paths.push(common)
  886.     end
  887.     return paths
  888.   end
  889.   #-----------------------------------------------------------------------------
  890.   # * Gives hash-like access to the RTP subfolders
  891.   #-----------------------------------------------------------------------------
  892.   def self.[](folder)
  893.     return subfolder?(folder) ? @directories[folder] : []
  894.   end
  895.   #-----------------------------------------------------------------------------
  896.   # * Returns true/false if the given subfolder exists
  897.   #-----------------------------------------------------------------------------
  898.   def self.subfolder?(folder)
  899.     return @directories.has_key?(folder)
  900.   end
  901.   #-----------------------------------------------------------------------------
  902.   # * Get a complete list of full paths of files found in the given subfolder
  903.   #   subfolder   : The RTP folder whose files you want to get
  904.   #-----------------------------------------------------------------------------
  905.   def self.entries(subfolder)
  906.     files = []
  907.     paths.each {|path|
  908.       dir = path + '\\' + subfolder
  909.       if File.directory?(dir)
  910.         files = (Dir.entries(dir) - ['.', '..']).collect {|f| dir + '\\' + f }
  911.       end
  912.     }
  913.     return files
  914.   end
  915. end
  916.  
  917. #===============================================================================
  918. # ** Game_System
  919. #===============================================================================
  920.  
  921. class Game_System
  922.   #-----------------------------------------------------------------------------
  923.   # * Public Instance Variables
  924.   #-----------------------------------------------------------------------------
  925.   attr_accessor :memorized_audio
  926.   #-----------------------------------------------------------------------------
  927.   # * Memorize Audio
  928.   #      channels : Names of channels, or omit argument to memorize all channels
  929.   #-----------------------------------------------------------------------------
  930.   def memorize_audio(*channels)
  931.     @audio_memory = {}
  932.     if channels.empty?
  933.       Audio.each_mixer {|m|
  934.         data = [m.filepath, m.volume, m.speed, m.repeat, m.position]
  935.         @audio_memory[m.name] = data
  936.       }
  937.     else
  938.       channels.each {|channel|
  939.         m = Audio[channel]
  940.         data = [m.filepath, m.volume, m.speed, m.repeat, m.position]
  941.         @audio_memory[channel] = data
  942.       }
  943.     end
  944.   end
  945.   #-----------------------------------------------------------------------------
  946.   # * Restore Audio
  947.   #      channels : Names of channels, or omit argument to restore all channels
  948.   #-----------------------------------------------------------------------------
  949.   def restore_audio(*channels)
  950.     unless @audio_memory == nil || @audio_memory.empty?
  951.       keys = channels.empty? ? @audio_memory.keys : channels
  952.       keys.each {|name|
  953.         data = @audio_memory[name]
  954.         if data != nil && data[0] != nil
  955.           Audio[name].play(data[0], data[1], data[2], data[3])
  956.           Audio[name].seek(data[4])
  957.           @audio_memory.delete(name)
  958.         end
  959.       }
  960.     end
  961.   end
  962. end
  963.  
  964. # Intialize the MCI Player
  965. RTP.init
  966. Audio.init
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top