Как правильно пользоваться MIDIReadProc?

Согласно документам Apple, в нем говорится:

Поскольку ваш обратный вызов MIDIReadProc вызывается из отдельного потока, помните о проблемах синхронизации при использовании данных, предоставляемых этим обратным вызовом.

Означает ли это, что для обеспечения безопасности используйте @synchronize?

Или это буквально означает, что могут возникнуть проблемы с синхронизацией?

В настоящее время я пытаюсь прочитать файл midi и использую MIDIReadProc для включения / выключения примечания программного синтезатора на основе событий midi. Мне нужно, чтобы это было очень надежно и точно в срок. Прямо сейчас я замечаю, что когда я использую эти события midi и записываю звук в буфер (все это делается из MIDIReadProc), синхронизация очень неаккуратна и звучит совсем неправильно. Итак, я хотел бы знать, каков «правильный» способ получения миди-событий из MIDIReadProc?

Кроме того, является ли MIDIReadProc единственным вариантом для получения событий midi из файла midi?

Есть ли другой вариант настройки виртуальной конечной точки, которая могла бы использоваться моим синтезатором напрямую? Если да, то как именно это работает?


person patrick    schedule 16.05.2016    source источник


Ответы (2)


Если вы предполагаете, что функция этого формата является 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-события сохраняются в controller из обратного вызова MIDI и читаются audioRenderCallback() в каждом цикле рендеринга звука. Таким образом, вы можете свести к минимуму погрешности синхронизации в длине аудиобуфера, которую вы можете согласовать во время настройки AudioUnit, чтобы она была настолько короткой, насколько это позволяет система.

Контроллер может быть @interface myMidiSynthController : NSViewController, который вы определяете, состоящим из матрицы MIDI-каналов и заранее определенной максимальной полифонии на канал, среди других соответствующих данных, таких как элементы интерфейса, фазовые аккумуляторы для каждого активного голоса, AudioComponentInstance и т. Д. . Было бы неправильно изменять размер контроллера на основе ввода midiReadProc(). Оперативная память сейчас дешевая.

Я использую такие обратные вызовы MIDI для обработки прямого ввода с MIDI-устройств. Что касается воспроизведения файлов MIDI, если вы хотите обрабатывать потоки или файлы произвольной сложности, вы также можете столкнуться с сюрпризами. Стандарт MIDI сам по себе имеет функции синхронизации, которые работают настолько хорошо, насколько позволяет оборудование MIDI. После того, как вы прочитали весь файл в память, вы можете преобразовать свои данные во все, что захотите, и использовать свой собственный код для управления синтезом звука.
Пожалуйста, не используйте какой-либо код, который блокировал бы поток аудио-рендеринга (т.е. внутри audioRenderCallback()) или выполнял бы управление памятью на нем.

person user3078414    schedule 16.05.2016
comment
Но вы не упомянули о том, как передать его контроллеру, чтобы его можно было обрабатывать вне этого специального высокоприоритетного потока midiReadProc. Очевидно, мне нужно сохранить эти пакеты в структуре данных, которую основной поток может использовать и превратить в аудио. Как правильно это сделать? В идеале мне нужен многомерный массив, чтобы я мог делать пакеты [midiChannel] [currentChannelPacketIndex] = * packet ... Но я не знаю, как определить, сколько всего пакетов на данном канале, чтобы я мог правильно распределить память для этого хранилища. - person patrick; 17.05.2016
comment
@patrick, ваше предположение, что основной поток может что-то потреблять и превращать в звук, либо имеет проблему с формулировкой, либо не совсем точное. Основной поток заботится только об обновлениях графического интерфейса пользователя до тех пор, пока производство / потребление звука выполняется в потоке рендеринга в реальном времени, что является наиболее эффективным вариантом для определения времени. Что касается передачи данных, сгенерированных MIDI, в контроллер, если вы просто передадите их как таковые, вам все равно понадобится, чтобы декодировать пакеты во что-то, что будет понимать ваш поток рендеринга. Ваш вопрос довольно широкий, я адаптирую свой ответ как можно скорее. - person user3078414; 17.05.2016
comment
Что касается воспроизведения файлов MIDI, если вы хотите обрабатывать потоки или файлы произвольной сложности, вы также можете столкнуться с сюрпризами. - это моя проблема. Я пытаюсь транслировать midi-файл со скоростью 185 ударов в минуту, где самая быстрая нота - 16-я, и это звучит как абсолютная катастрофа. - person patrick; 17.05.2016
comment
Меня беспокоит то, что время срабатывания события MidiReadProc точное. Я сделал реализацию, в которой я записываю midi-пакеты в свой синтезатор, и в отдельном потоке мой синтезатор опрашивает, ожидая прибытия midi-пакетов, и выполняет отмечать события и выводить все это в буфер. Когда воспроизведение midi-файла завершено, воспроизводится буфер, и это звучит как крушение поезда. Я пробовал много разных реализаций / подходов, и ни один из них, похоже, не работает ............ - person patrick; 18.05.2016
comment
MidiReadProc ничего не стреляет. Предоставленной вами информации недостаточно для вывода. Прочтите этот сообщение SO. Не вызывайте блокирующие функции, такие как printf (), NSLog (), malloc () или вызовы методов Objective C из любого контекста ввода-вывода CoreMIDI, такого как этот обратный вызов ввода - вы вызываете инверсию приоритета. Обязательно прочтите документацию. - person user3078414; 18.05.2016
comment
Если бы вы были открыты для этого, у меня есть приложение на github, которое я сделал, чтобы продемонстрировать мою проблему .. Могу я как-нибудь связаться с вами за пределами отсюда? - person patrick; 18.05.2016
comment
Если вам так нравится, вы можете начать чат здесь. - person user3078414; 18.05.2016
comment
Я открыл новый вопрос, более конкретный ... stackoverflow.com/questions/37328670/ - person patrick; 20.05.2016

Вы можете использовать AVAudioEngine.musicSequence и подготовить график вашего аудиоустройства. Затем используйте MusicSequence, чтобы загрузить файл GM. Таким образом, вам не нужно определять время самостоятельно. Обратите внимание, что я сам пока этого не делал, но теоретически понимаю, что это должно работать следующим образом.

После создания экземпляра аудиоблока синтезатора вы присоединяете и подключаете его к AVAudioEngine графику.

Означает ли это, что для обеспечения безопасности используйте @synchronize?

Верно противоположное тому, что вы сказали: вам, конечно, не следует блокировать поток в реальном времени. Директива @synchronized заблокирует, если ресурс уже заблокирован. Вы можете использовать очереди без блокировки для потоков реального времени. См. Также Четыре распространенные ошибки при разработке аудио.

Если вам нужно использовать CoreMIDI и MIDIReadProc, вы можете отправлять MIDI-команды на аудиоустройство синтезатора, вызывая MusicDeviceMIDIEvent прямо из вашего обратного вызова.

person bio    schedule 18.06.2016