Karaoke App
This example demonstrates how to build a simple karaoke app from Switchboard SDK components.
We need to create an AudioPlayerNode
which plays the base song and a RecorderNode
which records our voice. When we are done with the recording we can mix together the base song and the recording using a MixerNode
, and we can also apply a wide range of effects, like Autotune, Echo, etc. Refer to our FX Chains example to see an example implementation of audio effects usage building on top of this example. To check the list of available effects please visit out our extensions page.
We can render the result of the AudioGraph
into an audio file, which will contain the base song mixed with our recording.
To play this newly created audio file, create a new AudioGraph
with an AudioPlayerNode
, and pass it to the AudioEngine
start
method.
We can visualize the audio graph to get a grasp on how the nodes are connected. Let's take a look at the AudioGraph
which does the recording while playing the base song:
The graph that does the mixing of the recording with the base song looks as follows:
The graph that plays the base song mixed with the recorded voice is a pretty simple one, since it contains just an AudioPlayerNode
so we don't need to visualize that.
- Swift
- Kotlin
import SwitchboardSDK
class KaraokeExample {
let audioEngine = SBAudioEngine()
let recorderNode: SBRecorderNode
let baseSongAudioPlayerNode = SBAudioPlayerNode()
let mixedSongAudioPlayerNode = SBAudioPlayerNode()
let audioGraph = SBAudioGraph()
let mixedSongAudioGraph = SBAudioGraph()
let recordedFileName: String = "recording"
let mixedSongFilePath: String = "mixed_song_path"
let currentFormat: SBCodec = .wav
init() {
recorderNode = SBRecorderNode(
name: "TestRecorderNode",
recordingSampleRate: 48000,
numberOfRecordedChannels: 2
)
audioGraph.addNode(recorderNode)
audioGraph.addNode(baseSongAudioPlayerNode)
audioGraph.connect(audioGraph.inputNode, to: recorderNode)
audioGraph.connect(baseSongAudioPlayerNode, to: audioGraph.outputNode)
audioEngine.microphoneEnabled = true
audioEngine.start(audioGraph)
}
func loadBaseSong(path: String, codec: SBCodec) {
baseSongAudioPlayerNode.load(path, withFormat: codec)
}
func playBaseSongAndRecord() {
baseSongAudioPlayerNode.play()
recorderNode.start()
}
func stopBaseSongAndRecording() {
recorderNode.stop(recordedFileName, withFormat: currentFormat)
baseSongAudioPlayerNode.stop()
}
// This call could be time consuming, should not be called on the UI thread.
func mixBaseSongWithRecording(outputFilePath: String) {
let mixAudioGraph = SBAudioGraph()
let recordedVoicePlayer = SBAudioPlayerNode()
let mixerNode = SBMixerNode()
let autotuneNode = SBAutotuneNode()
autotuneNode.scale = SBAutotuneNode.TunerScale.CMAJOR
autotuneNode.range = SBAutotuneNode.TunerRange.ALTO
autotuneNode.speed = SBAutotuneNode.TunerSpeed.EXTREME
autotuneNode.frequencyOfA = 440
autotuneNode.isEnabled = true
let echoNode = SBEchoNode()
echoNode.isEnabled = true
recordedVoicePlayer.load(recorderNode.getFilePath(), withFormat: currentFormat)
mixAudioGraph.addNode(recordedVoicePlayer)
mixAudioGraph.addNode(autotuneNode)
mixAudioGraph.addNode(echoNode)
mixAudioGraph.addNode(baseSongAudioPlayerNode)
mixAudioGraph.addNode(mixerNode)
mixAudioGraph.connect(recordedVoicePlayer, to: autotuneNode)
mixAudioGraph.connect(autotuneNode, to: echoNode)
mixAudioGraph.connect(echoNode, to: mixerNode)
mixAudioGraph.connect(baseSongAudioPlayerNode, to: mixerNode)
mixAudioGraph.connect(mixerNode, to: audioGraph.outputNode)
recordedVoicePlayer.play()
baseSongAudioPlayerNode.play()
mixAudioGraph.renderToFile(mixedSongFilePath)
mixedSongAudioPlayerNode.load(outputFilePath, withFormat: currentFormat)
}
func initMixedSongGraph() {
mixedSongAudioGraph.addNode(mixedSongAudioPlayerNode)
mixedSongAudioGraph.connect(mixedSongAudioPlayerNode, to: mixedSongAudioGraph.outputNode)
audioEngine.start(mixedSongAudioGraph)
mixedSongAudioPlayerNode.load(mixedSongFilePath, withFormat: currentFormat)
}
func playMixedSong() {
mixedSongAudioPlayerNode.play()
}
func pauseMixedSong() {
mixedSongAudioPlayerNode.pause()
}
func stopMixedSong() {
mixedSongAudioPlayerNode.stop()
}
func close() {
audioEngine.close()
audioGraph.close()
recorderNode.close()
baseSongAudioPlayerNode.close()
}
}
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.AudioPlayerNode
import com.synervoz.switchboard.sdk.audiographnodes.MixerNode
import com.synervoz.switchboard.sdk.audiographnodes.RecorderNode
class KaraokeExample {
val audioEngine = AudioEngine(enableInput = true)
val recorderNode = RecorderNode()
val baseSongAudioPlayerNode = AudioPlayerNode()
val mixedSongAudioPlayerNode = AudioPlayerNode()
val audioGraph = AudioGraph()
val mixedSongAudioGraph = AudioGraph()
val mixedSongFilePath: String = "mixed_song_path"
let recordedFileName: String = "recording"
var currentFormat: Codec = Codec.WAV
init {
audioGraph.addNode(recorderNode)
audioGraph.addNode(baseSongAudioPlayerNode)
audioGraph.connect(audioGraph.inputNode, recorderNode)
audioGraph.connect(baseSongAudioPlayerNode, audioGraph.outputNode)
audioEngine.start(audioGraph)
}
fun loadBaseSong(path: String, codec: Codec) {
baseSongAudioPlayerNode.load(path, codec)
}
fun playBaseSongAndRecord() {
baseSongAudioPlayerNode.play()
recorderNode.start()
}
fun stopBaseSongAndRecording() {
recorderNode.stop(recordedFileName, currentFormat)
baseSongAudioPlayerNode.stop()
}
// This call could be time consuming, should not be called on the UI thread.
fun mixBaseSongWithRecording(outputFilePath: String) {
val mixAudioGraph = AudioGraph()
val recordedVoicePlayer = AudioPlayerNode()
val mixerNode = MixerNode()
val autotuneNode = AutotuneNode()
autotuneNode.scale = AutotuneNode.TunerScale.CMAJOR
autotuneNode.range = AutotuneNode.TunerRange.ALTO
autotuneNode.speed = AutotuneNode.TunerSpeed.EXTREME
autotuneNode.frequencyOfA = 440f
autotuneNode.isEnabled = true
val echoNode = EchoNode()
echoNode.isEnabled = true
recordedVoicePlayer.load(recorderNode.getFilePath(), currentFormat)
mixAudioGraph.addNode(recordedVoicePlayer)
mixAudioGraph.addNode(autotuneNode)
mixAudioGraph.addNode(echoNode)
mixAudioGraph.addNode(baseSongAudioPlayerNode)
mixAudioGraph.addNode(mixerNode)
mixAudioGraph.connect(recordedVoicePlayer, autotuneNode)
mixAudioGraph.connect(autotuneNode, echoNode)
mixAudioGraph.connect(echoNode, mixerNode)
mixAudioGraph.connect(baseSongAudioPlayerNode, mixerNode)
mixAudioGraph.connect(mixerNode, audioGraph.outputNode)
mixAudioGraph.renderToFile(mixedSongFilePath)
recordedVoicePlayer.play()
baseSongAudioPlayerNode.play()
initMixedSongGraph()
}
fun initMixedSongGraph() {
mixedSongAudioGraph.addNode(mixedSongAudioPlayerNode)
mixedSongAudioGraph.connect(mixedSongAudioPlayerNode, mixedSongAudioGraph.outputNode)
audioEngine.start(mixedSongAudioGraph)
mixedSongAudioPlayerNode.load(mixedSongFilePath, currentFormat)
}
fun playMixedSong() {
mixedSongAudioPlayerNode.play()
}
fun pauseMixedSong() {
mixedSongAudioPlayerNode.pause()
}
fun stopMixedSong() {
mixedSongAudioPlayerNode.stop()
}
fun close() {
audioEngine.close()
audioGraph.close()
recorderNode.close()
baseSongAudioPlayerNode.close()
}
}