Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Help request - Extending FMOD Ex capabilities in RGSS.

Hiya all,

I'm using the awesome binding script Janus wrote to use FMOD Ex within RGSS. He wrote it only to allow FMOD's playback options (file formats) to be used for your game bgm's (not BGS, ME, or SE yet). Well, I'm looking to expand on this.

Seeing as I had to learn programming from scratch, things are going decently well, or were until I hit a snag. When you create an FMOD object, it assigns them a "handle". My problem lies with when I need to interact with FMOD. When I try to pass this handle back to FMOD to find an FMOD object I created, it cannot find it.

Here is the full code, written by Janus, with my additions:
Code:
# FMod Ex
# binding by Kevin Gadd (janus@luminance.org)
module FModEx

 FMOD_INIT_NORMAL = 0

 FMOD_OK = 0
 FMOD_ERR_FILE_NOT_FOUND = 23
 FMOD_ERR_INVALID_HANDLE = 36
 FMOD_ERR_DSP_NOTFOUND = 16

 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
 FMOD_DSP_TYPE_REVERB = 13

 FMOD_CHANNEL_FREE = -1
 FMOD_CHANNEL_REUSE = -2

 FMOD_FILE_TYPES = ["ogg", "aac", "wma", "mp3", "wav", "it", "xm", "mod", "s3m", "mid", "midi"]

 # class that manages importing functions from the DLL
 class DLL
   attr_accessor :filename
   attr_accessor :functions

   def initialize(filename = "fmodex.dll")
     @filename = filename # store DLL filename for instance
     @functions = {} # empty hashtable
     @handle = 0

     # load library
     w32_LL = Win32API.new("kernel32.dll", "LoadLibrary", 'p', 'l')
     @handle = w32_LL.call(filename)

     self.import("System_Create", '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')
     self.import("System_CreateDSPByType", 'llp')
     
     self.import("DSP_GetType", 'lp')

     self.import("Sound_Release", 'l')
     self.import("Sound_GetMode", 'lp')
     self.import("Sound_SetMode", 'll')

     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_AddDSP", 'lp')
   end

   def import(name, args = '', returnType = 'l')
     @functions[name] = Win32API.new(@filename, "FMOD_" + name, args, returnType)
   end

   def [](key)
     return @functions[key]
   end

   def invoke(name, *args)
     fn = @functions[name]
     raise "function not imported: #{name}" if fn.nil?
     result = fn.call(*args)
     if result == FMOD_OK
       # raise "success"
     else
       # raise "FMod Ex returned error #{result}"
       # error
     end
     return result
   end

   def convertFloat(f)
     temp = [f].pack('f')
     return unpackInt(temp)
   end

   def unpackInt(s)
     return s.unpack('l')[0]
   end

   def unpackFloat(s)
     return s.unpack('f')[0]
   end

   def unpackBool(s)
     return s.unpack('l')[0] == 0 ? false : true
   end

   def dispose
     @handle
   end
 end

 # class that manages an instance of FMOD::System
 class System
   attr_accessor :fmod
   attr_accessor :handle

   def initialize(theDLL, maxChannels = 32, flags = FMOD_INIT_NORMAL, extraDriverData = 0)
     @fmod = theDLL
     temp = 0.chr * 4
     @fmod.invoke("System_Create", temp)
     @handle = @fmod.unpackInt(temp)
     @fmod.invoke("System_Init", @handle, maxChannels, flags, extraDriverData)
   end

   def createSound(filename, mode = FMOD_DEFAULT)
     temp = 0.chr * 4
     result = @fmod.invoke("System_CreateSound", @handle, filename, mode, 0, temp)
     raise "File not found: #{filename}!" if result == FMOD_ERR_FILE_NOT_FOUND
     newSound = Sound.new(self, @fmod.unpackInt(temp))
     return newSound
   end

   def createStream(filename, mode = FMOD_DEFAULT)
     temp = 0.chr * 4
     result = @fmod.invoke("System_CreateStream", @handle, filename, mode, 0, temp)
     #print "File not found: #{filename}!" if result == FMOD_ERR_FILE_NOT_FOUND
     newSound = Sound.new(self, @fmod.unpackInt(temp))
     return newSound
   end
   
   def createDSPByType(type = FMOD_DSP_TYPE_REVERB)
     temp = 0.chr * 4
     result = @fmod.invoke("System_CreateDSPByType", @handle, type, 01.to_s)
     print result
     newDSP = DSP.new(self, @fmod.unpackInt(temp))
     return newDSP
   end

   def dispose
     if (@handle > 0)
       @fmod.invoke("System_Close", @handle)
       @fmod.invoke("System_Release", @handle)
       @handle = 0
     end
     @fmod = nil
   end
 end

 # class that manages an instance of FMOD::DSP
 class DSP
   attr_accessor :system
   attr_accessor :handle
   attr_accessor :fmod
   
   def initialize(theSystem, theHandle)
     @system = theSystem
     @fmod = theSystem.fmod
     @handle = theHandle
   end
   
   def getType
     temp = 0.chr * 4
     result = @fmod.invoke("DSP_GetType", @handle, temp)
   end
   
 end
      
 # class that manages an instance of FMOD::Sound
 class Sound
   attr_accessor :system
   attr_accessor :fmod
   attr_accessor :handle

   def initialize(theSystem, theHandle)
     @system = theSystem
     @fmod = theSystem.fmod
     @handle = theHandle
   end

   def play(paused = false, channel = nil)
     if (channel == nil)
       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)
     newChannel = Channel.new(self, theChannel)
     return newChannel
   end

   def mode
     temp = 0.chr * 4
     @fmod.invoke("Sound_GetMode", @handle, temp)
     return @fmod.unpackInt(temp)
   end

   def mode=(newMode)
     @fmod.invoke("Sound_SetMode", @handle, newMode)
   end

   def loopMode
     temp = 0.chr * 4
     @fmod.invoke("Sound_GetMode", @handle, temp)
     return @fmod.unpackInt(temp) & FMOD_LOOP_BITMASK
   end

   def loopMode=(newMode)
     @fmod.invoke("Sound_SetMode", @handle, (self.mode() & ~FMOD_LOOP_BITMASK) | newMode)
   end

   def dispose
     if (@handle > 0)
       @fmod.invoke("Sound_Release", @handle)
       @handle = 0
     end
     @fmod = nil
     @system = nil
   end
 end

 # class that represents an FMOD::Channel
 class Channel
   attr_accessor :system
   attr_accessor :sound
   attr_accessor :fmod
   attr_accessor :handle

   def initialize(theSound, theHandle)
     @sound = theSound
     @system = theSound.system
     @fmod = theSound.system.fmod
     @handle = theHandle
   end

   def stop
     @fmod.invoke("Channel_Stop", @handle)
   end

   def valid?
     temp = 0.chr * 4
     result = @fmod.invoke("Channel_IsPlaying", @handle, temp)
     return false if (result == FMOD_ERR_INVALID_HANDLE)
     return true
   end

   def playing?
     temp = 0.chr * 4
     @fmod.invoke("Channel_IsPlaying", @handle, temp)
     return @fmod.unpackBool(temp)
   end

   def volume
     temp = 0.chr * 4
     @fmod.invoke("Channel_GetVolume", @handle, temp)
     return @fmod.unpackFloat(temp)
   end

   def volume=(newVolume)
     @fmod.invoke("Channel_SetVolume", @handle, @fmod.convertFloat(newVolume))
   end

   def pan
     temp = 0.chr * 4
     @fmod.invoke("Channel_GetPan", @handle, temp)
     return @fmod.unpackFloat(temp)
   end

   def pan=(newPan)
     @fmod.invoke("Channel_SetPan", @handle, @fmod.convertFloat(newPan))
   end

   def frequency
     temp = 0.chr * 4
     @fmod.invoke("Channel_GetFrequency", @handle, temp)
     return @fmod.unpackFloat(temp)
   end

   def frequency=(newFrequency)
     @fmod.invoke("Channel_SetFrequency", @handle, @fmod.convertFloat(newFrequency))
   end

   def paused
     temp = 0.chr * 4
     @fmod.invoke("Channel_GetPaused", @handle, temp)
     return @fmod.unpackBool(temp)
   end

   def paused=(newPaused)
     @fmod.invoke("Channel_SetPaused", @handle, (newPaused == true) ? 1 : 0)
   end
   
   def addDSP(dspobject)
     temp = [dspobject].pack('l')
     result = @fmod.invoke("Channel_AddDSP", @handle, temp)
     print "boo" if result == FMOD_ERR_DSP_NOTFOUND 
   end
   
   def dispose
     @handle = 0
     @sound = nil
     @system = nil
     @fmod = nil
   end
 end
