如何在AudioKit中构建带频率/音高包络的振荡器?
嘿,作为AudioKit新手,你这个思路抓得特别准——把ADSR包络映射到频率这类参数上,确实没法直接用现成的振荡器Bank(它们默认只把ADSR连到音量增益上),得自己动手改改DSP层面的路由逻辑。其实你说的“借鉴AKxxxOscillatorBanks的代码,把ADSR应用到音高”完全可行,下面给你具体的实现思路和代码示例:
核心思路拆解
现成的振荡器Bank本质是给每个音符分配一个振荡器+一个ADSR包络,然后把包络输出连到振荡器的增益参数。我们要做的就是:
- 保留“多复音+每个音符对应独立包络”的结构
- 把包络的输出目标从「增益」换成「频率」(或者你需要的其他参数,比如滤波器截止频率、相位等)
具体实现步骤与代码示例
下面是一个简化版的「支持频率ADSR的正弦振荡器Bank」,你可以基于这个扩展成其他波形的版本:
import AudioKit class ADSRFrequencyOscillatorBank: AKPolyphonicNode { // 存储每个复音对应的振荡器和包络生成器 private var oscillators: [AKSineOscillator] = [] private var envelopeGenerators: [AKEnvelopeGenerator] = [] // 可配置的最大频率偏移范围(单位:Hz) var maxFrequencyOffset: Double = 500.0 // 初始化:指定最大复音数,创建对应数量的振荡器和包络 init(maxPolyphony: Int = 8, maxFrequencyOffset: Double = 500.0) { self.maxFrequencyOffset = maxFrequencyOffset super.init() for _ in 0..<maxPolyphony { let osc = AKSineOscillator() let env = AKEnvelopeGenerator() // 关键:把包络输出映射到频率偏移,再连到振荡器的频率参数 // 包络输出范围是0-1,乘以偏移范围后得到0到maxFrequencyOffset的偏移量 env.output.map { $0 * maxFrequencyOffset } >>> osc.frequency oscillators.append(osc) envelopeGenerators.append(env) // 把振荡器输出连到Bank的总输出 osc >>> self.outputNode } } // 触发音符:设置基础音高、力度和ADSR参数 func play(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, attackDuration: Double = 0.1, decayDuration: Double = 0.2, sustainLevel: Double = 0.5, releaseDuration: Double = 0.3) { // 找到空闲的振荡器和包络(没有就复用第一个,简单处理) guard let (freeOsc, freeEnv) = findFreeVoice() else { return } // 计算当前音符的基础频率 let baseFrequency = AKMIDIConverters.frequency(forNoteNumber: noteNumber) freeOsc.frequency = baseFrequency // 配置包络的ADSR参数 freeEnv.attackDuration = attackDuration freeEnv.decayDuration = decayDuration freeEnv.sustainLevel = sustainLevel freeEnv.releaseDuration = releaseDuration // 启动包络和振荡器 freeEnv.start() freeOsc.start() } // 释放音符:触发包络的release阶段,结束后停止振荡器 func stop(noteNumber: MIDINoteNumber) { for (osc, env) in zip(oscillators, envelopeGenerators) { if osc.isPlaying { env.stop() // 等包络释放完成后再停止振荡器,避免突然截断 DispatchQueue.main.asyncAfter(deadline: .now() + env.releaseDuration) { osc.stop() } } } } // 辅助方法:找到第一个空闲的振荡器+包络组合 private func findFreeVoice() -> (AKSineOscillator, AKEnvelopeGenerator)? { for (osc, env) in zip(oscillators, envelopeGenerators) { if !osc.isPlaying { return (osc, env) } } return (oscillators[0], envelopeGenerators[0]) } }
关键细节说明
包络到频率的映射:
上面的代码是把包络的0-1输出直接转成Hz偏移,如果你想要更符合音乐逻辑的音高变化(比如半音偏移),可以把映射逻辑改成:// 把包络值映射到-12到+12半音的偏移,再转成频率倍数 env.output.map { let semitoneOffset = $0 * 24 - 12 // 范围-12到+12 return pow(2, semitoneOffset / 12) } >>> osc.frequency这样包络输出0对应降12半音,1对应升12半音,sustain阶段保持在对应比例的音高。
扩展到其他参数:
如果你想把包络同时连到多个参数(比如频率+滤波器截止频率),只需要多加几条连接就行:// 同时控制频率偏移和滤波器截止频率 env.output.map { $0 * maxFrequencyOffset } >>> osc.frequency env.output.map { $0 * 5000 + 100 } >>> filter.cutoffFrequency参考官方Bank源码:
你可以去看AudioKit里AKOscillatorBank的源码,它的ADSR就是连到AKAmplitudeEnvelope再控制增益的,照着那个结构改参数目标就行,官方的代码在复音管理、音符匹配上更严谨,适合进阶优化。
内容的提问来源于stack exchange,提问作者headkit
相关产品推荐
相关产品推荐

