Advertisement
ForeverZer0

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

Jul 6th, 2014
300
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 42.22 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement