Development guide: Mixing signals

What you'll need

If you followed our Getting started guide, you'll have created a sine tone generator that should be coming out of the speakers. This guide will continue from that point.


Overview

It's likely that you'd want to be able to mix multiple signals together within your Superpowered audio application, so let's see how that works within Superpowered. Like all DSP, there are multiple ways to achieve the same outcome, but here's the way we think you should go about adding together two signals in the most flexible way.

We'll be writing the guides in vanilla Javascript (no framework), which should be the easiest way to understand what is going on.

An important thing to consider where the mixing of the signals will take place. Remember that Superpowered sits on top of the Web Audio API's AudioContext and runs within an instance (or multiple) of AudioWorkletProcessorNodes. The AudioWorklet processor has a single stereo input and a single stereo output. Mixing takes place within the AudioWorklet, not by varying the volume of the AudioWorkletProcessorNode that the processor is hooked into with the Web Audio API. The worklet will handle the generation of the signals and the summing.

We should keep all audio related code and manipulation within Superpowered's control and within it's ecosystem, that way the code is transferable between platforms, provides identical audio across web browsers and has the highest audio processing performance.


Creating another audio signal

To demonstrate the summing of signals, we'll add an additional instance of a Superpowered Generator, but this second one will be set to a different frequency. We'll also need to create an instance of a Superpowered MonoMixer to handle the summing of the two mono generator signals.

Within our existing processor script, first modify the onReady function like this:

...
onReady() {
// Create the Generators and a MonoMixer to sum signals.
this.generator1 = new this.Superpowered.Generator(
this.samplerate,
this.Superpowered.Generator.Sine
);
this.generator2 = new this.Superpowered.Generator(
this.samplerate,
this.Superpowered.Generator.Sine
);
this.mixer = new this.Superpowered.MonoMixer();
// Pre-allocate some buffers for processing inside processAudio.
// Allocating 1024 floats is safe, the buffer size is only 128 in most cases.
this.gen1OutputBuffer = new this.Superpowered.Float32Buffer(1024);
this.gen2OutputBuffer = new this.Superpowered.Float32Buffer(1024);
this.monoMixerOutputBuffer = new this.Superpowered.Float32Buffer(1024);
// Fixed gain values for the mixer channels.
this.mixer.inputGain[0] = 0.5;
this.mixer.inputGain[1] = 0.5;
// Fixed frequencies for the two generators.
this.generator1.frequency = 440;
this.generator2.frequency = 660;
// Notify the main scope that we're prepared.
this.sendMessageToMainScope({ event: "ready" });
}
...

Note the creation of this.gen1OutputBuffer and this.gen2OutputBuffer, these are initially empty buffers which we will use to store the full volume raw output of the generators.

Within our ViewController.m, we'll need to make sure we are importing an additional library file for a new Superpowered class.

#import "ViewController.h"
#include "Superpowered.h"
#include "SuperpoweredSimple.h"
#include "SuperpoweredOSXAudioIO.h"
#include "SuperpoweredGenerator.h"
#include "SuperpoweredMixer.h" // You need this to access the MonoMixer class we'll be using.

Then, modify the viewDidLoad function to the following.

...
- (void)viewDidLoad {
[super viewDidLoad];
Superpowered::Initialize("ExampleLicenseKey-WillExpire-OnNextUpdate");
NSLog(@"Superpowered version: %u", Superpowered::Version());
// Create the Generators and a MonoMixer to sum signals.
generator1 = new Superpowered::Generator(48000, Superpowered::Generator::Sine);
generator2 = new Superpowered::Generator(48000, Superpowered::Generator::Sine);
monoMixer = new Superpowered::MonoMixer();
// Fixed gain values for the mixer channels.
monoMixer->inputGain[0] = 0.5f;
monoMixer->inputGain[1] = 0.5f;
// Fixed frequencies for the two generators.
generator1->frequency = 440;
generator2->frequency = 660;
// Start audio I/O.
audioIO = [[SuperpoweredOSXAudioIO alloc] initWithDelegate:(id<SuperpoweredOSXAudioIODelegate>)self preferredBufferSizeMs:12 numberOfChannels:2 enableInput:true enableOutput:true];
[audioIO start];
}
...

Adding two signals together

Superpowered includes a MonoMixer class which we can use to sum together up to 4 mono signals.

The instance of the MonoMixer was created in the onReady block above. Next we turn our attention to the processAudio() method. Call the generate methods of both Superpowered Generators instances, then sum the two signals together with the Superpowered MonoMixer.

...
processAudio(inputBuffer, outputBuffer, buffersize, parameters) {
// Ensure the samplerate is in sync on every audio processing callback.
this.generator1.samplerate = this.samplerate;
this.generator2.samplerate = this.samplerate;
// Generate the first signal.
this.generator1.generate(
this.gen1OutputBuffer.pointer,
buffersize
);
// Generate the second signal.
this.generator2.generate(
this.gen2OutputBuffer.pointer,
buffersize
);
// Mix the two tones into another buffer.
this.mixer.process(
this.gen1OutputBuffer.pointer, // input 1
this.gen2OutputBuffer.pointer, // input 2
0, // input 3 (empty)
0, // input 4 (empty)
this.monoMixerOutputBuffer.pointer, // output
buffersize
);
// Copy the mono buffer into the interleaved stereo output.
this.Superpowered.Interleave(
this.monoMixerOutputBuffer.pointer, // left side
this.monoMixerOutputBuffer.pointer, // right side
outputBuffer.pointer,
buffersize
);
}
...

Clearing up

Lastly, we must remember to clear up the memory allocations of any class instances we have created by modifying the onDestruct() method of the AudioWorkletProcessor script:

// onDestruct is called when the parent AudioWorkletNode.destruct() method is called.
// You should clear up all Superpowered objects and allocated buffers here.
onDestruct() {
this.generator1.destruct();
this.generator2.destruct();
this.mixer.destruct();
this.gen1OutputBuffer.free();
this.gen2OutputBuffer.free();
this.monoMixerOutputBuffer.free();
}

The instance of the MonoMixer class was created in the viewDidLoad function above so we have it setup ready to use. Now we turn our attention to the audioProcessingCallback, which is where we will add the signals generated by both Superpowered Generators together.

Modify the audioProcessingCallback in the following way. This function is called by the SuperpoweredOSXAudioIO wrapper and it is the audio processing block of the application running on it's own dedicated thread.

...
- (bool)audioProcessingCallback:(float *)inputBuffer outputBuffer:(float *)outputBuffer numberOfFrames:(unsigned int)numberOfFrames samplerate:(unsigned int)samplerate hostTime:(unsigned long long int)hostTime {
// Ensure the samplerate is in sync on every audio processing callback.
generator1->samplerate = samplerate;
generator2->samplerate = samplerate;
// Generate the tones into two buffers.
float gen1OutputBuffer[numberOfFrames];
float gen2OutputBuffer[numberOfFrames];
generator1->generate(gen1OutputBuffer, numberOfFrames);
generator2->generate(gen2OutputBuffer, numberOfFrames);
// Mix the two tones into another buffer.
float monoBuffer[numberOfFrames];
monoMixer->process(
gen1OutputBuffer, // input 1
gen2OutputBuffer, // input 2
NULL, // input 3 (empty)
NULL, // input 4 (empty)
monoBuffer, // output
numberOfFrames
);
// Copy the mono buffer into the interleaved stereo output.
Superpowered::Interleave(
monoBuffer, // left side
monoBuffer, // right side
outputBuffer,
numberOfFrames
);
return true;
}
...

End result

Below is a Javascript sandbox example of the guide above for you to play experiment with. We've added a little UI (buttons, logos etc) to the code but the audio code is as described above. On most devices, a browser's AudioContext will be created in a suspended state and must but resumed with a user interaction, this is why we have included some minimal UI.

You should now hear the two sine tones when you hit the start button.

If you've followed the guide and set things up correctly, you should see something like the following when you build and run:


You can find the example code for this guide and all the others in both JS and native in one repository over at GitHub.

splice/superpowered-guideshttps://github.com/splice/superpowered-guides

Download the code examples for all the Superpowered guides in both Native and JS.


v1.0.6