根据苹果公司的文档,它说:
因为您的MIDIReadProc回调是从一个单独的线程调用的, 使用提供的数据时请注意同步问题 这个回调。
这是否意味着,使用@synchronize为安全做线程阻塞?
或者这确实意味着可能会发生同步时序问题?
我目前正在尝试读取midi文件,并使用MIDIReadProc触发基于midi事件的软件合成器的音符开/关。我需要这是非常可靠和完美的及时。现在,我注意到当我使用这些midi事件并将音频写入缓冲区(全部来自MIDIReadProc)时,时间非常邋and,听起来根本没有。所以我想知道,从MIDIReadProc消费midi事件的“正确”方法是什么?
另外,MIDIReadProc是从midi文件中消费midi事件的唯一选择吗?
关于设置可以由我的合成器直接使用的虚拟端点,还有其他选择吗?如果是这样,那究竟如何运作?
答案 0 :(得分:0)
如果您假定此格式的功能为midiReadProc
,
void midiReadProc(const MIDIPacketList *packetList,
void* readProcRefCon,
void* srcConnRefCon)
{
MIDIPacket *packet = (MIDIPacket*)packetList->packet;
int count = packetList->numPackets;
for (int k=0; k<count; k++) {
Byte midiStatus = packet->data[0];
Byte midiChannel= midiStatus & 0x0F;
Byte midiCommand = midiStatus >> 4;
//parse MIDI messages, extract relevant information and pass it to the controller
//controller must be visible from the midiReadProc
}
packet = MIDIPacketNext(packet);
}
MIDI客户端必须在controller
中声明,解释的MIDI事件从MIDI回调存储到controller
,并在每个音频渲染周期由audioRenderCallback()
读取。通过这种方式,您可以最大限度地减少对时间的不精确
音频缓冲区的长度,您可以在AudioUnit设置期间协商,使其与系统允许的时间一样短。
控制器可以是您定义的@interface myMidiSynthController : NSViewController
,由MIDI通道矩阵和预先确定的每通道最大复音数组成,以及其他相关数据,如界面元素,每个活动的相位累加器语音,AudioComponentInstance
等......基于midiReadProc()
输入调整控制器大小是错误的。 RAM现在很便宜。
我正在使用这样的MIDI回调来处理来自MIDI设备的实时输入。关于MIDI文件的播放,如果你
想要处理任意复杂的流或文件,你也可能会遇到意外。 MIDI标准本身
具有定时功能,可以像MIDI硬件一样工作。一旦将整个文件读入内存,就可以
将您的数据转换为您想要的任何数据,并使用您自己的代码来控制声音合成。
请注意,不要使用任何会阻止音频渲染线程的代码(即在audioRenderCallback()
内),或者对它进行内存管理。
答案 1 :(得分:0)
您可以使用AVAudioEngine.musicSequence
并准备音频单位图表。然后使用MusicSequence API加载您的GM文件。像这样你不需要自己做时间。注意到目前为止我自己还没有这样做,但我理解它应该像这样工作。
在您实例化合成器音频单元后,您将其附加并连接到AVAudioEngine
图表。
这是否意味着,使用@synchronize为安全做线程阻塞?
与你所说的相反是正确的:你当然不应该锁定实时线程。如果资源已被锁定,@synchronized
指令将锁定。您可以考虑对实时线程使用无锁队列。另请参阅Four common mistakes in audio development。
如果你必须使用CoreMIDI和MIDIReadProc
,你可以直接从你的回调中调用MusicDeviceMIDIEvent
将MIDI命令发送到合成器音频单元。