我有一个简单的应用程序,带有键盘来播放音符。我的问题是我正在尝试添加一个滑块来弯曲音符音高,但音高没有变化。没有错误或任何东西,音符继续播放,音高没有任何变化。
我知道还有其他类似的问题,我已经阅读了我能找到的所有内容,并且我所实施的内容是基于该研究,但它不起作用。
请帮助我找出我做错了什么,或者为我指出一个更好的方向。
在我的工作代码中,我使用的是 AudioToolbox。我创建并打开一个新的 AUGraph。我在图中添加了一个 kAudioUnitSubType_RemoteIO 和一个 kAudioUnitSubType_MIDISynth 节点。我把它全部连接起来,我可以弹奏音符。
为了添加弯音,我创建了一个 kAudioUnitSubType_NewTimePitch AUNode,并将它连接在合成器和输出节点之间。然后我从效果节点获取音高 AudioUnit,当我的滑块值更改时,我尝试使用带有 kNewTimePitchParam_Pitch 的 AudioUnitSetParameter 更改音高。一切仍然有效,但是当我弹奏一个音符并拖动滑块时,音符的音高没有任何变化。我的滑块值在 -1000 和 +1000 之间变化,我看到打印出来的值,所以我知道正在调用 pitchBend 函数。
这是我的相关代码。这段代码,除了我试图添加的音高效果部分,大量复制了我在这里找到的内容: https ://rollout.io/blog/building-a-midi-music-app-for-ios-in -迅速/
import Foundation
import AudioToolbox
class AudioSynth
{
var audioGraph: AUGraph?
var synthNode = AUNode()
var effectNode = AUNode()
var outputNode = AUNode()
var synthUnit: AudioUnit?
var effectUnit: AudioUnit?
var patch = UInt32(0)
func initAudio() {
checkError(osstatus: NewAUGraph(&audioGraph))
createOutputNode(audioGraph: audioGraph!, outputNode: &outputNode)
createSynthNode()
createEffectNode()
checkError(osstatus: AUGraphOpen(audioGraph!))
// get the synth unit
checkError(osstatus: AUGraphNodeInfo(audioGraph!, synthNode, nil, &synthUnit))
checkError(osstatus: AUGraphNodeInfo(audioGraph!, effectNode, nil, &effectUnit))
let synthOutputElement: AudioUnitElement = 0
let effectOutputElement: AudioUnitElement = 0
let ioUnitInputElement: AudioUnitElement = 0
checkError(osstatus:
AUGraphConnectNodeInput(audioGraph!, synthNode, synthOutputElement,
effectNode, effectOutputElement))
checkError(osstatus:
AUGraphConnectNodeInput(audioGraph!, effectNode, effectOutputElement,
outputNode, ioUnitInputElement))
checkError(osstatus: AUGraphInitialize(audioGraph!))
checkError(osstatus: AUGraphStart(audioGraph!))
loadSoundFont()
loadPatch(patchNo: 0)
}
// Mark: - Audio Init Utility Methods
func createOutputNode(audioGraph: AUGraph, outputNode: UnsafeMutablePointer<AUNode>) {
var cd = AudioComponentDescription(
componentType: OSType(kAudioUnitType_Output),
componentSubType: OSType(kAudioUnitSubType_RemoteIO),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: 0,componentFlagsMask: 0)
checkError(osstatus: AUGraphAddNode(audioGraph, &cd, outputNode))
}
func createSynthNode() {
var cd = AudioComponentDescription(
componentType: OSType(kAudioUnitType_MusicDevice),
componentSubType: OSType(kAudioUnitSubType_MIDISynth),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: 0,componentFlagsMask: 0)
checkError(osstatus: AUGraphAddNode(audioGraph!, &cd, &synthNode))
}
func createEffectNode() {
var cd = AudioComponentDescription(
componentType: OSType(kAudioUnitType_Effect),
componentSubType: OSType(kAudioUnitSubType_NewTimePitch),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: 0,componentFlagsMask: 0)
checkError(osstatus: AUGraphAddNode(audioGraph!, &cd, &effectNode))
}
func pitchBend(pitchBend: Double) {
print("Bend pitch by " + String(pitchBend))
checkError(osstatus: AudioUnitSetParameter(effectUnit!, AudioUnitPropertyID(kNewTimePitchParam_Pitch), AudioUnitScope(kAudioUnitScope_Global), 0, AudioUnitParameterValue(pitchBend), 0))
}
func playNoteOn(channel: Int, note: UInt32, midiVelocity: Int) {
let noteCommand = UInt32(0x90 | channel)
checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, noteCommand, note, UInt32(midiVelocity), 0))
}
func playNoteOff(channel: Int, note: UInt32, midiVelocity: Int) {
let noteCommand = UInt32(0x80 | channel)
checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, noteCommand, note, 0, 0))
}
// In the simulator this takes a long time, so we
// call it in a background thread in the controller
func loadSoundFont() {
var bankURL = Bundle.main.url(forResource: "FluidR3_GM", withExtension: "sf2")
checkError(osstatus: AudioUnitSetProperty(synthUnit!, AudioUnitPropertyID(kMusicDeviceProperty_SoundBankURL), AudioUnitScope(kAudioUnitScope_Global), 0, &bankURL, UInt32(MemoryLayout<URL>.size)))
}
func loadPatch(patchNo: Int) {
let channel = UInt32(0)
var enabled = UInt32(1)
var disabled = UInt32(0)
patch = UInt32(patchNo)
checkError(osstatus: AudioUnitSetProperty(
synthUnit!,
AudioUnitPropertyID(kAUMIDISynthProperty_EnablePreload),
AudioUnitScope(kAudioUnitScope_Global),
0,
&enabled,
UInt32(MemoryLayout<UInt32>.size)))
let programChangeCommand = UInt32(0xC0 | channel)
checkError(osstatus: MusicDeviceMIDIEvent(self.synthUnit!, programChangeCommand, patch, 0, 0))
checkError(osstatus: AudioUnitSetProperty(
synthUnit!,
AudioUnitPropertyID(kAUMIDISynthProperty_EnablePreload),
AudioUnitScope(kAudioUnitScope_Global),
0,
&disabled,
UInt32(MemoryLayout<UInt32>.size)))
// the previous programChangeCommand just triggered a preload
// this one actually changes to the new voice
checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, programChangeCommand, patch, 0, 0))
}
func checkError(osstatus: OSStatus) {
if osstatus != noErr {
print(SoundError.GetErrorMessage(osstatus))
}
}
}
谢谢。任何帮助都会得到帮助。