Skip to main content

Karaoke App

About Karaoke App

Karaoke apps rank among the most popular audio-related applications within the mobile app marketplace. Who doesn't like a good karaoke party?

The idea behind these apps is straightforward, yet their development can become highly complex without a fundamental understanding of audio programming. For example making sure that the backing track is in sync with the recording is not straightforward task.

Fortunately, the process of creating a karaoke app is significantly simplified when using the Switchboard SDK. This example will illustrate how you can easily construct one by essentially connecting various audio nodes.

Karaoke App

GitHub

You can find the source code on the following link:

Karaoke App - iOS

GitHub

You can find the source code on the following link:

Karaoke App - Android

Perfect Sync: Offset Below 10 ms

In karaoke-like apps, where there is a backing track and a vocal or voice recording over it, it's crucial that the vocals are synchronized with the music in the final recording to ensure a pleasant user experience. Fortunately, with the Switchboard SDK, the offset is reduced almost to zero, keeping the backing track perfectly in sync with the vocals.

Features:

The app has the following features:

  • Vocal recording over a backing track
  • Ability to use a volume mixer after recording
  • Ability to apply different effects after recording
  • Sharing of the completed recording

It consists of the following screens:

  • Song List: Listen to the available songs, and choose one
  • Sing: Play the selected song and record your voice over it
  • Mixing: Apply effects on your voice and use the volume mixer to achieve perfect balance between your voice and the song. Export and share the rendered file.

You can find more info about the screens below.

Song List Screen

The Song List screen contains a list of available songs.

You can play and pause and different songs, and press the "Sing" button when you have selected your favorite.



Audio Graph

The audio graph for the Song List screen contains a player node that is routed to the speaker output:

Code Example

import SwitchboardSDK

class SongListAudioSystem: AudioSystem {
let audioPlayerNode = SBAudioPlayerNode()

override init() {
super.init()
audioGraph.addNode(audioPlayerNode)
audioGraph.connect(audioPlayerNode, to: audioGraph.outputNode)
}

func play() {
audioPlayerNode.play()
}

func pause() {
audioPlayerNode.pause()
}

func loadSong(songURL: String) {
audioPlayerNode.load(songURL)
}
}

Sing Screen

The Sing screen consists of a progress bar for the backing track, a start / finish recording button and a lyrics view.

When you are ready to sing, press the Start button. This will start the playback of the backing track, and your vocal input is recorded. Press finish when you want to stop singing. This will bring you to the Mixer screen.

On this screen please use wired headphones for the best experience!



Audio Graph

To make sure that the recording is in sync with the audio playback we use a SubgraphProcessorNode. This ensures that the RecorderNode and the AudioPlayerNode for the backing track is started at the same time.

Code Example

import SwitchboardSDK
import SwitchboardSuperpowered

class SingAudioSystem: AudioSystem {
let internalAudioGraph = SBAudioGraph()
let subgraphNode = SBSubgraphProcessorNode()
let audioPlayerNode = SBAudioPlayerNode()
let recorderNode = SBRecorderNode()
let splitterNode = SBBusSplitterNode()
let multiChannelToMonoNode = SBMultiChannelToMonoNode()
let vuMeterNode = SBVUMeterNode()

override init() {
super.init()

vuMeterNode.smoothingDurationMs = 100.0
internalAudioGraph.addNode(audioPlayerNode)
internalAudioGraph.addNode(recorderNode)
internalAudioGraph.addNode(splitterNode)
internalAudioGraph.addNode(multiChannelToMonoNode)
internalAudioGraph.addNode(vuMeterNode)
internalAudioGraph.connect(internalAudioGraph.inputNode, to: splitterNode)
internalAudioGraph.connect(splitterNode, to: recorderNode)
internalAudioGraph.connect(splitterNode, to: multiChannelToMonoNode)
internalAudioGraph.connect(multiChannelToMonoNode, to: vuMeterNode)
internalAudioGraph.connect(audioPlayerNode, to: internalAudioGraph.outputNode)
subgraphNode.audioGraph = internalAudioGraph

audioGraph.addNode(subgraphNode)
audioGraph.connect(audioGraph.inputNode, to: subgraphNode)
audioGraph.connect(subgraphNode, to: audioGraph.outputNode)

audioEngine.microphoneEnabled = true
}

override func start() {
internalAudioGraph.start()
super.start()
}

func playAndRecord() {
audioPlayerNode.play()
recorderNode.start()
}

func loadSong(songURL: String) {
audioPlayerNode.load(songURL)
}

func getSongDurationInSeconds() -> Double {
return audioPlayerNode.duration()
}

func getPositionInSeconds() -> Double {
return audioPlayerNode.position
}

func getProgress() -> Float {
return Float(audioPlayerNode.position / audioPlayerNode.duration())
}

func isPlaying() -> Bool {
return audioPlayerNode.isPlaying
}

func finish() {
super.stop()
internalAudioGraph.stop()
audioPlayerNode.stop()
recorderNode.stop(Config.recordingFilePath, withFormat: Config.fileFormat)
}
}

