Last active
November 18, 2023 18:20
-
-
Save ABridoux/fa31bb912cc5504e51f50a5b8a6d0fa0 to your computer and use it in GitHub Desktop.
Wrapper around an `AVAudioUnit` of type `kAudioUnitType_Mixer` and sub type `kAudioUnitSubType_MatrixMixer` for convenient usage.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AVFoundation | |
// MARK: - MatrixMixerNodeWrapper | |
/// Wrapper around an audio unit node of type `kAudioUnitSubType_MatrixMixer` for convenience. | |
public final class MatrixMixerNodeWrapper { | |
// MARK: Properties | |
/// Matrix mixer to map input channels | |
/// | |
/// Helpful links about `kAudioUnitSubType_MatrixMixer` usage: | |
/// - https://lists.apple.com/archives/coreaudio-api/2008/Apr/msg00169.html | |
/// - https://stackoverflow.com/questions/48059405/how-should-an-aumatrixmixer-be-configured-in-an-avaudioengine-graph | |
/// - https://stackoverflow.com/questions/53208006/change-audio-volume-of-some-channels-using-avaudioengine | |
private let matrixMixerNode: AVAudioUnit | |
/// Volume of the node output. | |
public var volume: Float = 1 { | |
didSet { updateOutputVolume() } | |
} | |
// MARK: Init | |
public init() async throws { | |
let matrixUnitDescription = AudioComponentDescription( | |
componentType: kAudioUnitType_Mixer, | |
componentSubType: kAudioUnitSubType_MatrixMixer, | |
componentManufacturer: kAudioUnitManufacturer_Apple, | |
componentFlags: 0, | |
componentFlagsMask: 0 | |
) | |
matrixMixerNode = try await AVAudioUnit.instantiate(with: matrixUnitDescription) | |
} | |
} | |
// MARK: - Node | |
extension MatrixMixerNodeWrapper { | |
/// Wrapped node. | |
public var node: AVAudioNode { matrixMixerNode } | |
} | |
// MARK: - Setup | |
extension MatrixMixerNodeWrapper { | |
/// Setup the matrix mixer to pass audio. | |
/// | |
/// - important: Has to be called after the `AVAudioEngine` has been started. | |
public func setup() { | |
AudioUnitSetParameter(matrixMixerNode.audioUnit, kMatrixMixerParam_Volume, kAudioUnitScope_Global, 0xFFFF_FFFF, 1, 0) | |
for inputChannelIndex in 0..<matrixMixerNode.inputFormat(forBus: 0).channelCount { | |
AudioUnitSetParameter( | |
matrixMixerNode.audioUnit, | |
kMatrixMixerParam_Volume, | |
kAudioUnitScope_Input, | |
inputChannelIndex, | |
1, | |
0 | |
) | |
} | |
updateOutputVolume() | |
} | |
private func updateOutputVolume() { | |
for outputChannelIndex in 0..<matrixMixerNode.outputFormat(forBus: 0).channelCount { | |
AudioUnitSetParameter( | |
matrixMixerNode.audioUnit, | |
kMatrixMixerParam_Volume, | |
kAudioUnitScope_Output, | |
outputChannelIndex, | |
volume, | |
0 | |
) | |
} | |
} | |
} | |
// MARK: - Mapping | |
extension MatrixMixerNodeWrapper { | |
/// Set the volume for the input channel to the output channels in the `outputChannelIndexes` set. | |
public func setInputVolume( | |
_ volume: Float, | |
forInputChannel inputChannelIndex: Int, | |
toOutputChannels outputChannelIndexes: Set<Int> | |
) { | |
var outputChannelIndex = 0 | |
while outputChannelIndex < matrixMixerNode.outputFormat(forBus: 0).channelCount { | |
let volume = outputChannelIndexes.contains(outputChannelIndex) ? volume : 0 | |
let crossPoint = UInt32((inputChannelIndex << 16) | outputChannelIndex) | |
AudioUnitSetParameter( | |
matrixMixerNode.audioUnit, | |
kMatrixMixerParam_Volume, | |
kAudioUnitScope_Global, | |
crossPoint, | |
volume, | |
0 | |
) | |
outputChannelIndex += 1 | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Notes
Usage
Setup
Note
Calling
setup
every time the engine is started is not mandatory, but I find it easier to manage and couldn't find any drawbacks.Update volume and channel connexions
Set node output volume:
Set volume for one connexion:
Important
When no volume is set for a connexion, its default value is 0. Also, it's better to set the volume for each connexion each time the engine is started.
Connexions examples
Make a stereo signal mono
Invert left and right channels
Links
Here are some links that were useful to make that work: