About "Stereo" and "Mono" Audio Modes - clevelandmusicco/HothouseExamples GitHub Wiki

Overview

The differences between mono-to-mono, mono-to-dual-mono, stereo-to-stereo, and so on, can be confusing. To make it easier to understand, some simple definitions and code examples are presented on this page.

Definitions

Let's start by explaining each audio mode that stereo devices like the Hothouse are capable of.

The Workhorses

  • Mono-to-Mono: A single-channel input signal is processed and output as a single-channel signal, maintaining its mono format throughout. The other channel is not used. This is the typical mode for a mono guitar effect pedal.
  • Mono-to-Dual-Mono: A single-channel mono input is (usually) processed, then duplicated and sent to both left and right output channels, each carrying the same signal.
  • Mono-to-Stereo: A single-channel mono input is processed and split into two output channels (left and right), creating a stereo effect from the mono source. For example, a mono keyboard signal processed through a stereo reverb effect.
  • Stereo-to-Stereo: A two-channel stereo input signal is processed and output as two stereo channels, preserving the left and right channel information throughout.

The Outliers

  • Stereo-to-Mono: A two-channel stereo input signal is summed together and output as one mono channel.
  • Dual-Mono: Two independent mono signals are processed separately on two channels, where left and right channels each carry distinct mono signals. For example, applying the same (Or different!) processing to two distinct mono guitar parts.

It's probably fair to call dual-mono the least common mode in our case, given the Hothouse's pedal form factor. (It's not a piece of rack gear with two separate compressor channels, for example. Of course, you can program and use it this way if you like!)

[!NOTE] You may occasionally see common audio processing modes presented as acronyms, like this:

  • MIMO – Mono In, Mono Out
  • MISO – Mono In, Stereo Out
  • SISO – Stereo In, Stereo Out
  • SIMO – Stereo In, Mono Out

I'm not aware of an acronym for mono-to-dual-mono (which is clearly different than true mono-to-stereo). So, being pedantic, I prefer the foo-to-bar format. :shrug:

Examples

For consistency, all of the examples will use daisysp::ReverbSc because it has stereo inputs and outputs. The mono-to-mono example is the only complete code example; those that follow only present the code in the AudioCallback that makes them different.

Mono-to-mono

Since the vast majority of guitar amplifier setups are mono, this is the typical mode for mono effects you find on a guitar pedal board. There are no controls for reverb decay, level, etc. in this example; we're just focused on how the audio channels and code interact.

#include "daisysp.h"
#include "hothouse.h"

using namespace clevelandmusicco;
using namespace daisy;
using namespace daisysp;

Hothouse hw;
ReverbSc reverb;

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
  for (size_t i = 0; i < size; ++i) {
    float dry_sig = in[0][i];  // Use left input channel (mono source)
    float wet_l, wet_r;

    // Process the dry signal into stereo (wet_l and wet_r)
    reverb.Process(dry_sig, dry_sig, &wet_l, &wet_r);

    out[0][i] = dry_sig + 0.5f * (wet_l + wet_r);  // Sum processed left and right
    out[1][i] = 0.0f;                              // Mute the unused channel
  }
}

int main() {
  hw.Init();
  hw.SetAudioBlockSize(48);
  hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);

  reverb.Init(hw.AudioSampleRate());
  reverb.SetFeedback(0.7f); // Sensible default

  hw.StartAdc();
  hw.StartAudio(AudioCallback);

  while (true) {
  }
}

[!IMPORTANT] By convention,in[0] is wired to the tip of the Hothouse's TRS stereo input jack and is considered the left channel. in[1] is wired to the ring and considered the right channel. So, when you plug a mono (TS) cable in, your code accesses the signal at in[0]. The ring of the jack is shorted to ground when using a TS cable, so there will be no audio at in[1].

The same is true for the TRS stereo output jack: out[0] is the tip, T, or left, and out[1] is the ring, R, or right. The ring is shorted to ground when a TS cable is inserted.

[!TIP] Even if you are writing code solely for mono processing on the left channel (for example, a mono guitar reverb pedal), it's best practice to mute (write zeroes to) the unused right output channel. This ensures that if someone ever does use a stereo output cable, there won't be any unintended noise on the right channel.

Mono-to-dual-mono

This may not be the most sensible example, but it is accurate; for mono-to-dual-mono, we copy the left output from the previous example to the right output. Just the affected AudioCallback code:

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
  for (size_t i = 0; i < size; ++i) {
    float dry_sig = in[0][i];  // Use left input channel (mono source)
    float wet_l, wet_r;

    // Process the dry signal into stereo (wet_l and wet_r)
    reverb.Process(dry_sig, dry_sig, &wet_l, &wet_r);

    // Silly example; you likely wouldn't do this if you have stereo outputs:
    // Sum processed left and right and assign to both output channels
    out[0][i] = out[1][i] = dry_sig + 0.5f * (wet_l + wet_r);
  }
}