Mixer Screen

The Mixer screen consists of a seek bar for the player which allows you to seek forward and backward in the mix of your vocal input and the backing track.

It also has volume sliders for the vocals and backing track to make you able to mix volumes to your liking.

You can also enable different effects on the vocals by using the effect switches.

When you are done with the mixing and editing you can save and share your recording using your favorite app by tapping the Export button.



Audio Graph

The same audio graph will be used with the Offline Graph Renderer to render the final mix to an output file which can be shared.

import SwitchboardSDK
import SwitchboardSuperpowered

class MixerAudioSystem: AudioSystem {
let musicPlayer = SBAudioPlayerNode()
let voicePlayer = SBAudioPlayerNode()
let mixerNode = SBMixerNode()
let offlineGraphRenderer = SBOfflineGraphRenderer()
let musicGainNode = SBGainNode()
let voiceGainNode = SBGainNode()
let reverbNode = SBReverbNode()
let compressorNode = SBCompressorNode()
let avpcNode = SBAutomaticVocalPitchCorrectionNode()

override init() {
super.init()

reverbNode.isEnabled = false
compressorNode.isEnabled = false
avpcNode.isEnabled = false

audioGraph.addNode(musicPlayer)
audioGraph.addNode(voicePlayer)
audioGraph.addNode(mixerNode)
audioGraph.addNode(musicGainNode)
audioGraph.addNode(voiceGainNode)
audioGraph.addNode(reverbNode)
audioGraph.addNode(compressorNode)
audioGraph.addNode(avpcNode)
audioGraph.connect(musicPlayer, to: musicGainNode)
audioGraph.connect(musicGainNode, to: mixerNode)
audioGraph.connect(voicePlayer, to: voiceGainNode)
audioGraph.connect(voiceGainNode, to: avpcNode)
audioGraph.connect(avpcNode, to: compressorNode)
audioGraph.connect(compressorNode, to: reverbNode)
audioGraph.connect(reverbNode, to: mixerNode)
audioGraph.connect(mixerNode, to: audioGraph.outputNode)

audioEngine.microphoneEnabled = false
}

func isPlaying() -> Bool {
return musicPlayer.isPlaying
}

func renderMix() -> String {
let sampleRate = max(musicPlayer.sourceSampleRate, voicePlayer.sourceSampleRate)
musicPlayer.position = 0.0
voicePlayer.position = 0.0
musicPlayer.play()
voicePlayer.play()
offlineGraphRenderer.sampleRate = sampleRate
offlineGraphRenderer.maxNumberOfSecondsToRender = musicPlayer.duration()
offlineGraphRenderer.processGraph(audioGraph, withOutputFile: Config.mixedFilePath, withOutputFileCodec: Config.fileFormat)

return Config.mixedFilePath
}

func play() {
musicPlayer.play()
voicePlayer.play()
}

func pause() {
musicPlayer.pause()
voicePlayer.pause()
}

func loadSong(songURL: String) {
musicPlayer.load(songURL)
}

func loadRecording(recordingPath: String) {
voicePlayer.load(recordingPath, withFormat: Config.fileFormat)
}

func getSongDurationInSeconds() -> Double {
return musicPlayer.duration()
}

func getPositionInSeconds() -> Double {
return musicPlayer.position
}

func setPositionInSeconds(position: Double) {
musicPlayer.position = position
if (voicePlayer.duration() > position) {
voicePlayer.position = position
}
}

func getProgress() -> Float {
return Float(musicPlayer.position / musicPlayer.duration())
}

func setMusicVolume(volume: Float) {
musicGainNode.gain = volume
}

func setVoiceVolume(volume: Float) {
voiceGainNode.gain = volume
}

func enableReverb(enable: Bool) {
reverbNode.isEnabled = enable
}

func enableCompressor(enable: Bool) {
compressorNode.isEnabled = enable
}

func enableAutomaticVocalPitchCorrection(enable: Bool) {
avpcNode.isEnabled = enable
}
}

GitHub

You can find the source code on the following link:

Karaoke App - iOS

GitHub

You can find the source code on the following link:

Karaoke App - Android