DJ App
About the DJ App
DJ apps are really popular in the audio app ecosystem, giving both amateur and professional DJs the tools to mix, scratch, and blend music right from their mobile devices. Creating the perfect mix is an art form, and developing an app to support that art is no less challenging.
The conceptual foundation of a DJ app may seem simple, but the actual execution can get complicated, especially when dealing with real-time audio manipulation and synchronization. Elements like beat-matching, crossfading, must be flawlessly integrated to ensure a seamless user experience.
Enter SwitchboardSDK. Utilizing this framework significantly eases the development hurdles of building a DJ app. This SDK allows you to tie together different audio nodes, to create a robust, real-time audio manipulation platform. This example aims to demonstrate how you can efficiently construct a high-quality DJ app leveraging the capabilities of SwitchboardSDK.
DJ App
You can find the source code on the following link:
DJ App - iOS
You can find the source code on the following link:
DJ App - Android
The app has the following features:
- Mixing tracks
- Applying various effects
- Beat and tempo sync
This example uses the Superpowered Extension.
Why use the Superpowered Extension instead of Superpowered directly?
Main Screen
The main screen features volume and effect controls for both tracks, as well as a crossfader that allows for smooth transitions between the tracks.
Audio Graph
- Swift
- Kotlin
import SwitchboardSDK
import SwitchboardSuperpowered
class MainAudioEngine {
let audioGraph = SBAudioGraph()
let audioPlayerNodeAWithMasterControl = SBAdvancedAudioPlayerNode()
let audioPlayerNodeB = SBAdvancedAudioPlayerNode()
let gainNodeA = SBGainNode()
let gainNodeB = SBGainNode()
let compressorNodeA = SBCompressorNode()
let compressorNodeB = SBCompressorNode()
let flangerNodeA = SBFlangerNode()
let flangerNodeB = SBFlangerNode()
let reverbNodeA = SBReverbNode()
let reverbNodeB = SBReverbNode()
let filterNodeA = SBFilterNode()
let filterNodeB = SBFilterNode()
let mixerNode = SBMixerNode()
let audioEngine = SBAudioEngine()
init() {
audioGraph.addNode(audioPlayerNodeAWithMasterControl)
audioGraph.addNode(audioPlayerNodeB)
audioGraph.addNode(mixerNode)
audioGraph.addNode(gainNodeA)
audioGraph.addNode(gainNodeB)
audioGraph.addNode(compressorNodeA)
audioGraph.addNode(compressorNodeB)
audioGraph.addNode(flangerNodeA)
audioGraph.addNode(flangerNodeB)
audioGraph.addNode(reverbNodeA)
audioGraph.addNode(reverbNodeB)
audioGraph.addNode(filterNodeA)
audioGraph.addNode(filterNodeB)
audioGraph.connect(audioPlayerNodeAWithMasterControl, to: gainNodeA)
audioGraph.connect(gainNodeA, to: compressorNodeA)
audioGraph.connect(compressorNodeA, to: flangerNodeA)
audioGraph.connect(flangerNodeA, to: reverbNodeA)
audioGraph.connect(reverbNodeA, to: filterNodeA)
audioGraph.connect(filterNodeA, to: mixerNode)
audioGraph.connect(audioPlayerNodeB, to: gainNodeB)
audioGraph.connect(gainNodeB, to: compressorNodeB)
audioGraph.connect(compressorNodeB, to: flangerNodeB)
audioGraph.connect(flangerNodeB, to: reverbNodeB)
audioGraph.connect(reverbNodeB, to: filterNodeB)
audioGraph.connect(filterNodeB, to: mixerNode)
audioGraph.connect(mixerNode, to: audioGraph.outputNode)
audioPlayerNodeAWithMasterControl.isLoopingEnabled = true
audioPlayerNodeB.isLoopingEnabled = true
audioPlayerNodeAWithMasterControl.setNodeToSyncWith(playerNode)
audioEngine.start(audioGraph)
}
func start() {
audioEngine.start(audioGraph)
}
func stop() {
audioEngine.stop()
}
func pausePlayback() {
audioGraph.stop()
audioPlayerNodeAWithMasterControl.pause()
audioPlayerNodeB.pause()
}
func startPlayback() {
if audioPlayerNodeAWithMasterControl.isMaster {
audioPlayerNodeAWithMasterControl.play()
audioPlayerNodeB.playSynchronized()
} else {
audioPlayerNodeAWithMasterControl.playSynchronized()
audioPlayerNodeB.play()
}
audioGraph.start()
}
func loadA(path: String) {
audioPlayerNodeAWithMasterControl.load(path)
}
func loadB(path: String) {
audioPlayerNodeB.load(path)
}
func setBeatGridInformationA(originalBPM: Double, firstBeatMs: Double) {
audioPlayerNodeAWithMasterControl.setBeatGridInformationWithOriginalBPM(originalBPM, firstBeatMs: firstBeatMs)
}
func setBeatGridInformationB(originalBPM: Double, firstBeatMs: Double) {
audioPlayerNodeB.setBeatGridInformationWithOriginalBPM(originalBPM, firstBeatMs: firstBeatMs)
}
func isPlaying() -> Bool {
return audioPlayerNodeAWithMasterControl.isPlaying || audioPlayerNodeB.isPlaying
}
func setCrossfader(value: Float, volumeA: Float, volumeB: Float) {
gainNodeA.gain = volumeA * cosf(Float.pi / 2 * value)
gainNodeB.gain = volumeB * cosf(Float.pi / 2 * (1 - value))
audioPlayerNodeAWithMasterControl.isMaster = value <= 0.5
}
func setPlaybackRateA(rate: Float) {
audioPlayerNodeAWithMasterControl.playbackRate = Double(rate)
}
func setPlaybackRateB(rate: Float) {
audioPlayerNodeB.playbackRate = Double(rate)
}
func setVolumeA(volume: Float) {
gainNodeA.gain = volume
}
func setVolumeB(volume: Float) {
gainNodeB.gain = volume
}
func enableFilterA(enable: Bool) {
filterNodeA.isEnabled = enable
}
func enableFlangerA(enable: Bool) {
flangerNodeA.isEnabled = enable
}
func enableCompressorA(enable: Bool) {
compressorNodeA.isEnabled = enable
}
func enableReverbA(enable: Bool) {
reverbNodeA.isEnabled = enable
}
func enableFilterB(enable: Bool) {
filterNodeB.isEnabled = enable
}
func enableFlangerB(enable: Bool) {
flangerNodeB.isEnabled = enable
}
func enableCompressorB(enable: Bool) {
compressorNodeB.isEnabled = enable
}
func enableReverbB(enable: Bool) {
reverbNodeB.isEnabled = enable
}
}
package com.synervoz.djapp
import android.content.Context
import com.synervoz.switchboard.sdk.audioengine.AudioEngine
import com.synervoz.switchboard.sdk.audiograph.AudioGraph
import com.synervoz.switchboard.sdk.audiographnodes.GainNode
import com.synervoz.switchboard.sdk.audiographnodes.MixerNode
import com.synervoz.switchboardsuperpowered.audiographnodes.AdvancedAudioPlayerNode
import com.synervoz.switchboardsuperpowered.audiographnodes.CompressorNode
import com.synervoz.switchboardsuperpowered.audiographnodes.FilterNode
import com.synervoz.switchboardsuperpowered.audiographnodes.FlangerNode
import com.synervoz.switchboardsuperpowered.audiographnodes.ReverbNode
import kotlin.math.cos
class MainAudioEngine(context: Context) {
val audioGraph = AudioGraph()
val audioPlayerNodeAWithMasterControl = AdvancedAudioPlayerNode()
val audioPlayerNodeB = AdvancedAudioPlayerNode()
val gainNodeA = GainNode()
val gainNodeB = GainNode()
val compressorNodeA = CompressorNode()
val compressorNodeB = CompressorNode()
val flangerNodeA = FlangerNode()
val flangerNodeB = FlangerNode()
val reverbNodeA = ReverbNode()
val reverbNodeB = ReverbNode()
val filterNodeA = FilterNode()
val filterNodeB = FilterNode()
val mixerNode = MixerNode()
val audioEngine = AudioEngine(context)
init {
audioGraph.addNode(audioPlayerNodeAWithMasterControl)
audioGraph.addNode(audioPlayerNodeB)
audioGraph.addNode(mixerNode)
audioGraph.addNode(gainNodeA)
audioGraph.addNode(gainNodeB)
audioGraph.addNode(compressorNodeA)
audioGraph.addNode(compressorNodeB)
audioGraph.addNode(flangerNodeA)
audioGraph.addNode(flangerNodeB)
audioGraph.addNode(reverbNodeA)
audioGraph.addNode(reverbNodeB)
audioGraph.addNode(filterNodeA)
audioGraph.addNode(filterNodeB)
audioGraph.connect(audioPlayerNodeAWithMasterControl, gainNodeA)
audioGraph.connect(gainNodeA, compressorNodeA)
audioGraph.connect(compressorNodeA, flangerNodeA)
audioGraph.connect(flangerNodeA, reverbNodeA)
audioGraph.connect(reverbNodeA, filterNodeA)
audioGraph.connect(filterNodeA, mixerNode)
audioGraph.connect(audioPlayerNodeB, gainNodeB)
audioGraph.connect(gainNodeB, compressorNodeB)
audioGraph.connect(compressorNodeB, flangerNodeB)
audioGraph.connect(flangerNodeB, reverbNodeB)
audioGraph.connect(reverbNodeB, filterNodeB)
audioGraph.connect(filterNodeB, mixerNode)
audioGraph.connect(mixerNode, audioGraph.outputNode)
audioPlayerNodeAWithMasterControl.isLoopingEnabled = true
audioPlayerNodeB.isLoopingEnabled = true
audioPlayerNodeAWithMasterControl.setNodeToSyncWith(audioPlayerNodeB)
audioEngine.start(audioGraph)
}
val isPlaying: Boolean
get() {
return audioPlayerNodeAWithMasterControl.isPlaying || audioPlayerNodeB.isPlaying
}
fun pausePlayback() {
audioGraph.stop()
audioPlayerNodeAWithMasterControl.pause()
audioPlayerNodeB.pause()
}
fun startPlayback() {
if (audioPlayerNodeAWithMasterControl.isMaster) {
audioPlayerNodeAWithMasterControl.play()
audioPlayerNodeB.playSynchronized()
} else {
audioPlayerNodeAWithMasterControl.playSynchronized()
audioPlayerNodeB.play()
}
audioGraph.start()
}
fun loadA(packageResourcePath: String, fileOffset: Int, fileLength: Int) {
audioPlayerNodeAWithMasterControl.loadFromAssetFile(packageResourcePath, fileOffset, fileLength)
}
fun loadB(packageResourcePath: String, fileOffset: Int, fileLength: Int) {
audioPlayerNodeB.loadFromAssetFile(packageResourcePath, fileOffset, fileLength)
}
fun setBeatGridInformationA(originalBPM: Double, firstBeatMs: Double) {
audioPlayerNodeAWithMasterControl.setBeatGridInformation(originalBPM, firstBeatMs)
}
fun setBeatGridInformationB(originalBPM: Double, firstBeatMs: Double) {
audioPlayerNodeB.setBeatGridInformation(originalBPM, firstBeatMs)
}
fun setCrossfader(crossFaderPosition: Float, volumeA: Float, volumeB: Float) {
gainNodeA.gain = (volumeA * cos(Math.PI / 2 * crossFaderPosition)).toFloat()
gainNodeB.gain = (volumeB * cos(Math.PI / 2 * (1 - crossFaderPosition))).toFloat()
audioPlayerNodeAWithMasterControl.isMaster = crossFaderPosition <= 0.5
}
fun getPlaybackRateA() = audioPlayerNodeAWithMasterControl.playbackRate
fun getPlaybackRateB() = audioPlayerNodeB.playbackRate
fun setPlaybackRateA(rate: Float) {
audioPlayerNodeAWithMasterControl.playbackRate = rate.toDouble()
}
fun setPlaybackRateB(rate: Float) {
audioPlayerNodeB.playbackRate = rate.toDouble()
}
fun setVolumeA(volume: Float) {
gainNodeA.gain = volume
}
fun setVolumeB(volume: Float) {
gainNodeB.gain = volume
}
fun enableFilterA(enable: Boolean) {
filterNodeA.isEnabled = enable
}
fun enableFlangerA(enable: Boolean) {
flangerNodeA.isEnabled = enable
}
fun enableCompressorA(enable: Boolean) {
compressorNodeA.isEnabled = enable
}
fun enableReverbA(enable: Boolean) {
reverbNodeA.isEnabled = enable
}
fun enableFilterB(enable: Boolean) {
filterNodeB.isEnabled = enable
}
fun enableFlangerB(enable: Boolean) {
flangerNodeB.isEnabled = enable
}
fun enableCompressorB(enable: Boolean) {
compressorNodeB.isEnabled = enable
}
fun enableReverbB(enable: Boolean) {
reverbNodeB.isEnabled = enable
}
fun close() {
audioEngine.stop()
audioGraph.close()
audioPlayerNodeAWithMasterControl.close()
audioPlayerNodeB.close()
gainNodeA.close()
gainNodeB.close()
compressorNodeA.close()
compressorNodeB.close()
flangerNodeA.close()
flangerNodeB.close()
reverbNodeA.close()
reverbNodeB.close()
filterNodeA.close()
filterNodeB.close()
mixerNode.close()
audioEngine.close()
}
}
You can find the source code on the following link:
DJ App - iOS
You can find the source code on the following link:
DJ App - Android