如何在AudioKit中通过音符门同步OSC与滤波器包络
解决方案:同步双ADSR包络控制振幅与滤波器截止频率
我之前也碰到过类似的需求,核心就是给两个不同参数分别绑定独立ADSR包络,然后在音符触发事件里同步启动/释放它们。下面是具体的实现步骤和Playground代码:
核心思路
我们需要创建两个独立的ADSR包络:一个控制振荡器组的振幅,另一个控制低通滤波器的截止频率。关键在于,在音符按下/释放的瞬间,同时触发两个包络的启动与停止操作,让两者的变化完全和音符事件同步。
在AudioKit里,用AmplitudeEnvelope专门处理信号振幅的包络变化,用ParameterEnvelope来控制滤波器截止频率这类参数的包络(它可以绑定任意音频节点的参数)。
Playground 完整代码示例
import AudioKit import PlaygroundSupport // 初始化AudioKit引擎 let engine = AudioEngine() // 1. 创建振荡器组(关闭自带包络,用外部ADSR控制) let oscBank = AMOscillatorBank( waveform: Table(.sine), count: 8, // 支持8个复音 attackDuration: 0, decayDuration: 0, sustainLevel: 1, releaseDuration: 0 ) // 2. 创建低通滤波器 let lowPass = LowPassFilter(oscBank) lowPass.cutoffFrequency = 2000 // 默认值,后续由ADSR控制变化范围 // 3. 创建振幅ADSR包络(控制振荡器输出振幅) let amplitudeEnvelope = AmplitudeEnvelope(oscBank) amplitudeEnvelope.attackDuration = 0.1 amplitudeEnvelope.decayDuration = 0.2 amplitudeEnvelope.sustainLevel = 0.7 amplitudeEnvelope.releaseDuration = 0.5 // 4. 创建截止频率ADSR包络(控制滤波器cutoff参数) // 用恒定值作为触发源,只需要它的包络曲线来驱动参数变化 let cutoffTrigger = Constant(value: 1) let cutoffEnvelope = ParameterEnvelope( cutoffTrigger, parameter: lowPass.cutoffFrequency, start: 500, // 包络起始截止频率 peak: 5000, // 包络峰值截止频率 end: 500, // 包络结束截止频率 attackDuration: 0.05, decayDuration: 0.3, sustainLevel: 0.5, // 持续阶段是峰值的50% → 2500Hz releaseDuration: 0.4 ) // 连接信号链路:振幅包络输出 → 滤波器 → 引擎输出 engine.output = lowPass // 启动AudioKit do { try engine.start() } catch { print("AudioKit启动失败: \(error)") } // 同步触发音符与双ADSR的核心方法 func playNote(midiNote: MIDINoteNumber, velocity: MIDIVelocity) { oscBank.play(noteNumber: midiNote, velocity: velocity) // 同时启动两个包络 amplitudeEnvelope.start() cutoffEnvelope.start() } func stopNote(midiNote: MIDINoteNumber) { oscBank.stop(noteNumber: midiNote) // 同时释放两个包络 amplitudeEnvelope.stop() cutoffEnvelope.stop() } // 测试:播放C4音符,3秒后释放 playNote(midiNote: 60, velocity: 100) DispatchQueue.main.asyncAfter(deadline: .now() + 3) { stopNote(midiNote: 60) } // 保持Playground运行 PlaygroundPage.current.needsIndefiniteExecution = true
关键细节说明
- 关闭振荡器自带包络:把
AMOscillatorBank的自带包络参数设为0,避免和外部AmplitudeEnvelope冲突,让振幅完全由外部包络控制。 - 参数包络的灵活配置:
ParameterEnvelope的start/peak/end可以自定义截止频率的变化区间,sustainLevel是相对于峰值的比例,按需调整就能得到不同的滤波动态效果。 - 严格同步触发:在
playNote和stopNote里必须同时调用两个包络的启动/释放方法,这样振幅和截止频率的包络变化才会完全和音符事件对齐。 - 复音扩展:如果需要支持多音符同时发声,可以为每个音符实例化对应的包络,或者利用
AKPolyphonicNode的特性来复用包络逻辑。
MIDI输入扩展提示
如果用MIDI键盘触发,只需要监听MIDI事件并调用上面的方法即可:
class MIDIInputHandler: MIDIListener { func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) { playNote(midiNote: noteNumber, velocity: velocity) } func receivedMIDINoteOff(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) { stopNote(midiNote: noteNumber) } } let midiHandler = MIDIInputHandler() MIDIHandler.shared.addListener(midiHandler)
内容的提问来源于stack exchange,提问作者Dmitry Evglevsky
相关产品推荐
相关产品推荐