end

module Win32
 CSIDL_PROGRAM_FILES_COMMON = 43
end

$fmod_dll = FModEx::DLL.new
$fmod = FModEx::System.new($fmod_dll)

def getRGSSFolder()
 getFL = Win32API.new("shell32.dll", "SHGetSpecialFolderLocation", 'llp', 'l')
 temp = 0.chr * 4
 getFL.call(0, Win32::CSIDL_PROGRAM_FILES_COMMON, temp)
 pidl = temp.unpack('l')[0]
 getFP = Win32API.new("shell32.dll", "SHGetPathFromIDList", 'lp', 'l')
 buf = 32.chr * 260
 result = getFP.call(pidl, buf)
 if (result > 0)
   buf = buf.slice(0,buf.index(0.chr))
   return buf + "\\Enterbrain\\RGSS\\Standard\\"
 end
end

def checkExtensions(name, extensions)
 if FileTest.exist?(name)
   return name
 end
 extensions.each do |ext|
   if FileTest.exist?(name + "." + ext)
     return name + "." + ext
   end
 end
 return name
end

def selectBGMFilename(name)
 name = name.gsub("/", "\\")
 localname = checkExtensions(name, FModEx::FMOD_FILE_TYPES)
 commonname = checkExtensions(getRGSSFolder() + name, FModEx::FMOD_FILE_TYPES)
 if FileTest.exist?(localname)
   return localname
 end
 if FileTest.exist?(commonname)
   return commonname
 end
 return name
