feat: Sidechain ducking
Description
When playing sound effects and music at the same time, sidechain ducking is a useful feature.
Basically, here's how this feature works. Let's take a video game with background music and SFX as an example:
- We put all SFX into a single audio bus ("Mixing Bus" feature of Soloud, see here: https://solhsa.com/soloud/concepts.html)
- We monitor the volume on that bus
- We use the SFX volume as the input of a compressor on the music volume. In other words, when the volume of all the sound effects (combined) crosses some threshold, we lower the music volume. The music volume thus "ducks" down whenever the sfx volume is loud.
This lets the music be loud when the SFX is quiet while also making sure that sound effects can be heard without distortion when they are loud.
Sidechain / ducking is also popular for mixing voice. The voice provides the sidechain, and music or sound effects "duck". Think of a radio DJ lowering the volume of a track to talk over it, except this is automatic and usually much faster.
Requirements
- [ ] Voices can be put into Mixing Buses
- [ ] The compressor filter (or its derivative) can perform its work based on audio output of a mixing bus (through a side chain) instead of the output of its own channel
- [ ] Make sure this is performant enough (we probably don't want this feature if it means some kind of drastic slow down)
- [ ] Some kind of test that this works
Additional Context
- FMOD: https://www.fmod.com/docs/2.02/studio/effect-reference.html#compressor
- Ableton live: https://www.ableton.com/en/blog/elphnt-introduction-sidechaining-live/ has a nice video that explains sidechaining quite well
Hi @filiph and @NTayloe (from #363), this is a feature I wishes to add since the beginning and never tought that this plugin would have gone so far.
SoLoud's mixing buses have their own logic, similar to that of the "normal" player.
I still need to figure out how this is working because for example the normal play() accept, aside the other parameters, also an int aBus (look for "Soloud.play()") that probably add that . The bus also has a play method and I didn't understand yet if there could be more than one bus.
Probably a start could be to add a separate class to be used as like SoLoud.instance.bus on Dart side or maybe manage the bus inside the AudioSource. But this is a very first look and I'll accept any suggestions.
FWIW, I think a Bus should be a class. Here's a sketch of the API (just spitballing):
final soloud = SoLoud.instance;
await soloud.initialize();
final sfxBus = soloud.createBus();
sfxBus.setVolume(0.5);
sfxBus.filter.compressor.activate();
await sfxBus.play(someAudioSource);
// ...
sfxBus.dispose();
I'm not sure we need SoLoud.instance.bus but maybe I'm just not thinking it through.
It would make sense to be able to call .createBus() on a bus, which is how you could build a tree structure of busses feeding into the final mixing bus. But this is, imho, low priority. Most projects don't need a hierarchy — they need a few high-level buses such as SFX, music, (ambient, voice, ...).
Ideally, in terms of sidechaining, we'd then have something like:
final musicBus = soloud.createBus();
musicBus.filter.compressor.activate();
musicBus.filter.compressor.setInput(sfxBus);
musicBus.play(...);