Code: |
# Copyright (c) 2005, Kevin Gadd #============================================================================== # ** FModEx #------------------------------------------------------------------------------ # FMOD Ex binding by Kevin Gadd (janus@luminance.org) #============================================================================== unless $FMODEX $FMODEX=true module FModEx #-------------------------------------------------------------------------- # * Constants #-------------------------------------------------------------------------- # FMOD_INITFLAGS flags FMOD_INIT_NORMAL = 0 # FMOD_RESULT flags FMOD_OK = 0 FMOD_ERR_CHANNEL_STOLEN = 11 FMOD_ERR_FILE_NOT_FOUND = 23 FMOD_ERR_INVALID_HANDLE = 36 # FMOD_MODE flags FMOD_DEFAULT = 0 FMOD_LOOP_OFF = 1 FMOD_LOOP_NORMAL = 2 FMOD_LOOP_BIDI = 4 FMOD_LOOP_BITMASK = 7 FMOD_2D = 8 FMOD_3D = 16 FMOD_HARDWARE = 32 FMOD_SOFTWARE = 64 FMOD_CREATESTREAM = 128 FMOD_CREATESAMPLE = 256 FMOD_OPENUSER = 512 FMOD_OPENMEMORY = 1024 FMOD_OPENRAW = 2048 FMOD_OPENONLY = 4096 FMOD_OPENMEMORY_POINT = 0x10000000 FMOD_ACCURATETIME = 8192 FMOD_MPEGSEARCH = 16384 FMOD_NONBLOCKING = 32768 FMOD_UNIQUE = 65536 # The default mode that the script uses FMOD_DEFAULT_SOFTWARWE = FMOD_LOOP_OFF | FMOD_2D | FMOD_SOFTWARE FMOD_TEST = FMOD_OPENMEMORY | FMOD_SOFTWARE # FMOD_CHANNELINDEX flags FMOD_CHANNEL_FREE = -1 FMOD_CHANNEL_REUSE = -2 # FMOD_TIMEUNIT_flags FMOD_TIMEUNIT_MS = 1 FMOD_TIMEUNIT_PCM = 2 # The default time unit the script uses FMOD_DEFAULT_UNIT = FMOD_TIMEUNIT_MS # Types supported by FMOD Ex FMOD_FILE_TYPES = ['ogg', 'aac', 'wma', 'mp3', 'wav', 'it', 'xm', 'mod', 's3m', 'mid', 'midi'] #============================================================================ # ** DLL #---------------------------------------------------------------------------- # A class that manages importing functions from the DLL #============================================================================ class DLL #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :filename # DLL file name for instance attr_accessor :functions # hash of functions imported (by name) Functions = {} Sys_C='System_Create' W32_LL = Win32API.new('kernel32.dll', 'LoadLibrary', 'p', 'l') FN='fmodex.dll' F='f' L='l' #-------------------------------------------------------------------------- # * Object Initialization # filename : Name of the DLL #-------------------------------------------------------------------------- def initialize(filename = FN) @filename = filename @handle = 0 # Handle to the DLL # Load specified library into the address space of game process @handle = W32_LL.call(filename) unless Functions[Sys_C] # System functions: self.import(Sys_C, 'p') self.import('System_Init', 'llll') self.import('System_Close', 'l') self.import('System_Release', 'l') self.import('System_CreateSound', 'lplpp') self.import('System_CreateStream', 'lplpp') self.import('System_PlaySound', 'llllp') # Sound functions: self.import('Sound_Release', 'l') self.import('Sound_GetMode', 'lp') self.import('Sound_SetMode', 'll') self.import('Sound_SetLoopPoints', 'lllll') self.import('Sound_GetLength', 'lpl') # Channel functions: self.import('Channel_Stop', 'l') self.import('Channel_IsPlaying', 'lp') self.import('Channel_GetPaused', 'lp') self.import('Channel_SetPaused', 'll') self.import('Channel_GetVolume', 'lp') self.import('Channel_SetVolume', 'll') self.import('Channel_GetPan', 'lp') self.import('Channel_SetPan', 'll') self.import('Channel_GetFrequency', 'lp') self.import('Channel_SetFrequency', 'll') self.import('Channel_GetPosition', 'lpl') self.import('Channel_SetPosition', 'lll') end end #-------------------------------------------------------------------------- # * Create a Win32API Object And Add it to Hashtable # name : Function name # args : Argument types (p = pointer, l = int, v = void) # returnType: Type of value returned by function #-------------------------------------------------------------------------- def import(name, args = '', returnType = L) Functions[name] = Win32API.new(@filename, 'FMOD_' + name, args, returnType) end #-------------------------------------------------------------------------- # * Get Function by Name # key : Function name #-------------------------------------------------------------------------- def [](key) return Functions[key] end #-------------------------------------------------------------------------- # * Call a Function With Passed Arguments # name : Function name # args : Argument to function #-------------------------------------------------------------------------- def invoke(name, *args) fn = Functions[name] raise "function not imported: #{name}" if fn.nil? result = fn.call(*args) unless result == FMOD_OK or result == FMOD_ERR_CHANNEL_STOLEN or result == FMOD_ERR_FILE_NOT_FOUND if result==36 Audio.se_clean unless $FMOD_CLEANING else print "FMOD Ex returned error #{result}.\n" end end return result end #-------------------------------------------------------------------------- # * Store Float as Binary Int Because Floats Can't be Passed Directly # f : Float to convert #-------------------------------------------------------------------------- def convertFloat(f) # First pack the float in a string as a native binary float temp = [f].pack(F) # Then unpack the native binary float as an integer return unpackInt(temp) end #-------------------------------------------------------------------------- # * Unpack Binary Data to Integer # s : String containing binary data #-------------------------------------------------------------------------- def unpackInt(s) return s.unpack(L)[0] end #-------------------------------------------------------------------------- # * Unpack Binary Data to Float # s : String containing binary data #-------------------------------------------------------------------------- def unpackFloat(s) return s.unpack(F)[0] end #-------------------------------------------------------------------------- # * Unpack Binary Data to Boolean # s : String containing binary data #-------------------------------------------------------------------------- def unpackBool(s) return s.unpack(L)[0] != 0 end end #============================================================================ # ** System #---------------------------------------------------------------------------- # A class that manages an instance of FMOD::System #============================================================================ class System #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to System object attr_accessor :maxChannels # Maximum number of channels System_Create='System_Create' System_Init='System_Init' System_CreateSound='System_CreateSound' UX="U*" CX="C*" System_CreateStream='System_CreateStream' System_Close='System_Close' System_Release='System_Release' #-------------------------------------------------------------------------- # * Object Initialization # fmod : An instance of DLL class # maxChannels : Maximum number of used channels # flags : FMOD_INITFLAGS # extraDriverData : Driver specific data #-------------------------------------------------------------------------- def initialize(theDLL, maxChannels = 32, flags = FMOD_INIT_NORMAL, extraDriverData = 0) @fmod = theDLL @maxChannels = maxChannels # Create and initialize FMOD::System temp = 0.chr * 4 @fmod.invoke(System_Create, temp) @handle = @fmod.unpackInt(temp) @fmod.invoke(System_Init, @handle, maxChannels, flags, extraDriverData) end #-------------------------------------------------------------------------- # * Create FMOD::Sound (fully loaded into memory by default) # filename : Name of file to open # mode : FMOD_MODE flags #-------------------------------------------------------------------------- def createSound(filename, mode = FMOD_DEFAULT_SOFTWARWE,struc=0) # Create sound and return it temp = 0.chr * 4 result = @fmod.invoke(System_CreateSound, @handle, filename, mode, struc, temp) if result == FMOD_ERR_FILE_NOT_FOUND result2 = @fmod.invoke(System_CreateSound, @handle, filename.unpack(UX).pack(CX), mode, 0, temp) raise "File not found: \"#{filename}\"" if result2 == FMOD_ERR_FILE_NOT_FOUND end newSound = Sound.new(self, @fmod.unpackInt(temp)) return newSound end #-------------------------------------------------------------------------- # * Create Streamed FMOD::Sound (chunks loaded on demand) # filename : Name of file to open # mode : FMOD_MODE flags #-------------------------------------------------------------------------- def createStream(filename, mode = FMOD_DEFAULT_SOFTWARWE,struc=0) # Create sound and return it temp = 0.chr * 4 result = @fmod.invoke(System_CreateStream, @handle, filename, mode, struc, temp) if result == FMOD_ERR_FILE_NOT_FOUND result2 = @fmod.invoke(System_CreateStream, @handle, filename.unpack(UX).pack(CX), mode, 0, temp) raise "File not found: \"#{filename}\"" if result2 == FMOD_ERR_FILE_NOT_FOUND end newSound = Sound.new(self, @fmod.unpackInt(temp)) return newSound end #-------------------------------------------------------------------------- # * Close And Release System #-------------------------------------------------------------------------- def dispose if (@handle > 0) @fmod.invoke(System_Close, @handle) @fmod.invoke(System_Release, @handle) @handle = 0 end @fmod = nil end end #============================================================================ # ** Sound #---------------------------------------------------------------------------- # A class that manages an instance of FMOD::Sound #============================================================================ class Sound #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :system # System that created this Sound attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to Sound object System_PlaySound='System_PlaySound' Sound_GetMode='Sound_GetMode' Sound_SetMode='Sound_SetMode' Sound_GetLength='Sound_GetLength' Sound_SetLoopPoints='Sound_SetLoopPoints' Sound_Release='Sound_Release' #-------------------------------------------------------------------------- # * Object Initialization # theSystem : The System that created this Sound object # handle : Handle to the FMOD::Sound object #-------------------------------------------------------------------------- def initialize(theSystem, theHandle) @system = theSystem @fmod = theSystem.fmod @handle = theHandle end #-------------------------------------------------------------------------- # * Play Sound # paused : Start paused? # channel : Channel allocated to sound (nil for automatic) #-------------------------------------------------------------------------- def play(paused = false, channel = nil) # If channel wasn't specified, let FMOD pick a free one, # otherwise use the passed channel (id from 0 to maxChannels) unless channel temp = 0.chr * 4 else temp = [channel].pack('l') end @fmod.invoke(System_PlaySound, @system.handle, (channel == nil) ? FMOD_CHANNEL_FREE : FMOD_CHANNEL_REUSE, @handle, (paused == true) ? 1 : 0, temp) theChannel = @fmod.unpackInt(temp) # Create a Channel object based on returned channel newChannel = Channel.new(self, theChannel) return newChannel end #-------------------------------------------------------------------------- # * Get FMOD_MODE Bits #-------------------------------------------------------------------------- def mode temp = 0.chr * 4 @fmod.invoke(Sound_GetMode, @handle, temp) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set FMOD_MODE Bits #-------------------------------------------------------------------------- def mode=(newMode) @fmod.invoke(Sound_SetMode, @handle, newMode) end #-------------------------------------------------------------------------- # * Get FMOD_LOOP_MODE #-------------------------------------------------------------------------- def loopMode temp = 0.chr * 4 @fmod.invoke(Sound_GetMode, @handle, temp) return @fmod.unpackInt(temp) & FMOD_LOOP_BITMASK end #-------------------------------------------------------------------------- # * Set FMOD_LOOP_MODE #-------------------------------------------------------------------------- def loopMode=(newMode) @fmod.invoke(Sound_SetMode, @handle, (self.mode & ~FMOD_LOOP_BITMASK) | newMode) end #-------------------------------------------------------------------------- # * Return Sound Length #-------------------------------------------------------------------------- def length(unit = FMOD_DEFAULT_UNIT) temp = 0.chr * 4 @fmod.invoke(Sound_GetLength, @handle, temp, unit) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def setLoopPoints(first, second, unit = FMOD_DEFAULT_UNIT) @fmod.invoke(Sound_SetLoopPoints, @handle, first, unit, second, unit) end #-------------------------------------------------------------------------- # * Release Sound #-------------------------------------------------------------------------- def dispose if (@handle > 0) @fmod.invoke(Sound_Release, @handle) @handle = 0 end @fmod = nil @system = nil end end #============================================================================ # ** Channel #---------------------------------------------------------------------------- # A class that represents an FMOD::Channel #============================================================================ class Channel #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :system # System that created the Sound attr_accessor :sound # Sound using the Channel attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to Sound object Channel_Stop='Channel_Stop' Channel_IsPlaying='Channel_IsPlaying' Channel_GetVolume='Channel_GetVolume' Channel_SetVolume='Channel_SetVolume' Channel_GetPan='Channel_GetPan' Channel_SetPan='Channel_SetPan' Channel_GetFrequency='Channel_GetFrequency' Channel_SetFrequency='Channel_SetFrequency' Channel_GetPaused='Channel_GetPaused' Channel_SetPaused='Channel_SetPaused' Channel_GetPosition='Channel_GetPosition' Channel_SetPosition='Channel_SetPosition' #-------------------------------------------------------------------------- # * Object Initialization # theSound : The Sound using this Channel object # handle : Handle to the FMOD::Channel object #-------------------------------------------------------------------------- def initialize(theSound, theHandle) @sound = theSound @system = theSound.system @fmod = theSound.system.fmod @handle = theHandle end #-------------------------------------------------------------------------- # * Stop Channel and Make it Available for Other Sounds #-------------------------------------------------------------------------- def stop @fmod.invoke(Channel_Stop, @handle) end #-------------------------------------------------------------------------- # * Is the Channel Handle Valid? #-------------------------------------------------------------------------- def valid? temp = 0.chr * 4 begin result = @fmod.invoke(Channel_IsPlaying, @handle, temp) rescue if (result == FMOD_ERR_INVALID_HANDLE) return false else raise end end # If we get here then it's valid return true end #-------------------------------------------------------------------------- # * Is the Channel Playing? #-------------------------------------------------------------------------- def playing? temp = 0.chr * 4 @fmod.invoke(Channel_IsPlaying, @handle, temp) return @fmod.unpackBool(temp) end #-------------------------------------------------------------------------- # * Get Channel Volume Level (0.0 -> 1.0) #-------------------------------------------------------------------------- def volume temp = 0.chr * 4 @fmod.invoke(Channel_GetVolume, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Volume Level (0.0 -> 1.0) #-------------------------------------------------------------------------- def volume=(newVolume) @fmod.invoke(Channel_SetVolume, @handle, @fmod.convertFloat(newVolume)) end #-------------------------------------------------------------------------- # * Get Channel Pan Position (-1.0 -> 1.0) #-------------------------------------------------------------------------- def pan temp = 0.chr * 4 @fmod.invoke(Channel_GetPan, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Pan Position (-1.0 -> 1.0) #-------------------------------------------------------------------------- def pan=(newPan) @fmod.invoke(Channel_SetPan, @handle, @fmod.convertFloat(newPan)) end #-------------------------------------------------------------------------- # * Get Channel Frequency in HZ (Speed/Pitch) #-------------------------------------------------------------------------- def frequency temp = 0.chr * 4 @fmod.invoke(Channel_GetFrequency, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Frequency in HZ (Speed/Pitch) #-------------------------------------------------------------------------- def frequency=(newFrequency) @fmod.invoke(Channel_SetFrequency, @handle, @fmod.convertFloat(newFrequency)) end #-------------------------------------------------------------------------- # * Is Channel Paused? #-------------------------------------------------------------------------- def paused temp = 0.chr * 4 @fmod.invoke(Channel_GetPaused, @handle, temp) return @fmod.unpackBool(temp) end #-------------------------------------------------------------------------- # * Pause Channel #-------------------------------------------------------------------------- def paused=(newPaused) @fmod.invoke(Channel_SetPaused, @handle, (newPaused == true) ? 1 : 0) end #-------------------------------------------------------------------------- # * Get Current Playback Position # unit : FMOD_TIMEUNIT to return position in #-------------------------------------------------------------------------- def position(unit = FMOD_DEFAULT_UNIT) temp = 0.chr * 4 @fmod.invoke(Channel_GetPosition, @handle, temp, unit) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set Current Playback Position # newPosition : New playback position # unit : FMOD_TIMEUNIT to use when setting position #-------------------------------------------------------------------------- def position=(newPosition, unit = FMOD_DEFAULT_UNIT) @fmod.invoke(Channel_SetPosition, @handle, newPosition, unit) end #-------------------------------------------------------------------------- # * Dispose of Channel #-------------------------------------------------------------------------- def dispose @handle = 0 @sound = nil @system = nil @fmod = nil end end end #============================================================================== # ** FMod #------------------------------------------------------------------------------ # A higher level module to access FMOD Ex #============================================================================== module Audio SLP_TIME=0.01 #============================================================================ # ** SoundFile #---------------------------------------------------------------------------- # Represents a Sound file (BGM, BGS, SE, etc.) and associated Channel #============================================================================ class SoundFile #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :name # File name attr_accessor :sound # FModEx::Sound object attr_accessor :channel # Channel playing sound attr_accessor :volume # Volume in RPG::AudioFile format attr_accessor :pitch # Pitch in RPG::AudioFile format attr_accessor :looping # Sound loops attr_accessor :streaming # Sound is streamed attr_accessor :length # Sound length in milliseconds #-------------------------------------------------------------------------- # * Object Initialization #-------------------------------------------------------------------------- def initialize(name, sound, channel, volume, pitch, looping, streaming, length) @name = name @sound = sound @channel = channel @volume = volume @pitch = pitch @looping = looping @streaming = streaming @length = length end end #-------------------------------------------------------------------------- # * Instance Variables #-------------------------------------------------------------------------- @fmod_dll = FModEx::DLL.new # The FMOD Ex DLL @fmod = FModEx::System.new(@fmod_dll) # The global System object @fmod_se = [] # Array of Sound Effects @rtp_folder = nil # Name of RTP folder #-------------------------------------------------------------------------- # * Get Path of RTP Folder From Registry #-------------------------------------------------------------------------- def self.getRTPFolder if @rtp_folder return @rtp_folder end open_key = Win32API.new('advapi32.dll', 'RegOpenKeyExA', 'LPLLP', 'L') query_value = Win32API.new('advapi32.dll', 'RegQueryValueExA', 'LPLPPP', 'L') close_key = Win32API.new('advapi32', 'RegCloseKey', 'L', 'L') key = 0.chr * 4 # Open a HKEY_LOCAL_MACHINE with KEY_READ attribute and save handle in key open_key.call(0x80000002, 'Software\Enterbrain\RGSS\RTP', 0, 0x20019, key) key = @fmod_dll.unpackInt(key) type = 0.chr * 4 size = 0.chr * 4 # Query to get string size query_value.call(key, 'Standard', 0, type, 0, size) data = ' ' * @fmod_dll.unpackInt(size) # Query the string value itself using size query_value.call(key, 'Standard', 0, type, data, size) @rtp_folder = data.chop close_key.call(key) # Make sure the directory ends with a backslash @rtp_folder += "\\" if @rtp_folder[-1].chr != "\\" return @rtp_folder end #-------------------------------------------------------------------------- # * Return Proper File Name (With Extensions) # name : Name of the file # extensions : Extensions to add to file name #-------------------------------------------------------------------------- def self.checkExtensions(name, extensions) if FileTest.exist?(name) return name end # Add extension if needed extensions.each do |ext| if FileTest.exist?(name + '.' + ext) return name + '.' + ext end end # File doesn't exist return name end #-------------------------------------------------------------------------- # * Get Valid File Name # name : Name of the file #-------------------------------------------------------------------------- def self.selectBGMFilename(name) name = name.gsub("/", "\\") # See if file exists in game folder localname = self.checkExtensions(name, FModEx::FMOD_FILE_TYPES) # See if file exists in RTP commonname = self.checkExtensions(getRTPFolder + name, FModEx::FMOD_FILE_TYPES) if FileTest.exist?(localname) return localname end if FileTest.exist?(commonname) return commonname end # An invalid name was provided return name end #-------------------------------------------------------------------------- # * Play a Sound File Then Return it # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the sound loop? # streaming : Stream sound or load whole thing to memory? #-------------------------------------------------------------------------- def self.play(name, volume, pitch, position, looping, streaming) # Get a valid file name filename = self.selectBGMFilename(name) # Create Sound or Stream and set initial values sound = streaming ? @fmod.createStream(filename) : @fmod.createSound(filename) sound.loopMode = looping ? FModEx::FMOD_LOOP_NORMAL : FModEx::FMOD_LOOP_OFF channel = sound.play volume = volume * 1.0 pitch = pitch * 1.0 file_length = sound.length(FModEx::FMOD_DEFAULT_UNIT) sound_file = SoundFile.new(filename, sound, channel, volume, pitch, looping, streaming, file_length) sound_file.channel.volume = volume / 100.0 sound_file.channel.frequency = sound_file.channel.frequency * pitch / 100 sound_file.channel.position = position return sound_file end #-------------------------------------------------------------------------- # * Stop and Dispose of Sound File #-------------------------------------------------------------------------- def self.stop(sound_file) unless sound_file and sound_file.channel return end # Stop channel, then clear variables and dispose of bgm sound_file.channel.stop sound_file.channel = nil sound_file.sound.dispose end #-------------------------------------------------------------------------- # * Return Length in Milliseconds #-------------------------------------------------------------------------- def self.get_length(sound_file, unit = FModEx::FMOD_DEFAULT_UNIT) return sound_file.length#(unit) end #-------------------------------------------------------------------------- # * Check if Another Sound File is Playing #-------------------------------------------------------------------------- def self.already_playing?(sound_file, name, position = 0) # Get a valid file name filename = self.selectBGMFilename(name) if (sound_file) # If the same sound file is already playing don't play it again if (sound_file.name == filename and position == 0) return true end # If another sound file is playing, stop it if sound_file.channel self.stop(sound_file) end end # No sound file is playing or it was already stopped return false end #-------------------------------------------------------------------------- # * Check if Sound File is Playing #-------------------------------------------------------------------------- def self.playing?(sound_file) unless sound_file and sound_file.channel return false end return sound_file.channel.playing? end #-------------------------------------------------------------------------- # * Get Current Sound File Playing Position #-------------------------------------------------------------------------- def self.get_position(sound_file) unless sound_file and sound_file.channel return 0 end return sound_file.channel.position end #-------------------------------------------------------------------------- # * Seek to a New Sound File Playing Position #-------------------------------------------------------------------------- def self.set_position(sound_file, new_pos) unless sound_file and sound_file.channel return end sound_file.channel.position = new_pos end #-------------------------------------------------------------------------- # * Get Current Sound File Volume #-------------------------------------------------------------------------- def self.get_volume(sound_file) unless sound_file return 0 end return sound_file.volume end #-------------------------------------------------------------------------- # * Set Sound File Volume #-------------------------------------------------------------------------- def self.set_volume(sound_file, volume) unless sound_file and sound_file.channel return end sound_file.volume = volume * 1.0 sound_file.channel.volume = volume / 100.0 end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds (-1 for file end) # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.set_loop_points(sound_file, first, second, unit = FModEx::FMOD_DEFAULT_UNIT) unless sound_file and sound_file.channel return end # If second is -1 then set loop end to the file end if second == -1 second = sound_file.length - 1 end # Set loop points and reflush stream buffer sound_file.channel.sound.setLoopPoints(first, second, unit) sound_file.channel.position = sound_file.channel.position return sound_file end #-------------------------------------------------------------------------- # * Play ME # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGM loop? #-------------------------------------------------------------------------- def self.me_play(name, volume=100, pitch=100, position = 0, looping = false) return if self.already_playing?(@fmod_me, name, position) and Audio.me_playing? # Now play the new BGM as a stream @fmod_me = self.play(name, volume, pitch, position, false, true) Thread.new do if @fmod_bgm paused=@fmod_bgm.channel.paused @fmod_bgm.channel.paused=true else paused=false end loop do unless @fmod_me break end unless Audio.me_playing? break end sleep(SLP_TIME) end if @fmod_bgm @fmod_bgm.channel.paused=paused end end return @fmod_me end #-------------------------------------------------------------------------- # * Stop and Dispose of ME #-------------------------------------------------------------------------- def self.me_stop self.stop(@fmod_me) @fmod_me = nil end #-------------------------------------------------------------------------- # * Return ME Length in Milliseconds #-------------------------------------------------------------------------- def self.me_length(sound_file) self.get_length(@fmod_me) end #-------------------------------------------------------------------------- # * Check if a ME is Playing #-------------------------------------------------------------------------- def self.me_playing? return self.playing?(@fmod_me) end #-------------------------------------------------------------------------- # * Get Current ME Playing Position #-------------------------------------------------------------------------- def self.me_position return self.get_position(@fmod_me) end #-------------------------------------------------------------------------- # * Seek to New ME Playing Position #-------------------------------------------------------------------------- def self.me_position=(new_pos) self.set_position(@fmod_bgm, new_pos) end #-------------------------------------------------------------------------- # * Get Current ME Volume #-------------------------------------------------------------------------- def self.me_volume return self.get_volume(@fmod_me) end #-------------------------------------------------------------------------- # * Set ME Volume #-------------------------------------------------------------------------- def self.me_volume=(volume) self.set_volume(@fmod_me, volume) end #-------------------------------------------------------------------------- # * Set ME fade #-------------------------------------------------------------------------- def self.me_fade(time) return unless @fmod_me and Audio.me_playing? and !@fading_me @fading_me=true Thread.new do vol=Audio.me_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.me_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.me_stop @fading_me=false end end #-------------------------------------------------------------------------- # * Play BGM (or ME) # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGM loop? #-------------------------------------------------------------------------- def self.bgm_play(name, volume=100, pitch=100, position = 0, looping = true) return if self.already_playing?(@fmod_bgm, name, position) # Now play the new BGM as a stream @fmod_bgm = self.play(name, volume, pitch, position, looping, true) if @fmod_me and Audio.me_playing? @fmod_bgm.channel.paused=true end end #-------------------------------------------------------------------------- # * Stop and Dispose of BGM #-------------------------------------------------------------------------- def self.bgm_stop self.stop(@fmod_bgm) @fmod_bgm = nil end #-------------------------------------------------------------------------- # * Return BGM Length in Milliseconds #-------------------------------------------------------------------------- def self.bgm_length(sound_file) self.get_length(@fmod_bgm) end #-------------------------------------------------------------------------- # * Check if a BGM is Playing #-------------------------------------------------------------------------- def self.bgm_playing? return self.playing?(@fmod_bgm) end #-------------------------------------------------------------------------- # * Get Current BGM Playing Position #-------------------------------------------------------------------------- def self.bgm_position return self.get_position(@fmod_bgm) end #-------------------------------------------------------------------------- # * Seek to New BGM Playing Position #-------------------------------------------------------------------------- def self.bgm_position=(new_pos) self.set_position(@fmod_bgm, new_pos) end #-------------------------------------------------------------------------- # * Get Current BGM Volume #-------------------------------------------------------------------------- def self.bgm_volume return self.get_volume(@fmod_bgm) end #-------------------------------------------------------------------------- # * Set BGM Volume #-------------------------------------------------------------------------- def self.bgm_volume=(volume) self.set_volume(@fmod_bgm, volume) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.bgm_set_loop_points(first, second, unit = FModEx::FMOD_DEFAULT_UNIT) @fmod_bgm = self.set_loop_points(@fmod_bgm, first, second, unit) end #-------------------------------------------------------------------------- # * Set BGM fade #-------------------------------------------------------------------------- def self.bgm_fade(time) return unless @fmod_bgm and Audio.bgm_playing? and !@fading_bgm @fading_bgm=true Thread.new do vol=Audio.bgm_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.bgm_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.bgm_stop @fading_bgm=false end end #-------------------------------------------------------------------------- # * Play BGS # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGS loop? #-------------------------------------------------------------------------- def self.bgs_play(name, volume=100, pitch=100, position = 0, looping = true) return if self.already_playing?(@fmod_bgs, name, position) # Now play the new BGS as a stream @fmod_bgs = self.play(name, volume, pitch, position, looping, true) end #-------------------------------------------------------------------------- # * Stop and Dispose of BGS #-------------------------------------------------------------------------- def self.bgs_stop self.stop(@fmod_bgs) @fmod_bgs = nil end #-------------------------------------------------------------------------- # * Return BGS Length in Milliseconds #-------------------------------------------------------------------------- def self.bgm_length(sound_file) self.get_length(@fmod_bgs) end #-------------------------------------------------------------------------- # * Check if a BGS is Playing #-------------------------------------------------------------------------- def self.bgs_playing? return self.playing?(@fmod_bgs) end #-------------------------------------------------------------------------- # * Get Current BGS Playing Position #-------------------------------------------------------------------------- def self.bgs_position return self.get_position(@fmod_bgs) end #-------------------------------------------------------------------------- # * Seek to New BGS Playing Position #-------------------------------------------------------------------------- def self.bgs_position=(new_pos) self.set_position(@fmod_bgs, new_pos) end #-------------------------------------------------------------------------- # * Get Current BGS Volume #-------------------------------------------------------------------------- def self.bgs_volume return self.get_volume(@fmod_bgs) end #-------------------------------------------------------------------------- # * Set BGS Volume #-------------------------------------------------------------------------- def self.bgs_volume=(volume) self.set_volume(@fmod_bgs, volume) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.bgs_set_loop_points(first, second, unit = FModEx::FMOD_DEFAULT_UNIT) @fmod_bgs = self.set_loop_points(@fmod_bgs, first, second, unit) end #-------------------------------------------------------------------------- # * Set BGS fade #-------------------------------------------------------------------------- def self.bgs_fade(time) return unless @fmod_bgs and Audio.bgs_playing? and !@fading_bgs @fading_bgs=true Thread.new do vol=Audio.bgs_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.bgs_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.me_stop @fading_bgs=false end end #-------------------------------------------------------------------------- # * Play SE # name : Name of the file # volume : Channel volume # pitch : Channel frequency #-------------------------------------------------------------------------- def self.se_play(name, volume=100, pitch=100) if @fmod_se.size > @fmod.maxChannels #msgbox_p 0 se = @fmod_se.shift #msgbox_p se self.stop(se) if self.playing?(se) end # Load SE into memory and play it @fmod_se << self.play(name, volume, pitch, 0, false, false) end #-------------------------------------------------------------------------- # * Stop and Dispose of all SEs #-------------------------------------------------------------------------- def self.se_stop for se in @fmod_se self.stop(se) if self.playing?(se) end @fmod_se.clear end #-------------------------------------------------------------------------- # * Get Rid of Non-Playing SEs #-------------------------------------------------------------------------- def self.se_clean $FMOD_CLEANING=true for se in @fmod_se unless self.playing?(se) self.stop(se) @fmod_se.delete(se) end end $FMOD_CLEANING=false end #-------------------------------------------------------------------------- # * Check if There's Some SE in SE Array #-------------------------------------------------------------------------- def self.se_list_empty? return @fmod_se.empty? end #-------------------------------------------------------------------------- # * Dispose of Everything #-------------------------------------------------------------------------- def self.dispose self.bgm_stop self.bgs_stop self.se_stop @fmod.dispose end end end module FMod include Audio end |
Code: |
# Copyright (c) 2005, Kevin Gadd #============================================================================== # ** FModEx #------------------------------------------------------------------------------ # FMOD Ex binding by Kevin Gadd (janus@luminance.org) #============================================================================== unless $FMODEX $FMODEX=true module FModEx #-------------------------------------------------------------------------- # * Constants #-------------------------------------------------------------------------- # FMOD_INITFLAGS flags FMOD_INIT_NORMAL = 0 # FMOD_RESULT flags FMOD_OK = 0 FMOD_ERR_CHANNEL_STOLEN = 11 FMOD_ERR_FILE_NOT_FOUND = 23 FMOD_ERR_INVALID_HANDLE = 36 # FMOD_MODE flags FMOD_DEFAULT = 0 FMOD_LOOP_OFF = 1 FMOD_LOOP_NORMAL = 2 FMOD_LOOP_BIDI = 4 FMOD_LOOP_BITMASK = 7 FMOD_2D = 8 FMOD_3D = 16 FMOD_HARDWARE = 32 FMOD_SOFTWARE = 64 FMOD_CREATESTREAM = 128 FMOD_CREATESAMPLE = 256 FMOD_OPENUSER = 512 FMOD_OPENMEMORY = 1024 FMOD_OPENRAW = 2048 FMOD_OPENONLY = 4096 FMOD_ACCURATETIME = 8192 FMOD_MPEGSEARCH = 16384 FMOD_NONBLOCKING = 32768 FMOD_UNIQUE = 65536 # The default mode that the script uses FMOD_DEFAULT_SOFTWARWE = FMOD_LOOP_OFF | FMOD_2D | FMOD_SOFTWARE # FMOD_CHANNELINDEX flags FMOD_CHANNEL_FREE = -1 FMOD_CHANNEL_REUSE = -2 # FMOD_TIMEUNIT_flags FMOD_TIMEUNIT_MS = 1 FMOD_TIMEUNIT_PCM = 2 # The default time unit the script uses FMOD_DEFAULT_UNIT = FMOD_TIMEUNIT_MS # Types supported by FMOD Ex FMOD_FILE_TYPES = ['ogg', 'aac', 'wma', 'mp3', 'wav', 'it', 'xm', 'mod', 's3m', 'mid', 'midi'] #============================================================================ # ** DLL #---------------------------------------------------------------------------- # A class that manages importing functions from the DLL #============================================================================ class DLL #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :filename # DLL file name for instance attr_accessor :functions # hash of functions imported (by name) Functions = {} Sys_C='System_Create' W32_LL = Win32API.new('kernel32.dll', 'LoadLibrary', 'p', 'l') FN='fmodex.dll' F='f' L='l' #-------------------------------------------------------------------------- # * Object Initialization # filename : Name of the DLL #-------------------------------------------------------------------------- def initialize(filename = FN) @filename = filename @handle = 0 # Handle to the DLL # Load specified library into the address space of game process @handle = W32_LL.call(filename) unless Functions[Sys_C] # System functions: self.import(Sys_C, 'p') self.import('System_Init', 'llll') self.import('System_Close', 'l') self.import('System_Release', 'l') self.import('System_CreateSound', 'lpllp') self.import('System_CreateStream', 'lpllp') self.import('System_PlaySound', 'llllp') # Sound functions: self.import('Sound_Release', 'l') self.import('Sound_GetMode', 'lp') self.import('Sound_SetMode', 'll') self.import('Sound_SetLoopPoints', 'lllll') self.import('Sound_GetLength', 'lpl') # Channel functions: self.import('Channel_Stop', 'l') self.import('Channel_IsPlaying', 'lp') self.import('Channel_GetPaused', 'lp') self.import('Channel_SetPaused', 'll') self.import('Channel_GetVolume', 'lp') self.import('Channel_SetVolume', 'll') self.import('Channel_GetPan', 'lp') self.import('Channel_SetPan', 'll') self.import('Channel_GetFrequency', 'lp') self.import('Channel_SetFrequency', 'll') self.import('Channel_GetPosition', 'lpl') self.import('Channel_SetPosition', 'lll') end end #-------------------------------------------------------------------------- # * Create a Win32API Object And Add it to Hashtable # name : Function name # args : Argument types (p = pointer, l = int, v = void) # returnType: Type of value returned by function #-------------------------------------------------------------------------- def import(name, args = '', returnType = L) Functions[name] = Win32API.new(@filename, 'FMOD_' + name, args, returnType) end #-------------------------------------------------------------------------- # * Get Function by Name # key : Function name #-------------------------------------------------------------------------- def [](key) return Functions[key] end #-------------------------------------------------------------------------- # * Call a Function With Passed Arguments # name : Function name # args : Argument to function #-------------------------------------------------------------------------- def invoke(name, *args) fn = Functions[name] raise "function not imported: #{name}" if fn.nil? result = fn.call(*args) unless result == FMOD_OK or result == FMOD_ERR_CHANNEL_STOLEN or result == FMOD_ERR_FILE_NOT_FOUND if result==36 Audio.se_clean else msgbox_print "FMOD Ex returned error #{result}.\n" end end return result end #-------------------------------------------------------------------------- # * Store Float as Binary Int Because Floats Can't be Passed Directly # f : Float to convert #-------------------------------------------------------------------------- def convertFloat(f) # First pack the float in a string as a native binary float temp = [f].pack(F) # Then unpack the native binary float as an integer return unpackInt(temp) end #-------------------------------------------------------------------------- # * Unpack Binary Data to Integer # s : String containing binary data #-------------------------------------------------------------------------- def unpackInt(s) return s.unpack(L)[0] end #-------------------------------------------------------------------------- # * Unpack Binary Data to Float # s : String containing binary data #-------------------------------------------------------------------------- def unpackFloat(s) return s.unpack(F)[0] end #-------------------------------------------------------------------------- # * Unpack Binary Data to Boolean # s : String containing binary data #-------------------------------------------------------------------------- def unpackBool(s) return s.unpack(L)[0] != 0 end end #============================================================================ # ** System #---------------------------------------------------------------------------- # A class that manages an instance of FMOD::System #============================================================================ class System #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to System object attr_accessor :maxChannels # Maximum number of channels System_Create='System_Create' System_Init='System_Init' System_CreateSound='System_CreateSound' UX="U*" CX="C*" System_CreateStream='System_CreateStream' System_Close='System_Close' System_Release='System_Release' #-------------------------------------------------------------------------- # * Object Initialization # fmod : An instance of DLL class # maxChannels : Maximum number of used channels # flags : FMOD_INITFLAGS # extraDriverData : Driver specific data #-------------------------------------------------------------------------- def initialize(theDLL, maxChannels = 32, flags = FMOD_INIT_NORMAL, extraDriverData = 0) @fmod = theDLL @maxChannels = maxChannels # Create and initialize FMOD::System temp = 0.chr * 4 @fmod.invoke(System_Create, temp) @handle = @fmod.unpackInt(temp) @fmod.invoke(System_Init, @handle, maxChannels, flags, extraDriverData) end #-------------------------------------------------------------------------- # * Create FMOD::Sound (fully loaded into memory by default) # filename : Name of file to open # mode : FMOD_MODE flags #-------------------------------------------------------------------------- def createSound(filename, mode = FMOD_DEFAULT_SOFTWARWE) # Create sound and return it temp = 0.chr * 4 result = @fmod.invoke(System_CreateSound, @handle, filename, mode, 0, temp) if result == FMOD_ERR_FILE_NOT_FOUND result2 = @fmod.invoke(System_CreateSound, @handle, filename.unpack(UX).pack(CX), mode, 0, temp) raise "File not found: \"#{filename}\"" if result2 == FMOD_ERR_FILE_NOT_FOUND end newSound = Sound.new(self, @fmod.unpackInt(temp)) return newSound end #-------------------------------------------------------------------------- # * Create Streamed FMOD::Sound (chunks loaded on demand) # filename : Name of file to open # mode : FMOD_MODE flags #-------------------------------------------------------------------------- def createStream(filename, mode = FMOD_DEFAULT_SOFTWARWE) # Create sound and return it temp = 0.chr * 4 result = @fmod.invoke(System_CreateStream, @handle, filename, mode, 0, temp) if result == FMOD_ERR_FILE_NOT_FOUND result2 = @fmod.invoke(System_CreateStream, @handle, filename.unpack(UX).pack(CX), mode, 0, temp) raise "File not found: \"#{filename}\"" if result2 == FMOD_ERR_FILE_NOT_FOUND end newSound = Sound.new(self, @fmod.unpackInt(temp)) return newSound end #-------------------------------------------------------------------------- # * Close And Release System #-------------------------------------------------------------------------- def dispose if (@handle > 0) @fmod.invoke(System_Close, @handle) @fmod.invoke(System_Release, @handle) @handle = 0 end @fmod = nil end end #============================================================================ # ** Sound #---------------------------------------------------------------------------- # A class that manages an instance of FMOD::Sound #============================================================================ class Sound #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :system # System that created this Sound attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to Sound object System_PlaySound='System_PlaySound' Sound_GetMode='Sound_GetMode' Sound_SetMode='Sound_SetMode' Sound_GetLength='Sound_GetLength' Sound_SetLoopPoints='Sound_SetLoopPoints' Sound_Release='Sound_Release' #-------------------------------------------------------------------------- # * Object Initialization # theSystem : The System that created this Sound object # handle : Handle to the FMOD::Sound object #-------------------------------------------------------------------------- def initialize(theSystem, theHandle) @system = theSystem @fmod = theSystem.fmod @handle = theHandle end #-------------------------------------------------------------------------- # * Play Sound # paused : Start paused? # channel : Channel allocated to sound (nil for automatic) #-------------------------------------------------------------------------- def play(paused = false, channel = nil) # If channel wasn't specified, let FMOD pick a free one, # otherwise use the passed channel (id from 0 to maxChannels) unless channel temp = 0.chr * 4 else temp = [channel].pack('l') end @fmod.invoke(System_PlaySound, @system.handle, (channel == nil) ? FMOD_CHANNEL_FREE : FMOD_CHANNEL_REUSE, @handle, (paused == true) ? 1 : 0, temp) theChannel = @fmod.unpackInt(temp) # Create a Channel object based on returned channel newChannel = Channel.new(self, theChannel) return newChannel end #-------------------------------------------------------------------------- # * Get FMOD_MODE Bits #-------------------------------------------------------------------------- def mode temp = 0.chr * 4 @fmod.invoke(Sound_GetMode, @handle, temp) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set FMOD_MODE Bits #-------------------------------------------------------------------------- def mode=(newMode) @fmod.invoke(Sound_SetMode, @handle, newMode) end #-------------------------------------------------------------------------- # * Get FMOD_LOOP_MODE #-------------------------------------------------------------------------- def loopMode temp = 0.chr * 4 @fmod.invoke(Sound_GetMode, @handle, temp) return @fmod.unpackInt(temp) & FMOD_LOOP_BITMASK end #-------------------------------------------------------------------------- # * Set FMOD_LOOP_MODE #-------------------------------------------------------------------------- def loopMode=(newMode) @fmod.invoke(Sound_SetMode, @handle, (self.mode & ~FMOD_LOOP_BITMASK) | newMode) end #-------------------------------------------------------------------------- # * Return Sound Length #-------------------------------------------------------------------------- def length(unit = FMOD_DEFAULT_UNIT) temp = 0.chr * 4 @fmod.invoke(Sound_GetLength, @handle, temp, unit) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def setLoopPoints(first, second, unit = FMOD_DEFAULT_UNIT) @fmod.invoke(Sound_SetLoopPoints, @handle, first, unit, second, unit) end #-------------------------------------------------------------------------- # * Release Sound #-------------------------------------------------------------------------- def dispose if (@handle > 0) @fmod.invoke(Sound_Release, @handle) @handle = 0 end @fmod = nil @system = nil end end #============================================================================ # ** Channel #---------------------------------------------------------------------------- # A class that represents an FMOD::Channel #============================================================================ class Channel #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :system # System that created the Sound attr_accessor :sound # Sound using the Channel attr_accessor :fmod # Instance of DLL class (fmodex.dll) attr_accessor :handle # Handle (pointer) to Sound object Channel_Stop='Channel_Stop' Channel_IsPlaying='Channel_IsPlaying' Channel_GetVolume='Channel_GetVolume' Channel_SetVolume='Channel_SetVolume' Channel_GetPan='Channel_GetPan' Channel_SetPan='Channel_SetPan' Channel_GetFrequency='Channel_GetFrequency' Channel_SetFrequency='Channel_SetFrequency' Channel_GetPaused='Channel_GetPaused' Channel_SetPaused='Channel_SetPaused' Channel_GetPosition='Channel_GetPosition' Channel_SetPosition='Channel_SetPosition' #-------------------------------------------------------------------------- # * Object Initialization # theSound : The Sound using this Channel object # handle : Handle to the FMOD::Channel object #-------------------------------------------------------------------------- def initialize(theSound, theHandle) @sound = theSound @system = theSound.system @fmod = theSound.system.fmod @handle = theHandle end #-------------------------------------------------------------------------- # * Stop Channel and Make it Available for Other Sounds #-------------------------------------------------------------------------- def stop @fmod.invoke(Channel_Stop, @handle) end #-------------------------------------------------------------------------- # * Is the Channel Handle Valid? #-------------------------------------------------------------------------- def valid? temp = 0.chr * 4 begin result = @fmod.invoke(Channel_IsPlaying, @handle, temp) rescue if (result == FMOD_ERR_INVALID_HANDLE) return false else raise end end # If we get here then it's valid return true end #-------------------------------------------------------------------------- # * Is the Channel Playing? #-------------------------------------------------------------------------- def playing? temp = 0.chr * 4 @fmod.invoke(Channel_IsPlaying, @handle, temp) return @fmod.unpackBool(temp) end #-------------------------------------------------------------------------- # * Get Channel Volume Level (0.0 -> 1.0) #-------------------------------------------------------------------------- def volume temp = 0.chr * 4 @fmod.invoke(Channel_GetVolume, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Volume Level (0.0 -> 1.0) #-------------------------------------------------------------------------- def volume=(newVolume) @fmod.invoke(Channel_SetVolume, @handle, @fmod.convertFloat(newVolume)) end #-------------------------------------------------------------------------- # * Get Channel Pan Position (-1.0 -> 1.0) #-------------------------------------------------------------------------- def pan temp = 0.chr * 4 @fmod.invoke(Channel_GetPan, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Pan Position (-1.0 -> 1.0) #-------------------------------------------------------------------------- def pan=(newPan) @fmod.invoke(Channel_SetPan, @handle, @fmod.convertFloat(newPan)) end #-------------------------------------------------------------------------- # * Get Channel Frequency in HZ (Speed/Pitch) #-------------------------------------------------------------------------- def frequency temp = 0.chr * 4 @fmod.invoke(Channel_GetFrequency, @handle, temp) return @fmod.unpackFloat(temp) end #-------------------------------------------------------------------------- # * Set Channel Frequency in HZ (Speed/Pitch) #-------------------------------------------------------------------------- def frequency=(newFrequency) @fmod.invoke(Channel_SetFrequency, @handle, @fmod.convertFloat(newFrequency)) end #-------------------------------------------------------------------------- # * Is Channel Paused? #-------------------------------------------------------------------------- def paused temp = 0.chr * 4 @fmod.invoke(Channel_GetPaused, @handle, temp) return @fmod.unpackBool(temp) end #-------------------------------------------------------------------------- # * Pause Channel #-------------------------------------------------------------------------- def paused=(newPaused) @fmod.invoke(Channel_SetPaused, @handle, (newPaused == true) ? 1 : 0) end #-------------------------------------------------------------------------- # * Get Current Playback Position # unit : FMOD_TIMEUNIT to return position in #-------------------------------------------------------------------------- def position(unit = FMOD_DEFAULT_UNIT) temp = 0.chr * 4 @fmod.invoke(Channel_GetPosition, @handle, temp, unit) return @fmod.unpackInt(temp) end #-------------------------------------------------------------------------- # * Set Current Playback Position # newPosition : New playback position # unit : FMOD_TIMEUNIT to use when setting position #-------------------------------------------------------------------------- def position=(newPosition, unit = FMOD_DEFAULT_UNIT) @fmod.invoke(Channel_SetPosition, @handle, newPosition, unit) end #-------------------------------------------------------------------------- # * Dispose of Channel #-------------------------------------------------------------------------- def dispose @handle = 0 @sound = nil @system = nil @fmod = nil end end end #============================================================================== # ** FMod #------------------------------------------------------------------------------ # A higher level module to access FMOD Ex #============================================================================== module Audio SLP_TIME=0.01 #============================================================================ # ** SoundFile #---------------------------------------------------------------------------- # Represents a Sound file (BGM, BGS, SE, etc.) and associated Channel #============================================================================ class SoundFile #-------------------------------------------------------------------------- # * Public Instance Variables #-------------------------------------------------------------------------- attr_accessor :name # File name attr_accessor :sound # FModEx::Sound object attr_accessor :channel # Channel playing sound attr_accessor :volume # Volume in RPG::AudioFile format attr_accessor :pitch # Pitch in RPG::AudioFile format attr_accessor :looping # Sound loops attr_accessor :streaming # Sound is streamed attr_accessor :length # Sound length in milliseconds #-------------------------------------------------------------------------- # * Object Initialization #-------------------------------------------------------------------------- def initialize(name, sound, channel, volume, pitch, looping, streaming, length) @name = name @sound = sound @channel = channel @volume = volume @pitch = pitch @looping = looping @streaming = streaming @length = length end end #-------------------------------------------------------------------------- # * Instance Variables #-------------------------------------------------------------------------- @fmod_dll = FModEx::DLL.new # The FMOD Ex DLL @fmod = FModEx::System.new(@fmod_dll) # The global System object @fmod_se = [] # Array of Sound Effects @rtp_folder = nil # Name of RTP folder #-------------------------------------------------------------------------- # * Get Path of RTP Folder From Registry #-------------------------------------------------------------------------- def self.getRTPFolder if @rtp_folder return @rtp_folder end open_key = Win32API.new('advapi32.dll', 'RegOpenKeyExA', 'LPLLP', 'L') query_value = Win32API.new('advapi32.dll', 'RegQueryValueExA', 'LPLPPP', 'L') close_key = Win32API.new('advapi32', 'RegCloseKey', 'L', 'L') key = 0.chr * 4 # Open a HKEY_LOCAL_MACHINE with KEY_READ attribute and save handle in key open_key.call(0x80000002, 'Software\Enterbrain\RGSS3\RTP', 0, 0x20019, key) key = @fmod_dll.unpackInt(key) type = 0.chr * 4 size = 0.chr * 4 # Query to get string size query_value.call(key, 'RPGVXAce', 0, type, 0, size) data = ' ' * @fmod_dll.unpackInt(size) # Query the string value itself using size query_value.call(key, 'RPGVXAce', 0, type, data, size) @rtp_folder = data.chop close_key.call(key) # Make sure the directory ends with a backslash @rtp_folder += "\\" if @rtp_folder[-1].chr != "\\" return @rtp_folder end #-------------------------------------------------------------------------- # * Return Proper File Name (With Extensions) # name : Name of the file # extensions : Extensions to add to file name #-------------------------------------------------------------------------- def self.checkExtensions(name, extensions) if FileTest.exist?(name) return name end # Add extension if needed extensions.each do |ext| if FileTest.exist?(name + '.' + ext) return name + '.' + ext end end # File doesn't exist return name end #-------------------------------------------------------------------------- # * Get Valid File Name # name : Name of the file #-------------------------------------------------------------------------- def self.selectBGMFilename(name) name = name.gsub("/", "\\") # See if file exists in game folder localname = self.checkExtensions(name, FModEx::FMOD_FILE_TYPES) # See if file exists in RTP commonname = self.checkExtensions(getRTPFolder + name, FModEx::FMOD_FILE_TYPES) if FileTest.exist?(localname) return localname end if FileTest.exist?(commonname) return commonname end # An invalid name was provided return name end #-------------------------------------------------------------------------- # * Play a Sound File Then Return it # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the sound loop? # streaming : Stream sound or load whole thing to memory? #-------------------------------------------------------------------------- def self.play(name, volume, pitch, position, looping, streaming) # Get a valid file name filename = self.selectBGMFilename(name) # Create Sound or Stream and set initial values sound = streaming ? @fmod.createStream(filename) : @fmod.createSound(filename) sound.loopMode = looping ? FModEx::FMOD_LOOP_NORMAL : FModEx::FMOD_LOOP_OFF channel = sound.play volume = volume * 1.0 pitch = pitch * 1.0 file_length = sound.length(FModEx::FMOD_DEFAULT_UNIT) sound_file = SoundFile.new(filename, sound, channel, volume, pitch, looping, streaming, file_length) sound_file.channel.volume = volume / 100.0 sound_file.channel.frequency = sound_file.channel.frequency * pitch / 100 sound_file.channel.position = position return sound_file end #-------------------------------------------------------------------------- # * Stop and Dispose of Sound File #-------------------------------------------------------------------------- def self.stop(sound_file) unless sound_file and sound_file.channel return end # Stop channel, then clear variables and dispose of bgm sound_file.channel.stop sound_file.channel = nil sound_file.sound.dispose end #-------------------------------------------------------------------------- # * Return Length in Milliseconds #-------------------------------------------------------------------------- def self.get_length(sound_file, unit = FModEx::FMOD_DEFAULT_UNIT) return sound_file.length#(unit) end #-------------------------------------------------------------------------- # * Check if Another Sound File is Playing #-------------------------------------------------------------------------- def self.already_playing?(sound_file, name, position = 0) # Get a valid file name filename = self.selectBGMFilename(name) if (sound_file) # If the same sound file is already playing don't play it again if (sound_file.name == filename and position == 0) return true end # If another sound file is playing, stop it if sound_file.channel self.stop(sound_file) end end # No sound file is playing or it was already stopped return false end #-------------------------------------------------------------------------- # * Check if Sound File is Playing #-------------------------------------------------------------------------- def self.playing?(sound_file) unless sound_file and sound_file.channel return false end return sound_file.channel.playing? end #-------------------------------------------------------------------------- # * Get Current Sound File Playing Position #-------------------------------------------------------------------------- def self.get_position(sound_file) unless sound_file and sound_file.channel return 0 end return sound_file.channel.position end #-------------------------------------------------------------------------- # * Seek to a New Sound File Playing Position #-------------------------------------------------------------------------- def self.set_position(sound_file, new_pos) unless sound_file and sound_file.channel return end sound_file.channel.position = new_pos end #-------------------------------------------------------------------------- # * Get Current Sound File Volume #-------------------------------------------------------------------------- def self.get_volume(sound_file) unless sound_file return 0 end return sound_file.volume end #-------------------------------------------------------------------------- # * Set Sound File Volume #-------------------------------------------------------------------------- def self.set_volume(sound_file, volume) unless sound_file and sound_file.channel return end sound_file.volume = volume * 1.0 sound_file.channel.volume = volume / 100.0 end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds (-1 for file end) # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.set_loop_points(sound_file, first, second, unit = FModEx::FMOD_DEFAULT_UNIT) unless sound_file and sound_file.channel return end # If second is -1 then set loop end to the file end if second == -1 second = sound_file.length - 1 end # Set loop points and reflush stream buffer sound_file.channel.sound.setLoopPoints(first, second, unit) sound_file.channel.position = sound_file.channel.position return sound_file end #-------------------------------------------------------------------------- # * Play ME # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGM loop? #-------------------------------------------------------------------------- def self.me_play(name, volume=100, pitch=100, position = 0, looping = false) return if self.already_playing?(@fmod_me, name, position) and Audio.me_playing? # Now play the new BGM as a stream @fmod_me = self.play(name, volume, pitch, position, false, true) Thread.new do if @fmod_bgm paused=@fmod_bgm.channel.paused @fmod_bgm.channel.paused=true else paused=false end loop do unless @fmod_me break end unless Audio.me_playing? break end sleep(SLP_TIME) end if @fmod_bgm @fmod_bgm.channel.paused=paused end end return @fmod_me end #-------------------------------------------------------------------------- # * Stop and Dispose of ME #-------------------------------------------------------------------------- def self.me_stop self.stop(@fmod_me) @fmod_me = nil end #-------------------------------------------------------------------------- # * Return ME Length in Milliseconds #-------------------------------------------------------------------------- def self.me_length(sound_file) self.get_length(@fmod_me) end #-------------------------------------------------------------------------- # * Check if a ME is Playing #-------------------------------------------------------------------------- def self.me_playing? return self.playing?(@fmod_me) end #-------------------------------------------------------------------------- # * Get Current ME Playing Position #-------------------------------------------------------------------------- def self.me_position return self.get_position(@fmod_me) end #-------------------------------------------------------------------------- # * Seek to New ME Playing Position #-------------------------------------------------------------------------- def self.me_position=(new_pos) self.set_position(@fmod_bgm, new_pos) end #-------------------------------------------------------------------------- # * Get Current ME Volume #-------------------------------------------------------------------------- def self.me_volume return self.get_volume(@fmod_me) end #-------------------------------------------------------------------------- # * Set ME Volume #-------------------------------------------------------------------------- def self.me_volume=(volume) self.set_volume(@fmod_me, volume) end #-------------------------------------------------------------------------- # * Set ME fade #-------------------------------------------------------------------------- def self.me_fade(time) return unless @fmod_me and Audio.me_playing? and !@fading_me @fading_me=true Thread.new do vol=Audio.me_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.me_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.me_stop @fading_me=false end end #-------------------------------------------------------------------------- # * Play BGM (or ME) # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGM loop? #-------------------------------------------------------------------------- def self.bgm_play(name, volume=100, pitch=100, position = 0, looping = true) return if self.already_playing?(@fmod_bgm, name, position) # Now play the new BGM as a stream @fmod_bgm = self.play(name, volume, pitch, position, looping, true) if @fmod_me and Audio.me_playing? @fmod_bgm.channel.paused=true end end #-------------------------------------------------------------------------- # * Stop and Dispose of BGM #-------------------------------------------------------------------------- def self.bgm_stop self.stop(@fmod_bgm) @fmod_bgm = nil end #-------------------------------------------------------------------------- # * Return BGM Length in Milliseconds #-------------------------------------------------------------------------- def self.bgm_length(sound_file) self.get_length(@fmod_bgm) end #-------------------------------------------------------------------------- # * Check if a BGM is Playing #-------------------------------------------------------------------------- def self.bgm_playing? return self.playing?(@fmod_bgm) end #-------------------------------------------------------------------------- # * Get Current BGM Playing Position #-------------------------------------------------------------------------- def self.bgm_position return self.get_position(@fmod_bgm) end #-------------------------------------------------------------------------- # * Seek to New BGM Playing Position #-------------------------------------------------------------------------- def self.bgm_position=(new_pos) self.set_position(@fmod_bgm, new_pos) end #-------------------------------------------------------------------------- # * Get Current BGM Volume #-------------------------------------------------------------------------- def self.bgm_volume return self.get_volume(@fmod_bgm) end #-------------------------------------------------------------------------- # * Set BGM Volume #-------------------------------------------------------------------------- def self.bgm_volume=(volume) self.set_volume(@fmod_bgm, volume) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.bgm_set_loop_points(first, second, unit = FModEx::FMOD_DEFAULT_UNIT) @fmod_bgm = self.set_loop_points(@fmod_bgm, first, second, unit) end #-------------------------------------------------------------------------- # * Set BGM fade #-------------------------------------------------------------------------- def self.bgm_fade(time) return unless @fmod_bgm and Audio.bgm_playing? and !@fading_bgm @fading_bgm=true Thread.new do vol=Audio.bgm_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.bgm_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.bgm_stop @fading_bgm=false end end #-------------------------------------------------------------------------- # * Play BGS # name : Name of the file # volume : Channel volume # pitch : Channel frequency # position : Starting position in milliseconds # looping : Does the BGS loop? #-------------------------------------------------------------------------- def self.bgs_play(name, volume=100, pitch=100, position = 0, looping = true) return if self.already_playing?(@fmod_bgs, name, position) # Now play the new BGS as a stream @fmod_bgs = self.play(name, volume, pitch, position, looping, true) end #-------------------------------------------------------------------------- # * Stop and Dispose of BGS #-------------------------------------------------------------------------- def self.bgs_stop self.stop(@fmod_bgs) @fmod_bgs = nil end #-------------------------------------------------------------------------- # * Return BGS Length in Milliseconds #-------------------------------------------------------------------------- def self.bgm_length(sound_file) self.get_length(@fmod_bgs) end #-------------------------------------------------------------------------- # * Check if a BGS is Playing #-------------------------------------------------------------------------- def self.bgs_playing? return self.playing?(@fmod_bgs) end #-------------------------------------------------------------------------- # * Get Current BGS Playing Position #-------------------------------------------------------------------------- def self.bgs_position return self.get_position(@fmod_bgs) end #-------------------------------------------------------------------------- # * Seek to New BGS Playing Position #-------------------------------------------------------------------------- def self.bgs_position=(new_pos) self.set_position(@fmod_bgs, new_pos) end #-------------------------------------------------------------------------- # * Get Current BGS Volume #-------------------------------------------------------------------------- def self.bgs_volume return self.get_volume(@fmod_bgs) end #-------------------------------------------------------------------------- # * Set BGS Volume #-------------------------------------------------------------------------- def self.bgs_volume=(volume) self.set_volume(@fmod_bgs, volume) end #-------------------------------------------------------------------------- # * Set Loop Points # first : Loop start point in milliseconds # second : Loop end point in milliseconds # unit : FMOD_TIMEUNIT for points #-------------------------------------------------------------------------- def self.bgs_set_loop_points(first, second, unit = FModEx::FMOD_DEFAULT_UNIT) @fmod_bgs = self.set_loop_points(@fmod_bgs, first, second, unit) end #-------------------------------------------------------------------------- # * Set BGS fade #-------------------------------------------------------------------------- def self.bgs_fade(time) return unless @fmod_bgs and Audio.bgs_playing? and !@fading_bgs @fading_bgs=true Thread.new do vol=Audio.bgs_volume cnt=(time/1000.0/SLP_TIME).to_i cnt.times do |i| Audio.bgs_volume=(vol-(vol*i/cnt)) sleep SLP_TIME end Audio.me_stop @fading_bgs=false end end #-------------------------------------------------------------------------- # * Play SE # name : Name of the file # volume : Channel volume # pitch : Channel frequency #-------------------------------------------------------------------------- def self.se_play(name, volume=100, pitch=100) if @fmod_se.size > @fmod.maxChannels #msgbox_p 0 se = @fmod_se.shift #msgbox_p se self.stop(se) if self.playing?(se) end # Load SE into memory and play it @fmod_se << self.play(name, volume, pitch, 0, false, false) end #-------------------------------------------------------------------------- # * Stop and Dispose of all SEs #-------------------------------------------------------------------------- def self.se_stop for se in @fmod_se self.stop(se) if self.playing?(se) end @fmod_se.clear end #-------------------------------------------------------------------------- # * Get Rid of Non-Playing SEs #-------------------------------------------------------------------------- def self.se_clean for se in @fmod_se unless self.playing?(se) self.stop(se) @fmod_se.delete(se) end end end #-------------------------------------------------------------------------- # * Check if There's Some SE in SE Array #-------------------------------------------------------------------------- def self.se_list_empty? return @fmod_se.empty? end #-------------------------------------------------------------------------- # * Dispose of Everything #-------------------------------------------------------------------------- def self.dispose self.bgm_stop self.bgs_stop self.se_stop @fmod.dispose end end end module FMod include Audio end module Audio def setup_midi 0 end module_function :setup_midi end |
Citation: |
????? 'FXMOD' ? 123 ??? RuntimeError ???????? LoadLibrary: fmodex.dll |