Ducking And Sound Effects App
About Ducking And Sound Effects
Providing your users with the best audio experience is essential for any communication app.
Ducking down your music while voice communication is active makes a fine distinction between different audio sources, while playing audio effects can spice up user interactions.
In this example we will extend the functionality of the Voice Communication App
Ducking And Sound Effects App
You can find the source code on the following link:
Ducking And Sound Effects App - iOS
You can find the source code on the following link:
Ducking And Sound Effects App - Android
The app has the following features over the Voice Communication App:
- Playing sound effect which is also transmitted in the active room
- Lowering music and effect volume when there is voice activity in the room
It consists of the following single screen:
- Room Screen: Voice communication in a room, custom username and room name setting, user presence list and play music and sound effect button.
This example uses the Agora Extension.
Room Screen
The Room screen consists of a username and room name input field, a join button and a user presence list and play music and play sound effect button.
To be able to join a room a username and a room name has to be entered. After joining a room the voice communication is possible with the remote parties in the same room.
After joining a room pressing the play sound effect button plays it locally while also transmitting it through the room to the other users.
The user is also able to listen to music, which is only played locally.
Audio Graph
The audio graph for the Room screen looks the following:
This graph might look daunting compared to the Voice Communication App's graph but upon closer inspection we can distinguish separate functions.
Let's start with the players. We have two separate players as their outputs are routed differently. The Music Player only needs to be played locally while the Effect Player has to be transmitted in the room as well.
The Ducking Node has two different input types. The first one is the audio which needs to be ducked, while the rest are the control audio which indicates when the audio needs to be ducked.
The ducked audio is the mix of the Music and Effect player as we want to lower both the Music and the Effects when somebody is speaking. Multiple controls can be routed to the Ducking Node so the user’s microphone input and the audio from Agora can be routed separately.
Code Example
- Swift
- Kotlin
import SwitchboardSDK
import SwitchboardAgora
class AudioSystem {
let audioEngine = SBAudioEngine()
let audioGraph = SBAudioGraph()
let agoraResampledSourceNode = SBResampledSourceNode()
let agoraResampledSinkNode = SBResampledSinkNode()
let musicPlayerNode = SBAudioPlayerNode()
let effectsPlayerNode = SBAudioPlayerNode()
let effectsSplitterNode = SBBusSplitterNode()
let playerMixerNode = SBMixerNode()
let inputSplitterNode = SBBusSplitterNode()
let inputMultiChannelToMonoNode = SBMultiChannelToMonoNode()
let monoToMultiChannelNode = SBMonoToMultiChannelNode()
let musicDuckingNode = SBMusicDuckingNode()
let agoraOutputMixerNode = SBMixerNode()
let multiChannelToMonoNode = SBMultiChannelToMonoNode()
let agoraSourceSplitterNode = SBBusSplitterNode()
let speakerMixerNode = SBMixerNode()
var isPlaying: Bool {
musicPlayerNode.isPlaying
}
init(roomManager: RoomManager) {
audioEngine.microphoneEnabled = true
audioEngine.voiceProcessingEnabled = true
agoraResampledSourceNode.sourceNode = roomManager.sourceNode
agoraResampledSinkNode.sinkNode = roomManager.sinkNode
agoraResampledSourceNode.internalSampleRate = roomManager.audioBus.getSampleRate()
agoraResampledSinkNode.internalSampleRate = roomManager.audioBus.getSampleRate()
let music = Bundle.main.url(forResource: "EMH-My_Lover", withExtension: "mp3")!
let effect = Bundle.main.url(forResource: "airhorn", withExtension: "mp3")!
musicPlayerNode.isLoopingEnabled = true
musicPlayerNode.load(music.absoluteString, withFormat: .apple)
effectsPlayerNode.load(effect.absoluteString, withFormat: .apple)
audioGraph.addNode(agoraResampledSourceNode)
audioGraph.addNode(agoraResampledSinkNode)
audioGraph.addNode(musicPlayerNode)
audioGraph.addNode(effectsPlayerNode)
audioGraph.addNode(effectsSplitterNode)
audioGraph.addNode(playerMixerNode)
audioGraph.addNode(inputSplitterNode)
audioGraph.addNode(inputMultiChannelToMonoNode)
audioGraph.addNode(monoToMultiChannelNode)
audioGraph.addNode(musicDuckingNode)
audioGraph.addNode(agoraOutputMixerNode)
audioGraph.addNode(multiChannelToMonoNode)
audioGraph.addNode(agoraSourceSplitterNode)
audioGraph.addNode(speakerMixerNode)
audioGraph.connect(effectsPlayerNode, to: effectsSplitterNode)
audioGraph.connect(effectsSplitterNode, to: playerMixerNode)
audioGraph.connect(musicPlayerNode, to: playerMixerNode)
audioGraph.connect(playerMixerNode, to: musicDuckingNode)
audioGraph.connect(audioGraph.inputNode, to: inputSplitterNode)
audioGraph.connect(inputSplitterNode, to: inputMultiChannelToMonoNode)
audioGraph.connect(inputMultiChannelToMonoNode, to: musicDuckingNode)
audioGraph.connect(inputSplitterNode, to: agoraOutputMixerNode)
audioGraph.connect(effectsSplitterNode, to: agoraOutputMixerNode)
audioGraph.connect(agoraOutputMixerNode, to: multiChannelToMonoNode)
audioGraph.connect(multiChannelToMonoNode, to: agoraResampledSinkNode)
audioGraph.connect(agoraResampledSourceNode, to: agoraSourceSplitterNode)
audioGraph.connect(agoraSourceSplitterNode, to: musicDuckingNode)
audioGraph.connect(agoraSourceSplitterNode, to: monoToMultiChannelNode)
audioGraph.connect(monoToMultiChannelNode, to: speakerMixerNode)
audioGraph.connect(musicDuckingNode, to: speakerMixerNode)
audioGraph.connect(speakerMixerNode, to: audioGraph.outputNode)
}
func start() {
audioEngine.start(audioGraph)
}
func stop() {
audioEngine.stop()
}
func playMusic() {
musicPlayerNode.play()
}
func pauseMusic() {
musicPlayerNode.pause()
}
func playSoundEffect() {
effectsPlayerNode.stop()
effectsPlayerNode.play()
}
}
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.BusSplitterNode
import com.synervoz.switchboard.sdk.audiographnodes.MixerNode
import com.synervoz.switchboard.sdk.audiographnodes.MonoToMultiChannelNode
import com.synervoz.switchboard.sdk.audiographnodes.MultiChannelToMonoNode
import com.synervoz.switchboard.sdk.audiographnodes.MusicDuckingNode
import com.synervoz.switchboard.sdk.audiographnodes.ResampledSinkNode
import com.synervoz.switchboard.sdk.audiographnodes.ResampledSourceNode
import com.synervoz.switchboardagora.rooms.RoomManager
class AudioSystem(roomManager: RoomManager) {
val audioEngine = AudioEngine(enableInput = true)
val audioGraph = AudioGraph()
val agoraResampledSourceNode = ResampledSourceNode()
val agoraResampledSinkNode = ResampledSinkNode()
val musicPlayerNode = AudioPlayerNode()
val effectsPlayerNode = AudioPlayerNode()
val effectsSplitterNode = BusSplitterNode()
val playerMixerNode = MixerNode()
val inputSplitterNode = BusSplitterNode()
val inputMultiChannelToMonoNode = MultiChannelToMonoNode()
val monoToMultiChannelNode = MonoToMultiChannelNode()
val musicDuckingNode = MusicDuckingNode()
val agoraOutputMixerNode = MixerNode()
val multiChannelToMonoNode = MultiChannelToMonoNode()
val agoraSourceSplitterNode = BusSplitterNode()
val speakerMixerNode = MixerNode()
val isPlaying: Boolean
get() = musicPlayerNode.isPlaying
init {
agoraResampledSourceNode.setSourceNode(roomManager.sourceNode)
agoraResampledSinkNode.setSinkNode(roomManager.sinkNode)
agoraResampledSourceNode.internalSampleRate = roomManager.getAudioBus().sampleRate
agoraResampledSinkNode.internalSampleRate = roomManager.getAudioBus().sampleRate
musicPlayerNode.isLoopingEnabled = true
audioGraph.addNode(agoraResampledSourceNode)
audioGraph.addNode(agoraResampledSinkNode)
audioGraph.addNode(musicPlayerNode)
audioGraph.addNode(effectsPlayerNode)
audioGraph.addNode(effectsSplitterNode)
audioGraph.addNode(playerMixerNode)
audioGraph.addNode(inputSplitterNode)
audioGraph.addNode(inputMultiChannelToMonoNode)
audioGraph.addNode(monoToMultiChannelNode)
audioGraph.addNode(musicDuckingNode)
audioGraph.addNode(agoraOutputMixerNode)
audioGraph.addNode(multiChannelToMonoNode)
audioGraph.addNode(agoraSourceSplitterNode)
audioGraph.addNode(speakerMixerNode)
audioGraph.connect(effectsPlayerNode, effectsSplitterNode)
audioGraph.connect(effectsSplitterNode, playerMixerNode)
audioGraph.connect(musicPlayerNode, playerMixerNode)
audioGraph.connect(playerMixerNode, musicDuckingNode)
audioGraph.connect(audioGraph.inputNode, inputSplitterNode)
audioGraph.connect(inputSplitterNode, inputMultiChannelToMonoNode)
audioGraph.connect(inputMultiChannelToMonoNode, musicDuckingNode)
audioGraph.connect(inputSplitterNode, agoraOutputMixerNode)
audioGraph.connect(effectsSplitterNode, agoraOutputMixerNode)
audioGraph.connect(agoraOutputMixerNode, multiChannelToMonoNode)
audioGraph.connect(multiChannelToMonoNode, agoraResampledSinkNode)
audioGraph.connect(agoraResampledSourceNode, agoraSourceSplitterNode)
audioGraph.connect(agoraSourceSplitterNode, musicDuckingNode)
audioGraph.connect(agoraSourceSplitterNode, monoToMultiChannelNode)
audioGraph.connect(monoToMultiChannelNode, speakerMixerNode)
audioGraph.connect(musicDuckingNode, speakerMixerNode)
audioGraph.connect(speakerMixerNode, audioGraph.outputNode)
}
fun start() {
audioEngine.start(audioGraph)
}
fun stop() {
audioEngine.stop()
}
fun playMusic() {
musicPlayerNode.play()
}
fun pauseMusic() {
musicPlayerNode.pause()
}
fun playSoundEffect() {
effectsPlayerNode.stop()
effectsPlayerNode.play()
}
fun close() {
audioGraph.close()
audioEngine.close()
multiChannelToMonoNode.close()
agoraResampledSourceNode.close()
agoraResampledSinkNode.close()
monoToMultiChannelNode.close()
}
}
The RoomManager
object in the constructor comes from the communication extension. It provides the audio system with the source and sink nodes through which the remote audio can be received and the local audio can be sent while using audio players to play the audio locally and transmit through the remote sink.
You can find the source code on the following link:
Ducking And Sound Effects App - iOS
You can find the source code on the following link:
Ducking And Sound Effects App - Android