Mixing with inserts and sends#

Goal: build a console-style mixer — a channel with an insert effect chain, and a shared reverb fed by aux sends.

Channels introduced the channel tree for grouping sounds under shared volume. Channels also carry the two routing primitives every mixer needs:

This tutorial walks through Demo21_Mixer: a music channel running EQ → compressor → chorus, plus a plate reverb both channels send into.

Source: Demo21_Mixer.cpp.

Two source channels#

  musicCh_.create("music", YSE::ChannelMaster());
  drumCh_.create("drums", YSE::ChannelMaster());

Building an insert chain#

Configure the effect modules, chain them with dspObject::link, then attach the head of the chain to the channel. The whole chain runs pre-fader on the summed channel signal:

  // Build the music channel's insert chain: EQ -> compressor -> chorus.
  eq_.gain(YSE::DSP::MODULES::EQ_LOW_SHELF, 4.0f);
  eq_.frequency(YSE::DSP::MODULES::EQ_PEAK_1, 900.f).gain(YSE::DSP::MODULES::EQ_PEAK_1, -3.0f);
  eq_.gain(YSE::DSP::MODULES::EQ_HIGH_SHELF, 3.0f);
  comp_.threshold(-18.f).ratio(4.f).attack(10.f).release(120.f).makeup(4.f);
  chorus_.mode(YSE::DSP::MODULES::MODE_CHORUS).rate(0.6f).depth(0.4f).impact(0.4f);
  eq_.link(comp_);
  comp_.link(chorus_);
  musicCh_.setDSP(&eq_);

Each module is an ordinary YSE::DSP::dspObject, so it has the inherited bypass / impact (wet/dry) controls on top of its own parameters. The mix-grade modules — parametric EQ, compressor, chorus — are documented on the Mixing effects page.

A shared reverb via send / return#

Create a return bus with makeReturn, attach an effect to it, then feed it from any channel with send(slot, returnBus, level). Here both the music and drum channels send into one plate reverb, post-fader:

  // A shared plate-reverb return bus fed post-fader from both channels.
  reverbReturn_.makeReturn("reverb");
  plate_.decay(0.6f).damping(6000.f).preDelay(20.f).impact(1.0f);
  reverbReturn_.setDSP(&plate_);
  musicCh_.send(0, reverbReturn_, sendLevel_);
  drumCh_.send(0, reverbReturn_, sendLevel_);

A return bus is just a channel that has been flagged with makeReturn — it keeps its own inserts and can even send onward to other returns (the graph must stay acyclic).

Driving it live#

Toggling an insert chain and riding the send levels is glitch-free — send levels are ramped internally, so they are safe to set every control tick:

void DemoMixer::ToggleInserts() {
  insertsOn_ = !insertsOn_;
  musicCh_.setDSP(insertsOn_ ? &eq_ : nullptr);
}

void DemoMixer::SendUp() {
  sendLevel_ = sendLevel_ + 0.1f > 1.0f ? 1.0f : sendLevel_ + 0.1f;
  musicCh_.setSendLevel(0, sendLevel_);
  drumCh_.setSendLevel(0, sendLevel_);
}

void DemoMixer::SendDown() {
  sendLevel_ = sendLevel_ - 0.1f < 0.0f ? 0.0f : sendLevel_ - 0.1f;
  musicCh_.setSendLevel(0, sendLevel_);
  drumCh_.setSendLevel(0, sendLevel_);
}

Passing nullptr to setDSP detaches the insert chain; the engine takes no ownership of the modules, so they must outlive the channel.

Tearing down routing#

Because the audio thread walks the insert chain and sends every block, tear the routing down — clearSend and setDSP(nullptr)before the channels and effect objects destruct:

  // Tear down the routing before the channels/effects destruct so the audio
  // thread stops touching the insert chain and sends first.
  musicCh_.clearSend(0);
  drumCh_.clearSend(0);
  musicCh_.setDSP(nullptr);
  reverbReturn_.setDSP(nullptr);
  for (int i = 0; i < 5; i++) {
    YSE::System().update();
    YSE::System().sleep(20);
  }

What you learned#

  • channel::setDSP(head) installs a pre-fader insert chain; chain modules with dspObject::link.

  • channel::makeReturn turns a channel into a return bus; channel::send / setSendLevel feed it from other channels.

  • The engine never owns insert or return effects — they must outlive the channel, and routing should be torn down before they destruct.

Next#