I need a granular synthesizer for vehicle engine audio, but so far have been given the runaround by literally anyone and everyone with a solution for this that works with FMOD (AudioMotors 2 is defunct, and REV hasn’t responded to any of my queries. Other community members with similar solutions have ghosted me as well), so I’m locked into figuring this out myself. Unfortunately for me, a DSP plugin for FMOD Studio seems out of reach as I do not know C++ at all.
I’ve been trying to hack something together using programmer instruments and audio tables, but haven’t been able to produce seamless grain playback.
I’ve taken a look at the example granular_synth.cpp project, and tried to replicate it using FMOD Studio API calls in Unity C# using a programmer instrument, but Studio seems to lack any way to call setDelay on an event so that things are queued up correctly. (You can technically get the channel to call set delay on, but it produces the ERR_STUDIO_NOT_LOADED error until the next FMOD update as I understand it, which is too late)
I’ve also been reading that you shouldn’t mix Studio and Core api calls together, so I’m kind of at a loss here on what to do.
I think I read that it is possible to build DSP plugins for Studio and whatnot using C# and not just C++ but there is literally no documentation on how this would work as far as actually setting up and building a project goes.
Any help would be appreciated!
We have an example of how to set up what is effectively your own granular synthesizer for engine audio in the Vehicles/Car Engine event in the Examples project that ships with FMOD Studio.
This solution doesn’t rely on code, and is a common solution for engine sound design.
If you prefer writing your own granular synthesizer, it is certainly possible to create a DSP in C# and we have an example of how to implement a DSP here Scripting Examples | DSP Capture.
In this case, the “plugin” isn’t a dynamic library as it would be with REV or AudioMotors, it is just C# code that implements an FMOD.DSP_DESCRIPTION
.
Most of the fields are optional, so to create a minimal DSP you just need to implement numinputbuffers
, numoutputbuffers
, and a read
callback. Here is a simple DSP written in C# that just generates white noise:
[AOT.MonoPInvokeCallback(typeof(FMOD.DSP_READ_CALLBACK))]
static FMOD.RESULT ReadCallback(ref FMOD.DSP_STATE dsp_state, IntPtr inbuffer, IntPtr outbuffer, uint length, int inchannels, ref int outchannels)
{
System.Random r = new System.Random();
int len = (int)length * inchannels;
float[] buffer = new float[len];
for (int s = 0; s < len; s++)
{
buffer[s] = 0.1f * (float)r.NextDouble();
}
Marshal.Copy(buffer, 0, outbuffer, len);
return FMOD.RESULT.OK;
}
void Start()
{
// Assign the callback to a member variable to avoid garbage collection
mReadCallback = ReadCallback;
FMOD.DSP_DESCRIPTION desc = new FMOD.DSP_DESCRIPTION();
desc.numinputbuffers = 2;
desc.numoutputbuffers = 2;
desc.read = mReadCallback;
FMODUnity.RuntimeManager.CoreSystem.getMasterChannelGroup(out FMOD.ChannelGroup masterCG);
FMODUnity.RuntimeManager.CoreSystem.createDSP(ref desc, out mCaptureDSP);
masterCG.addDSP(0, mDSP);
}
void OnDestroy()
{
FMODUnity.RuntimeManager.CoreSystem.getMasterChannelGroup(out FMOD.ChannelGroup masterCG);
masterCG.removeDSP(mDSP);
mCaptureDSP.release();
}
As for an actual granular synthesis implementation, getting something better than REV or AudioMotors is a pretty ambitious goal, but I would do something like:
- Decode an audio file into memory
- Pick a couple of random positions in the file to playback from
- Copy data from each of those positions for some random number of samples
- Fade in and out each of the “grains” created above to prevent clicks
- Repeat from step 2 every couple of Read callback invocations, creating new grains as time goes on
Hi Jeff. First of all, thank you so much for taking the time to write this all out for me.
So your first example is what I’ve been doing so far, but it relies on a set of really good engine loops at various RPMs, which are very difficult to find and also tend to lack the “bite” of an engine under load, which is what I’m looking for.
As for REV and AudioMotors, I’m not necessarily looking to better them, just making something using the underlying tech to get me started since it’s been so difficult to get hold of them. I recently got an email back from the FMOD sales team though saying AudioMotors ownership has been transferred to FMOD and will be continued this way, so that bodes well for maybe just using that though.
As for writing a DSP in C#, this is incredibly helpful. Though maybe I’m just not versed enough and need to read through the documentation more, but this would just play the audio directly “to speaker” so-to-speak right? Can I still spatialize and apply an effect chain to this like through an event?
Thank you
Yeah this DSP won’t be spatialized. You have a few options for spatializing the output of a DSP, the simplest would be to attach the DSP to a 3D sound.
You will need to update the position of the Channel
object (essentially an instance of a Sound
object) as well with Channel.set3DAttributes
.
private FMOD.Sound mSound;
private FMOD.Channel mChannel;
private FMOD.CREATESOUNDEXINFO mExinfo = new FMOD.CREATESOUNDEXINFO();
void Start()
{
mReadCallback = ReadCallback;
FMOD.DSP_DESCRIPTION desc = new FMOD.DSP_DESCRIPTION();
desc.numinputbuffers = 2;
desc.numoutputbuffers = 2;
desc.read = mReadCallback;
// Exinfo required for playing a custom "user" sound
mExinfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
mExinfo.numchannels = 2;
mExinfo.defaultfrequency = 48000;
mExinfo.length = 96000;
mExinfo.format = FMOD.SOUND_FORMAT.PCMFLOAT;
FMODUnity.RuntimeManager.CoreSystem.getMasterChannelGroup(out FMOD.ChannelGroup masterCG);
FMODUnity.RuntimeManager.CoreSystem.createSound("", FMOD.MODE._3D | FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER, ref mExinfo, out mSound);
FMODUnity.RuntimeManager.CoreSystem.createDSP(ref desc, out mCaptureDSP);
FMODUnity.RuntimeManager.CoreSystem.playSound(mSound, masterCG, false, out mChannel);
// Add the DSP to the sound's FMOD.Channel instead of the master channel group
mChannel.addDSP(FMOD.CHANNELCONTROL_DSP_INDEX.TAIL, mDSP);
}
void OnDestroy()
{
mChannel.removeDSP(mDSP);
}
void Update()
{
var attr = FMODUnity.RuntimeUtils.To3DAttributes(gameObject);
mChannel.set3DAttributes(ref attr.position, ref attr.velocity);
}
If you wanted to apply an effect chain from FMOD Studio you could pass this custom sound through a programmer sound callback, which would also allow you to handle spatialization through the event.
Oh I can use this with a programmer sound callback? I had read that it wasn’t a good idea to mix Studio and Core level callbacks. I can try giving it a shot though.