Splitting Audio Programatically

What is the right way to create an FMOD low-level channel group hierarchy where a signal is split, goes down two separate signal paths, and is then mixed together? Ideally we’d create a single channelGroup and then add it to TWO other channelGroups but it appears as though channelGroups are hierarchical and cannot be added to more than one channelGroup.

Our audio sources are programmatically created, so we are looking for the FMOD-low-level version of an audio channel that is sent to multiple mix buses.

ChannelGroups are too “high level” for this task, you’ll need to drop down to the DSP level to create a second signal path. As you’ve discovered a ChannelGroup can only have one output, but you can use the DSP API to add a second one.

Assuming you already have two target ChannelGroups T1 and T2 and a single source ChannelGroup S1, here is what you need to do:

  • Route S1 into T1 with T1->addGroup(S1)
  • Get the tail DSP of T2 with T2->getDSP(FMOD_CHANNELCONTROL_DSP_TAIL, &D_T2).
  • Get the head DSP of S1 with S1->getDSP(FMOD_CHANNELCONTROL_DSP_HEAD, &D_S1).
  • Now connect the head of S1 to the tail of T2 with D_T2->addInput(D_S1).

This has now created the split you’re after, use the ChannelGroups as you like, when you want to mix it all back together just put T1 and T2 into the same parent ChannelGroup. Let me know if you have any troubles or something doesn’t make sense.

2 Likes

Thank you! That’s exactly what we were experimenting with but I got Head/Tail backwards so it wasn’t working right. So it looks like your sources are “TAILS” and your outputs are “HEADS”?

Am I correct in assuming that we should be using FMOD_DSPCONNECTION_TYPE_SEND as the input type for the addInput dsp connection from S1 to T2?

What boolean should we use for the parent_clock argument when adding S1 to T1 using addGroup?

The last piece of background information is that we’re using the setMix the DSP connections from S1->T1 and S1->T2 in order to control the flow as necessary. If we want to mimic ‘severing’ a connection to T2 for instance, we just setMix(0.0). In addition to that, we also need to control the volume levels independently. For that we’re just called setVolume on T1 and T2 directly.

This all works properly except if we set the volume to 0.0 on T1, it looks like it’s stopping S1 completely which makes T2 go silent as well. Is there any decent work-around for that? We have a few ‘bad’ options such as limiting the minimum volume on T1 to 0.00001, or using the setMix to control the volume, or looking for the DSP fader on T1 and setting the volume directly. Any thoughts?

This would make a great section in our new docs spoiler alert we’re rebooting our API docs. Mainly because some diagrams would help immensely. The signal flows from tail to head, each Channel and ChannelGroup can have its own chain of DSPs, the signal will originate in the tail or leaf nodes of the graph and flow out the head until it reaches the head of the master ChannelGroup.

The answer for whether you should use a “send” connection or a “standard” connection depends on the desired behavior, both are legal. The graph is logically traversed from the head of the master ChannelGroup down to the leaf sources, “standard” connections when traversed cause DSP nodes to be executed, “send” connections copy signal produced by the “standard” connection.

For your case it sounds like both branches of the signal are equally important so both should be “standard” connections. Having both as standard connections should solve your “going silent” issue, because both signal paths need to be considered executing.

1 Like

Hi Mathew, that all makes sense though making S1->T1 and S1->T2 both “STANDARD” connections doesn’t seem to solve our issue. The S1->T2 path still goes silent if I set the volume of T1 to 0.0. I’ve tried toying with the propagatedspclock on addGroup and that didn’t seem to help either. I’m assuming T1’s deciding its chain can be stopped because it’s essentially muted and that’s shutting S1’s channel group down.

I just mocked this up and I can see what you are describing, the issue is vol0 virtual behavior. So when the audibility of the Channel reaches zero it becomes virtualized, this is calculated based on the ChannelGroup hierarchy and does not follow our new DSP connection. I can see three possible workarounds:

  1. Adjust your code to conflate the independent volume of the ChannelGroups with the respective input connections, so don’t use ChannelGroup::setVolume, just use the input connections.
  2. To keep the mix and level controls separate instead of using ChannelGroup::setVolume for your independent volume control, use the output DSPConnections of each ChannelGroup (much like you’re doing with the input connections).
  3. Instead of creating two ChannelGroups, create three. Set the volume on T1 (the ChannelGroup connected to S1) to 1.0 (this controls audibility). Set the volume on T2 and T3 for independent volume control. Set the mix of S1->T1 to 0, and use S1->T2 and S1->T3 for your mix controls.

I have plans to correct this behavior in a future version but it’s a significant change that I don’t have a release date for just yet. I would recommend one of the above workarounds.

Also, in case you aren’t aware, the FMOD Profiler that ships with the API can display the DSP graph and helps visualize the connections, which can really help with this kind of work.

That makes sense! I also somehow never knew that the FMOD LowLevel Profiler existed. I appreciate the help!