多轨音频播放 C++ / PortAudio

Multi-track audio playback C++ / PortAudio

我正在开发一个 synthesizer/live-coding 应用程序,我想让引擎的多个实例生成具有不同序列的不同声音。 (旁白:我的合成器引擎使用 MIDI 输入)。 假设用户对控制台的输入可能如下所示:

track:1,sound:pad,seq:[70, sleep 0.25, 77, sleep 0.5]
track:2,sound:bass,seq:[30, sleep 0.125, 30, sleep 0.125, 31, sleep 0.5]
play

我怎样才能将这两组事件的时间与正确的睡眠交织在一起?

我觉得必须有某种方法来同步这两个系列的事件,我不知道答案是多线程还是其他一些同步机制。我应该关注哪个编程领域?如果这个问题不清楚或完全天真,我们深表歉意。

例如,我几乎可以肯定以下内容不会像我想的那样:

# after issuing play command, the following events are generated, which clearly does not interleave these timing events
while (true) {
    stream.noteOn(70, track1);
    bpmSleep(0.25, track1); // beats
    stream.noteOff(70, track1);
    stream.noteOn(77, track1);
    bpmSleep(0.5, track1);
    stream.noteOff(77, track1);
    stream.noteOn(30, track2);
    bpmSleep(0.125, track2);
    stream.noteOff(30, track2);
    // etc.
}

将您的输入数据转换为与 MIDI 相同的表示形式:事件类型、轨道、参数、时间。然后将两个轨道按时间排序,并处理所有事件:抓取下一个事件,休眠直到该事件应该发生的时间,重复。

这就是真正的 MIDI。计划的事件表示。在 MIDI 中,NOTE ON 是一个完全独立于 NOTE OFF 的事件,因此事件甚至没有持续时间。如果您将每个轨道想象成一系列离散事件,那么您需要做的就是确保每个事件都有数据以了解它属于哪个轨道,并且您可以在一个队列中处理它们。

请注意,睡眠不需要轨道。这是事件的缺失,而不是事件本身。另请注意,您甚至不需要为此使用两个频道。同频道播放多个语音很常见

// pseudocode
struct event {
    enum {NOTE_ON, NOTE_OFF} event_type;
    int note;
};

while(true) {
    ev = events.pop();
    bpmSleep(ev->time - now);
    if(ev->event_type == NOTE_ON)
       stream.noteOn(ev->note, ev->channel);
    else if(next_event->event_type == NOTE_OFF)
       stream.noteOff(ev->note, ev->channel);
}