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