Skip to main content

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

GitHub

You can find the source code on the following link:

Ducking And Sound Effects App - iOS
GitHub

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.

Demo Video

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

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()
}
}

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.

GitHub

You can find the source code on the following link:

Ducking And Sound Effects App - iOS
GitHub

You can find the source code on the following link:

Ducking And Sound Effects App - Android