【问题标题】:How do I properly mix two AVAudioSourceNode objects (Signal Generators) into stereo output?如何正确地将两个 AVAudioSourceNode 对象(信号发生器)混合到立体声输出中?
【发布时间】:2022-07-13 00:56:27
【问题描述】:

我正在尝试采用 Apple 的 Building a Signal Generator 示例代码并对其进行扩充,以便我可以在立体声通道的左耳中播放与立体声通道中的右耳不同的频率。不幸的是,我尝试的结果是明显失真的噪音,而不是两种可区分的音调。

我还尝试创建两个具有相同频率和线性信号的 AVAudioSourceNode 来测试我的感知,但它似乎仍然失真。

有一次我包装了音频引擎连接并从DispatchQueue.global(qos: .background).async { } 开始,但这似乎也无关紧要。它仍然存在于代码中。

代码如下:

import SwiftUI
import AVFoundation

let frequency: Float = 432
let amplitude: Float = 1

let twoPi = 2 * Float.pi

let linear = { (phase: Float) -> Float in
    return phase
}

//let sine = { (phase: Float) -> Float in
//    return sin(phase)
//}
//
//let whiteNoise = { (phase: Float) -> Float in
//    return ((Float(arc4random_uniform(UINT32_MAX)) / Float(UINT32_MAX)) * 2 - 1)
//}
//
//let sawtoothUp = { (phase: Float) -> Float in
//    return 1.0 - 2.0 * (phase * (1.0 / twoPi))
//}
//
//let sawtoothDown = { (phase: Float) -> Float in
//    return (2.0 * (phase * (1.0 / twoPi))) - 1.0
//}
//
//let square = { (phase: Float) -> Float in
//    if phase <= Float.pi {
//        return 1.0
//    } else {
//        return -1.0
//    }
//}
//
//let triangle = { (phase: Float) -> Float in
//    var value = (2.0 * (phase * (1.0 / twoPi))) - 1.0
//    if value < 0.0 {
//        value = -value
//    }
//    return 2.0 * (value - 0.5)
//}

typealias Signal = (Float) -> Float

let signal: Signal = linear

let engine = AVAudioEngine()
let mainMixer = engine.mainMixerNode
let output = engine.outputNode
let outputFormat = output.inputFormat(forBus: 0)
let sampleRate = Float(outputFormat.sampleRate)
// Use the output format for the input, but reduce the channel count to 1.
let inputFormat = AVAudioFormat(commonFormat: outputFormat.commonFormat,
                                sampleRate: outputFormat.sampleRate,
                                channels: 1,
                                interleaved: outputFormat.isInterleaved)

var currentPhase: Float = 0
// The interval to advance the phase each frame.
let phaseIncrement = (twoPi / sampleRate) * frequency

let leftSrcNode = AVAudioSourceNode { _, _, frameCount, audioBufferList -> OSStatus in
    let ablPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)
    for frame in 0..<Int(frameCount) {
        // Get the signal value for this frame at time.
        let value = signal(currentPhase) * amplitude
        // Advance the phase for the next frame.
        currentPhase += phaseIncrement
        if currentPhase >= twoPi {
            currentPhase -= twoPi
        }
        if currentPhase < 0.0 {
            currentPhase += twoPi
        }
        // Set the same value on all channels (due to the inputFormat, there's only one channel though).
        for buffer in ablPointer {
            let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
            buf[frame] = value
        }
    }
    return noErr
}

let rightSrcNode = AVAudioSourceNode { _, _, frameCount, audioBufferList -> OSStatus in
    let ablPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)
    for frame in 0..<Int(frameCount) {
        // Get the signal value for this frame at time.
        let value = signal(currentPhase) * amplitude
        // Advance the phase for the next frame.
        currentPhase += phaseIncrement
        if currentPhase >= twoPi {
            currentPhase -= twoPi
        }
        if currentPhase < 0.0 {
            currentPhase += twoPi
        }
        // Set the same value on all channels (due to the inputFormat, there's only one channel though).
        for buffer in ablPointer {
            let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
            buf[frame] = value
        }
    }
    return noErr
}


@main
struct binauralApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
    
    init() {
        DispatchQueue.global(qos: .background).async {
            leftSrcNode.pan = -1.0
            engine.attach(leftSrcNode)
            engine.connect(leftSrcNode, to: mainMixer, format: inputFormat)
            
            rightSrcNode.pan = 1.0
            engine.attach(rightSrcNode)
            engine.connect(rightSrcNode, to: mainMixer, format: inputFormat)
            
            engine.connect(mainMixer, to: output, format: outputFormat)
            mainMixer.outputVolume = 0.5
            do {
               try engine.start()
            } catch {
               print("Could not start engine: \(error.localizedDescription)")
            }
        }
    }
}

【问题讨论】:

    标签: swift avfoundation core-audio


    【解决方案1】:

    这是一个没有完全理解信号发生器示例代码的愚蠢错误。

    解决方案是将两个渲染块 currentPhasephaseIncrement 之间同时发生变异的两个变量解耦。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-20
      • 1970-01-01
      相关资源
      最近更新 更多