Another (more practical example) is the default bypass behavior from most HothouseExamples effects:

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
  for (size_t i = 0; i < size; ++i) {
    // Stuff omitted...

    if (bypass) {
      // Copy left input to both outputs (mono-to-dual-mono)
      out[0][i] = out[1][i] = in[0][i];
    } else {
      // TODO: replace silence with something awesome
      out[0][i] = out[1][i] = 0.0f;
    }
  }
}

Mono-to-stereo

This is likely one of the most common modes you will work with. Again, the relevant AudioCallback code:

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        float dry_sig = in[0][i];  // Use left input channel (mono source)
        float wet_l, wet_r;
        
        // Process the dry signal into stereo (wet_l and wet_r)
        reverb.Process(dry_sig, dry_sig, &wet_l, &wet_r);

        // Output the processed signal in stereo
        out[0][i] = dry_sig + wet_l;  // Left output channel (dry + wet signal)
        out[1][i] = dry_sig + wet_r;  // Right output channel (dry + wet signal)
    }
}

[!NOTE] :neckbeard: Technically, the above example combines both mono-to-dual-mono mode (the mono input signal is copied to both left and right outputs), AND mono-to-stereo mode (the mono input is processed and output in stereo). However, in practical application, your code might implement a true mono-to-stereo "wet only" effect by omitting the dry signal from the output. Or better yet: a dry/wet mix control...

Stereo-to-stereo

At this point, we barely need any comments. The relevant AudioCallback code:

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        float dry_l = in[0][i];
        float dry_r = in[1][i];
        float wet_l, wet_r;
        
        // Process the left and right signals separately
        reverb.Process(dry_l, dry_r, &wet_l, &wet_r);

        // Output processed signals to stereo
        out[0][i] = dry_left + wet_l;
        out[1][i] = dry_right + wet_r;
    }
}

Switching between modes dynamically

In the real world, you might not always know beforehand how your code will be used, or maybe you just want the flexibility to handle most typical situations. Here's a simple way to use TOGGLESWITCH_1 to set the mode of the processing:

  • When the switch is UP, the processing is mono-to-mono
  • When the switch is in the MIDDLE, we're in mono-to-stereo mode
  • And DOWN gives us true stereo-to-stereo
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
    hw.ProcessAllControls();

    Hothouse::ToggleswitchPosition sw1_pos = hw.GetToggleswitchPosition(Hothouse::TOGGLESWITCH_1);

    for (size_t i = 0; i < size; ++i) {
        float dry_l = in[0][i];  // Left input (mono for some modes)
        float dry_r = (sw1_pos == Hothouse::TOGGLESWITCH_DOWN) ? in[1][i] : dry_l;

        float wet_l, wet_r;
        switch (sw1_pos) {
            case Hothouse::TOGGLESWITCH_UP:
            default:
                // Mono-to-mono
                reverb.Process(dry_l, dry_l, &wet_l, &wet_r);
                out[0][i] = dry_l + 0.5f * (wet_l + wet_r);
                out[1][i] = 0.0f;
                break;

            case Hothouse::TOGGLESWITCH_MIDDLE:
                // Mono-to-stereo
                reverb.Process(dry_l, dry_l, &wet_l, &wet_r);
                out[0][i] = dry_l + wet_l;
                out[1][i] = dry_l + wet_r;
                break;

            case Hothouse::TOGGLESWITCH_DOWN:
                // Stereo-to-stereo
                reverb.Process(dry_l, dry_r, &wet_l, &wet_r);
                out[0][i] = dry_l + wet_l;
                out[1][i] = dry_r + wet_r;
                break;
        }
    }
}

Or, if you don't mind sacrificing readability for concision, here's a compact version (where it's a little tricky to follow the branching logic):

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
    hw.ProcessAllControls();

    Hothouse::ToggleswitchPosition sw1_pos = hw.GetToggleswitchPosition(Hothouse::TOGGLESWITCH_1);

    // sw1_pos is handled like this:
    //   TOGGLESWITCH_UP: mono-to-mono
    //   TOGGLESWITCH_MIDDLE: mono-to-stereo
    //   TOGGLESWITCH_DOWN: stereo-to-stereo

    for (size_t i = 0; i < size; ++i) {
        float dry_l = in[0][i];
        float dry_r = (sw1_pos == Hothouse::TOGGLESWITCH_DOWN) ? in[1][i] : dry_l;

        float wet_l, wet_r;
        reverb.Process(dry_l, (sw1_pos == Hothouse::TOGGLESWITCH_DOWN) ? dry_r : dry_l, &wet_l, &wet_r);

        out[0][i] = dry_l + wet_l;  
        out[1][i] = (sw1_pos == Hothouse::TOGGLESWITCH_UP) ? 0.0f : dry_r + wet_r;
    }
}