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.
#=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
# 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
If you have issues with SE channel, here's an additional snippet that won't stop playing SEs when new SEs are started. Put it before the line that says
Simply set the code in the configuration to what version of RPG Maker you are using.
See script for detailed instructions on scrip calls available to you, and demo for examples of a few.
On some systems, audio can continue to play after the RGSS player has closed. To prevent this, add the following line to the botton of "Main", after all the code:
. This is a single package that will cover pretty much every standard audio format available, and even a few exotics (especially with the Mega-Pack).
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:
For more information about using MCI commands, please see the full documentation at
.
Most audio formats should play by default on most systems, but most notably is a lack of an OGG codec that will be used by default.
.