You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:31:13