Skip to content

Commit 363f2be

Browse files
authored
Add multi-channel audio support (#11)
Preserve channel layout during quantization for audio with more than 2 channels. Previously, the quantization step would lose channel layout information, causing issues with multi-channel audio comparison. Also adds a 4-channel audio test and refactors buffer creation helpers to support arbitrary channel counts.
1 parent fd34c2b commit 363f2be

3 files changed

Lines changed: 48 additions & 15 deletions

File tree

Sources/AudioSnapshotTesting/Internal/AudioDataComparator.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,25 @@ enum AudioDataComparator {
5959
/// This matches the quantization that ALAC applies during encoding.
6060
private static func quantize(_ buffer: AVAudioPCMBuffer, bitDepth: AudioBitDepth) throws -> AVAudioPCMBuffer {
6161
let intFormat: AVAudioFormat?
62-
switch bitDepth {
63-
case .bits16:
62+
let commonFormat: AVAudioCommonFormat = bitDepth == .bits16 ? .pcmFormatInt16 : .pcmFormatInt32
63+
64+
// For multi-channel audio (>2 channels), we need to preserve the channel layout
65+
if let channelLayout = buffer.format.channelLayout {
6466
intFormat = AVAudioFormat(
65-
commonFormat: .pcmFormatInt16,
67+
commonFormat: commonFormat,
6668
sampleRate: buffer.format.sampleRate,
67-
channels: buffer.format.channelCount,
68-
interleaved: false
69+
interleaved: false,
70+
channelLayout: channelLayout
6971
)
70-
case .bits32:
72+
} else {
7173
intFormat = AVAudioFormat(
72-
commonFormat: .pcmFormatInt32,
74+
commonFormat: commonFormat,
7375
sampleRate: buffer.format.sampleRate,
7476
channels: buffer.format.channelCount,
7577
interleaved: false
7678
)
7779
}
78-
80+
7981
guard let intFormat else {
8082
throw AudioComparisonError.formatCreationFailed
8183
}

Tests/AudioSnapshotTestingTests/AudioSnapshotTestingTests.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,45 @@ func audioSnapshotSynthesized() async throws {
154154
)
155155
}
156156

157-
private func createBuffer(from samples: [Float]) -> AVAudioPCMBuffer {
158-
let format = AVAudioFormat(standardFormatWithSampleRate: 32768, channels: 1)!
159-
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(samples.count))!
160-
let channelData = buffer.floatChannelData![0]
161-
samples.enumerated().forEach { index, sample in
162-
channelData[index] = sample
157+
@available(iOS 16, macOS 13, *)
158+
@Test(
159+
"Multi-channel (4ch) audio comparison",
160+
.audioSnapshot(record: false, strategy: .spectrogram(hopSize: 4096, frequencyCount: 2048))
161+
)
162+
func multiChannelComparison() async throws {
163+
let frameCount = 44100
164+
let frequencies: [Float] = [440, 880, 1320, 1760]
165+
let channels = frequencies.map { frequency in
166+
synthesizeSignal(frequencyAmplitudePairs: [(frequency, 0.5)], count: frameCount)
167+
}
168+
let buffer = createBuffer(channels: channels, sampleRate: 44100)
169+
await assertAudioSnapshot(of: buffer, named: "multiChannelComparison.4ch")
170+
}
171+
172+
private func createBuffer(from samples: [Float], sampleRate: Double = 32768) -> AVAudioPCMBuffer {
173+
createBuffer(channels: [samples], sampleRate: sampleRate)
174+
}
175+
176+
private func createBuffer(channels: [[Float]], sampleRate: Double = 32768) -> AVAudioPCMBuffer {
177+
let channelCount = channels.count
178+
let frameCount = channels.first?.count ?? 0
179+
180+
let format: AVAudioFormat
181+
if channelCount <= 2 {
182+
format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: AVAudioChannelCount(channelCount))!
183+
} else {
184+
let layout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_DiscreteInOrder | UInt32(channelCount))!
185+
format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channelLayout: layout)
186+
}
187+
188+
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount))!
189+
for (channelIndex, samples) in channels.enumerated() {
190+
let channelData = buffer.floatChannelData![channelIndex]
191+
for (frameIndex, sample) in samples.enumerated() {
192+
channelData[frameIndex] = sample
193+
}
163194
}
164-
buffer.frameLength = AVAudioFrameCount(samples.count)
195+
buffer.frameLength = AVAudioFrameCount(frameCount)
165196
return buffer
166197
}
167198

0 commit comments

Comments
 (0)