Multitrack Recorder App
About Multitrack Recorder App
Multitrack recording is a technique used in audio production that allows for the separate recording and mixing of individual audio tracks. In multitrack recording, each instrument or vocal track is recorded onto a separate track, which can then be mixed together with other tracks to create a final mix.
In this example we will create the skeleton of a simple studio recorder app which will record the dry (clean), the wet (processed) and the monitor mix of the incoming audio. You can check out the available effects on the extensions page.
Visualizing the audio graph first makes the implementation super easy.
note
This example uses the Superpowered Extension.
Why use the Superpowered Extension instead of Superpowered directly?
Code Example
- Swift
- Kotlin
import SwitchboardSDK
import SwitchboardSuperpowered
class MultitrackRecorderExample {
private let audioGraph = SBAudioGraph()
private let audioEngine = SBAudioEngine()
private let micInput = SBAudioSourceNode()
private let micDrySplitter = SBBusSplitterNode()
private let micDryRecorder = SBRecorderNode()
private let micCompressor = SBCompressorNode()
private let micReverb = SBReverbNode()
private let micWetSplitter = SBBusSplitterNode()
private let micWetRecorder = SBRecorderNode()
private let micGain = SBGainNode()
private let guitarInput = SBAudioSourceNode()
private let guitarDrySplitter = SBBusSplitterNode()
private let guitarDryRecorder = SBRecorderNode()
private let guitarFlanger = SBFlangerNode()
private let guitarWetSplitter = SBBusSplitterNode()
private let guitarWetRecorder = SBRecorderNode()
private let guitarGain = SBGainNode()
private let drumsInput = SBAudioSourceNode()
private let drumsDrySplitter = SBBusSplitterNode()
private let drumsDryRecorder = SBRecorderNode()
private let drumsBitcrusher = SBBitcrusherNode()
private let drumsWetSplitter = SBBusSplitterNode()
private let drumsWetRecorder = SBRecorderNode()
private let drumsGain = SBGainNode()
private let monitorMixer = SBMixerNode()
private let monitorSplitter = SBBusSplitterNode()
private let monitorRecorder = SBRecorderNode()
private let format: SBCodec = .wav
init() {
audioGraph.addNode(micInput)
audioGraph.addNode(micDrySplitter)
audioGraph.addNode(micDryRecorder)
audioGraph.addNode(micCompressor)
audioGraph.addNode(micReverb)
audioGraph.addNode(micWetSplitter)
audioGraph.addNode(micWetRecorder)
audioGraph.addNode(micGain)
audioGraph.addNode(guitarInput)
audioGraph.addNode(guitarDrySplitter)
audioGraph.addNode(guitarDryRecorder)
audioGraph.addNode(guitarFlanger)
audioGraph.addNode(guitarWetSplitter)
audioGraph.addNode(guitarWetRecorder)
audioGraph.addNode(guitarGain)
audioGraph.addNode(drumsInput)
audioGraph.addNode(drumsDrySplitter)
audioGraph.addNode(drumsDryRecorder)
audioGraph.addNode(drumsBitcrusher)
audioGraph.addNode(drumsWetSplitter)
audioGraph.addNode(drumsWetRecorder)
audioGraph.addNode(drumsGain)
audioGraph.addNode(monitorMixer)
audioGraph.addNode(monitorSplitter)
audioGraph.addNode(monitorRecorder)
audioGraph.connect(micInput, to: micDrySplitter)
audioGraph.connect(micDrySplitter, to: micDryRecorder)
audioGraph.connect(micDrySplitter, to: micCompressor)
audioGraph.connect(micCompressor, to: micReverb)
audioGraph.connect(micReverb, to: micWetSplitter)
audioGraph.connect(micWetSplitter, to: micWetRecorder)
audioGraph.connect(micWetSplitter, to: micGain)
audioGraph.connect(guitarInput, to: guitarDrySplitter)
audioGraph.connect(guitarDrySplitter, to: guitarDryRecorder)
audioGraph.connect(guitarDrySplitter, to: guitarFlanger)
audioGraph.connect(guitarFlanger, to: guitarWetSplitter)
audioGraph.connect(guitarWetSplitter, to: guitarWetRecorder)
audioGraph.connect(guitarWetSplitter, to: guitarGain)
audioGraph.connect(drumsInput, to: drumsDrySplitter)
audioGraph.connect(drumsDrySplitter, to: drumsDryRecorder)
audioGraph.connect(drumsDrySplitter, to: drumsBitcrusher)
audioGraph.connect(drumsBitcrusher, to: drumsWetSplitter)
audioGraph.connect(drumsWetSplitter, to: drumsWetRecorder)
audioGraph.connect(drumsWetSplitter, to: drumsGain)
audioGraph.connect(micGain, to: monitorMixer)
audioGraph.connect(guitarGain, to: monitorMixer)
audioGraph.connect(drumsGain, to: monitorMixer)
audioGraph.connect(monitorMixer, to: monitorSplitter)
audioGraph.connect(monitorSplitter, to: monitorRecorder)
audioGraph.connect(monitorSplitter, to: audioGraph.outputNode)
micCompressor.isEnabled = true
micReverb.isEnabled = true
guitarFlanger.isEnabled = true
drumsBitcrusher.isEnabled = true
audioEngine.start(audioGraph)
}
func startRecording() {
micDryRecorder.start()
micWetRecorder.start()
guitarDryRecorder.start()
guitarWetRecorder.start()
drumsDryRecorder.start()
drumsWetRecorder.start()
monitorRecorder.start()
}
func stopRecording(filePath: String) {
micDryRecorder.stop(filePath + "microphone_dry" + format.fileExtension,
withFormat: format)
micWetRecorder.stop(filePath + "microphone_wet" + format.fileExtension,
withFormat: format)
guitarDryRecorder.stop(filePath + "guitar_dry" + format.fileExtension,
withFormat: format)
guitarWetRecorder.stop(filePath + "guitar_wet" + format.fileExtension,
withFormat: format)
drumsDryRecorder.stop(filePath + "drums_dry" + format.fileExtension,
withFormat: format)
drumsWetRecorder.stop(filePath + "drums_wet" + format.fileExtension,
withFormat: format)
monitorRecorder.stop(filePath + "monitor_mix" + format.fileExtension,
withFormat: format)
}
}
import com.synervoz.switchboard.sdk.Codec
import com.synervoz.switchboard.sdk.audioengine.AudioEngine
import com.synervoz.switchboard.sdk.audiograph.AudioGraph
import com.synervoz.switchboard.sdk.audiographnodes.*
class MultitrackRecorderExample(context: Context) {
private val audioGraph = AudioGraph()
private val audioEngine = AudioEngine(context)
private val micInput = AudioSourceNode()
private val micDrySplitter = BusSplitterNode()
private val micDryRecorder = RecorderNode()
private val micCompressor = CompressorNode()
private val micReverb = ReverbNode()
private val micWetSplitter = BusSplitterNode()
private val micWetRecorder = RecorderNode()
private val micGain = GainNode()
private val guitarInput = AudioSourceNode()
private val guitarDrySplitter = BusSplitterNode()
private val guitarDryRecorder = RecorderNode()
private val guitarFlanger = FlangerNode()
private val guitarWetSplitter = BusSplitterNode()
private val guitarWetRecorder = RecorderNode()
private val guitarGain = GainNode()
private val drumsInput = AudioSourceNode()
private val drumsDrySplitter = BusSplitterNode()
private val drumsDryRecorder = RecorderNode()
private val drumsBitcrusher = BitcrusherNode()
private val drumsWetSplitter = BusSplitterNode()
private val drumsWetRecorder = RecorderNode()
private val drumsGain = GainNode()
private val monitorMixer = MixerNode()
private val monitorSplitter = BusSplitterNode()
private val monitorRecorder = RecorderNode()
private val format = Codec.WAV
init() {
audioGraph.addNode(micInput)
audioGraph.addNode(micDrySplitter)
audioGraph.addNode(micDryRecorder)
audioGraph.addNode(micCompressor)
audioGraph.addNode(micReverb)
audioGraph.addNode(micWetSplitter)
audioGraph.addNode(micWetRecorder)
audioGraph.addNode(micGain)
audioGraph.addNode(guitarInput)
audioGraph.addNode(guitarDrySplitter)
audioGraph.addNode(guitarDryRecorder)
audioGraph.addNode(guitarFlanger)
audioGraph.addNode(guitarWetSplitter)
audioGraph.addNode(guitarWetRecorder)
audioGraph.addNode(guitarGain)
audioGraph.addNode(drumsInput)
audioGraph.addNode(drumsDrySplitter)
audioGraph.addNode(drumsDryRecorder)
audioGraph.addNode(drumsBitcrusher)
audioGraph.addNode(drumsWetSplitter)
audioGraph.addNode(drumsWetRecorder)
audioGraph.addNode(drumsGain)
audioGraph.addNode(monitorMixer)
audioGraph.addNode(monitorSplitter)
audioGraph.addNode(monitorRecorder)
audioGraph.connect(micInput, micDrySplitter)
audioGraph.connect(micDrySplitter, micDryRecorder)
audioGraph.connect(micDrySplitter, micCompressor)
audioGraph.connect(micCompressor, micReverb)
audioGraph.connect(micReverb, micWetSplitter)
audioGraph.connect(micWetSplitter, micWetRecorder)
audioGraph.connect(micWetSplitter, micGain)
audioGraph.connect(guitarInput, guitarDrySplitter)
audioGraph.connect(guitarDrySplitter, guitarDryRecorder)
audioGraph.connect(guitarDrySplitter, guitarFlanger)
audioGraph.connect(guitarFlanger, guitarWetSplitter)
audioGraph.connect(guitarWetSplitter, guitarWetRecorder)
audioGraph.connect(guitarWetSplitter, guitarGain)
audioGraph.connect(drumsInput, drumsDrySplitter)
audioGraph.connect(drumsDrySplitter, drumsDryRecorder)
audioGraph.connect(drumsDrySplitter, drumsBitcrusher)
audioGraph.connect(drumsBitcrusher, drumsWetSplitter)
audioGraph.connect(drumsWetSplitter, drumsWetRecorder)
audioGraph.connect(drumsWetSplitter, drumsGain)
audioGraph.connect(micGain, monitorMixer)
audioGraph.connect(guitarGain, monitorMixer)
audioGraph.connect(drumsGain, monitorMixer)
audioGraph.connect(monitorMixer, monitorSplitter)
audioGraph.connect(monitorSplitter, monitorRecorder)
audioGraph.connect(monitorSplitter, audioGraph.outputNode)
micCompressor.isEnabled = true
micReverb.isEnabled = true
guitarFlanger.isEnabled = true
drumsBitcrusher.isEnabled = true
audioEngine.start(audioGraph)
}
fun startRecording() {
micDryRecorder.start()
micWetRecorder.start()
guitarDryRecorder.start()
guitarWetRecorder.start()
drumsDryRecorder.start()
drumsWetRecorder.start()
monitorRecorder.start()
}
fun stopRecording(filePath: String) {
micDryRecorder.stop(filePath + "microphone_dry" + format.fileExtension, format)
micWetRecorder.stop(filePath + "microphone_wet" + format.fileExtension, format)
guitarDryRecorder.stop(filePath + "guitar_dry" + format.fileExtension, format)
guitarWetRecorder.stop(filePath + "guitar_wet" + format.fileExtension, format)
drumsDryRecorder.stop(filePath + "drums_dry" + format.fileExtension, format)
drumsWetRecorder.stop(filePath + "drums_wet" + format.fileExtension, format)
monitorRecorder.stop(filePath + "monitor_mix" + format.fileExtension, format)
}
}