end

def FMod_bgm_play(name, volume, pitch)
 filename = selectBGMFilename(name)
 if ($fmod_bgm_name == filename)
   return
 end
 FMod_bgm_stop()
 $fmod_bgm_name = filename
 # load the sound as a stream
 $fmod_bgm = $fmod.createStream(filename)
 $fmod_bgm.loopMode = FModEx::FMOD_LOOP_NORMAL
 $fmod_bgm_reverb = $fmod.createDSPByType()
 $fmod_bgm_channel = $fmod_bgm.play(false, 100)
 # apply volume and pitch
 $fmod_bgm_channel.volume = volume / 100
 $fmod_bgm_channel.frequency = $fmod_bgm_channel.frequency * pitch / 100
 $fmod_bgm_channel.addDSP($fmod_bgm_reverb.handle)
 $fmod_bgm_reverb.getType
end

def FMod_bgm_stop()
 if $fmod_bgm_channel == nil
   return
 end
 $fmod_bgm_channel.stop()
 $fmod_bgm_channel = nil
 # clean up the sound
 $fmod_bgm.dispose()
 $fmod_bgm = nil
 $fmod_bgm_name = nil
end

def FMod_bgm_fade(time)
 # no easy way to make this work, so we just stop it
 FMod_bgm_stop()
end

The parts that I added to the above code are the following: DSP class and all its functions, createDSPByType in the System class, and addDSP in the Channel class.

With Janus's original code, he implemented a way to store a handle for the System, Sound, and Channel objects. Here's an example from the createStream function:
Code:
temp = 0.chr * 4 #to create a blank pointer with null chars
result = @fmod.invoke("System_CreateStream", @handle, filename, mode, 0, temp) # temp is now passed into FMOD, where FMOD fills temp with a pointer to the created object
The variable 'temp' now contains FMOD's native pointer to the object. He also created a method in which to extract the contents of the variable into a fixnum, so that it can be used within RGSS. He assigns each object this handle, but I'm not sure that it's ever used.

My problem is that I *do* need to use this handle. Once I've created a DSP object via createDSPByType function in the system class, I need to use the addDSP function in the channel class to attach the DSP object to that channel, thus filtering the noise.

I've determined that I am successful with creating the DSP object, as I've gotten a result of FMOD_OK (FMOD's return on a function that succeeds) for the createDSPByType. When I tried to pass the handle that is generated for this DSP object into the addDSP function I call for the playing channel, I get a return code from FMOD, FMOD_ERR_DSP_NOTFOUND. I have tried passing this handle in a few ways; I've tried using Janus's way, which is to use the 'temp' variable after FMOD has filled it with a pointer, "unpacking" that into an integer, then re-packing it when the addDSP function is called. I've also tried skipping the packing/unpacking process, setting a global variable as soon as 'temp' is popped out with FMOD's object handle, then plugging that same variable right back in without modifying it.

Either way, I can't help but feel it is a translation issue between RGSS and FMOD. FMOD has objects created, but I can't reference them. Anyone have any ideas?

If I can overcome that obstacle, I don't foresee anything in my way to extending functionality into RGSS for all of FMOD's DSP effects, not to mention setting up BGS, ME, and SE to all function through FMOD as well. Thanks for looking!
 
Well, I used Janus's script before so I have some idea about what you're trying to do, but I don't know much about DSP and thus I might not be able to help much.

The first thing I thought of was
result = @fmod.invoke("System_CreateDSPByType", @handle, type, 01.to_s)

According to the CreateDSPByType function prototype, the last parameter is where the DSP handle is returned, but in this case you pass it a temporary string, with no way of retrieving that back. Why not use @fmod.invoke("System_CreateDSPByType", @handle, type, temp), that's what Janus does anyway. If this is the problem (somehow I doubt it), it'd explain why you're getting a success error value but not getting the right DSP handle, as you're just not storing the returned DSP anywhere.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top