Skip to main content

FX Chains

This example shows you how to build a simple app that enables you to change recorded audio in interesting ways by utilizing FX chains.

In our Karaoke App example we recorded our voice on top of a base song. In this example we apply FX chains on the recorded voice before mixing it with the base song.

We need to add our FX chains between the recorded voice's AudioPlayerNode and the main MixerNode.

We implement our FX chains as separate audio graphs. These graphs can be run by passing it to a SingleBusAudioGraphProcessorNode. The different effect nodes can be chained inside this subgraph.

Harmonizer

This effect utilizes an autotune node to correct the pitch. The signal is split after the autotune pitch shifted in both directions to emulate harmonizing. It adds some reverb after mixing the pitch shifted outputs with the autotuned signal.

Example implementation:

#pragma once

#include "Switchboard.hpp"
#include "AudioGraph.hpp"
#include "SplitterNode.hpp"
#include "AutotuneNode.hpp"
#include "PitchShiftNode.hpp"
#include "MixerNode.hpp"
#include "ReverbNode.hpp"

using namespace switchboard;

class HarmonizerEffect {
public:
HarmonizerEffect() {
lowPitchShiftNode.setPitchShiftCents(-200);
highPitchShiftNode.setPitchShiftCents(200);

audioGraph.addNode(autotuneNode);
audioGraph.addNode(splitterNode);
audioGraph.addNode(lowPitchShiftNode);
audioGraph.addNode(highPitchShiftNode);
audioGraph.addNode(mixerNode);
audioGraph.addNode(reverbNode);

audioGraph.connect(audioGraph.getInputNode(), autotuneNode);
audioGraph.connect(autotuneNode, splitterNode);
audioGraph.connect(splitterNode, lowPitchShiftNode);
audioGraph.connect(splitterNode, highPitchShiftNode);
audioGraph.connect(lowPitchShiftNode, mixerNode);
audioGraph.connect(splitterNode, mixerNode);
audioGraph.connect(highPitchShiftNode, mixerNode);
audioGraph.connect(mixerNode, reverbNode);
audioGraph.connect(reverbNode, audioGraph.getOutputNode());
}

AudioGraph& getAudioGraph() const {
return audioGraph;
}

private:
AudioGraph audioGraph;

AutotuneNode autotuneNode;
SplitterNode splitterNode;
PitchShiftNode lowPitchShiftNode;
PitchShiftNode highPitchShiftNode;
MixerNode mixerNode;
ReverbNode reverbNode;
};

Radio effect

This effect first bandpass filters the signal to only keep frequencies in the 800-6000 Hz range, discarding bass and treble, then applies some distortion to it. This emulates an analog radio-like sound. At the end some reverb is applied to make it a bit richer.

Example implementation:

#pragma once

#include "Switchboard.hpp"
#include "AudioGraph.hpp"
#include "BandpassFilterNode.hpp"
#include "DistortionNode.hpp"
#include "ReverbNode.hpp"

using namespace switchboard;

class RadioEffect {
public:
RadioEffect() {
bandpassFilterNode.setLowThreshold(800.0f);
highPitchShiftNode.setHighThreshold(6000.0f);

audioGraph.addNode(bandpassFilterNode);
audioGraph.addNode(distortionNode);
audioGraph.addNode(reverbNode);

audioGraph.connect(audioGraph.getInputNode(), bandpassFilterNode);
audioGraph.connect(bandpassFilterNode, distortionNode);
audioGraph.connect(distortionNode, reverbNode);
audioGraph.connect(reverbNode, audioGraph.getOutputNode());
}

AudioGraph& getAudioGraph() const {
return audioGraph;
}

private:
AudioGraph audioGraph;

BandpassFilterNode bandpassFilterNode;
DistortionNode distortionNode;
ReverbNode reverbNode;
};

Using the FX chains

To run our FX chains we utilize a SingleBusAudioGraphProcessorNode in which we pass our FX audio graphs. This node is plugged in between the recorded voice player and the final mixer of our Karaoke App. SingleBusAudioGraphProcessorNode is capable of hot-plugging different audio graphs while running in real time, enabling us to switch between the different FX chains.

Only relevant parts of the implementation are shown, refer to our Karaoke App example for the rest.

#pragma once

#include "Switchboard.hpp"

#include "HarmonizerEffect.hpp"

#include "AudioGraph.hpp"
#include "AudioPlayerNode.hpp"
#include "SingleBusAudioGraphProcessorNode.hpp"
#include "MixerNode.hpp"

using namespace switchboard;

class FXChainsExample {
public:
FXChainsExample() {
fxNode.setAudioGraph(harmonizer.getAudioGraph());

audioGraph.addNode(beatPlayerNode);
audioGraph.addNode(voicePlayerNode);
audioGraph.addNode(fxNode);
audioGraph.addNode(mixerNode);

audioGraph.connect(beatPlayerNode, mixerNode);
audioGraph.connect(voicePlayerNode, fxNode);
audioGraph.connect(fxNode, mixerNode);
audioGraph.connect(mixerNode, audioGraph.getOutputNode());
}

void selectHarmonizer() {
fxNode.setAudioGraph(harmonizer.getAudioGraph());
}

void selectRadioEffect() {
fxNode.setAudioGraph(radio.getAudioGraph());
}

// This audio graph is run by the platform-specific AudioEngine, see the Karaoke App example for more information
AudioGraph& getAudioGraph() const {
return audioGraph;
}

private:
AudioGraph audioGraph;

HarmonizerEffect harmonizer;
RadioEffect radio;

AudioPlayerNode beatPlayerNode;
AudioPlayerNode voicePlayerNode;
SingleBusAudioGraphProcessorNode fxNode;
MixerNode mixerNode;
};