В этом примере показано, как преобразовать обычные файлы MIDI в представление сообщения MIDI с помощью Audio Toolbox™. В этом примере, вас:
Считайте бинарный файл MIDI в рабочую область MATLAB®.
Преобразуйте данные о файле MIDI в midimsg
объекты.
Проигрывайте сообщения MIDI на свою звуковую карту с помощью простого синтезатора.
Для получения дополнительной информации о взаимодействии с MIDI-устройствами с помощью MATLAB, смотрите Интерфейс MIDI-устройства. Чтобы узнать больше о MIDI в целом, консультируйтесь с Ассоциацией Производителей MIDI.
Файлы MIDI содержат сообщения MIDI, время выполнения и метаданные о закодированной музыке. В этом примере показано, как извлечь сообщения MIDI и время выполнения. Чтобы упростить код, этот пример игнорирует метаданные. Поскольку метаданные включают информацию как музыкальный размер и темп, этот пример принимает, что файл MIDI находится в 4/4 время в 120 ударах в минуту (BPM).
Считайте файл MIDI с помощью fread
функция. fread
функция возвращает вектор байтов, представленных как целые числа.
readme = fopen('CmajorScale.mid');
[readOut, byteCount] = fread(readme);
fclose(readme);
midimsg
ОбъектыФайлы MIDI имеют фрагменты заголовка и отслеживают фрагменты. Фрагменты заголовка предоставляют основную информацию, запрошенную, чтобы интерпретировать остальную часть файла. Файлы MIDI всегда запускаются с фрагмента заголовка. Отследите фрагменты, прибывшие после фрагмента заголовка. Фрагменты дорожки предоставляют сообщения MIDI, время выполнения и метаданные файла. Каждый фрагмент дорожки имеет заголовок фрагмента дорожки, который включает длину фрагмента дорожки. Фрагмент дорожки содержит события MIDI после заголовка фрагмента дорожки. Каждое событие MIDI имеет разовое дельтой и сообщение MIDI.
Фрагмент заголовка MIDI включает деление синхронизации файла. Деление синхронизации определяет, как интерпретировать разрешение меток деления в файле MIDI. Метки деления являются модулем времени, используемого, чтобы установить метки времени для файлов MIDI. Файл MIDI с большим количеством меток деления в единицу времени имеет сообщения MIDI с большим количеством гранулированных меток времени. Синхронизация деления не определяет темп. Файлы MIDI задают деление синхронизации или метками деления на четвертную ноту или кадрами в секунду. Этот пример принимает, что деление синхронизации MIDI находится в метках деления на четвертную ноту.
fread
функционируйте читает байт байтом двоичных файлов, но деление синхронизации хранится как 16-битное (2-байтовое) значение. Чтобы оценить несколько байтов как одно значение, используйте polyval
функция. Вектор байтов может быть оценен как полином, где x установлен в 256. Например, вектор байтов [1 2 3]
может быть оценен как:
% Concatenate ticksPerQNote from 2 bytes
ticksPerQNote = polyval(readOut(13:14),256);
Фрагмент дорожки MIDI содержит события MIDI и заголовок. Заголовок фрагмента дорожки содержит длину фрагмента дорожки. Остальная часть фрагмента дорожки содержит одно или несколько событий MIDI.
Все события MIDI имеют два основных компонента:
Разовое дельтой значение — разница во времени в метках деления между предыдущими беговыми соревнованиями MIDI и текущим
Сообщение MIDI — необработанные данные беговых соревнований MIDI
Чтобы проанализировать беговые соревнования MIDI последовательно, создайте цикл в цикле. Во внешнем контуре проанализируйте фрагменты дорожки, выполняющие итерации chunkIndex
. Во внутреннем цикле проанализируйте события MIDI, выполняющие итерации указателем ptr
.
Проанализировать беговые соревнования MIDI:
Считайте разовое дельтой значение в указателе.
Постепенно увеличьте указатель на начало сообщения MIDI.
Читайте MIDI передают и извлекают соответствующие данные.
Добавьте сообщение MIDI в массив сообщения MIDI.
Отобразите массив сообщения MIDI, когда завершенный.
% Initialize values chunkIndex = 14; % Header chunk is always 14 bytes ts = 0; % Timestamp - Starts at zero BPM = 120; msgArray = []; % Parse track chunks in outer loop while chunkIndex < byteCount % Read header of track chunk, find chunk length % Add 8 to chunk length to account for track chunk header length chunkLength = polyval(readOut(chunkIndex+(5:8)),256)+8; ptr = 8+chunkIndex; % Determine start for MIDI event parsing statusByte = -1; % Initialize statusByte. Used for running status support % Parse MIDI track events in inner loop while ptr < chunkIndex+chunkLength % Read delta-time [deltaTime,deltaLen] = findVariableLength(ptr,readOut); % Push pointer to beginning of MIDI message ptr = ptr+deltaLen; % Read MIDI message [statusByte,messageLen,message] = interpretMessage(statusByte,ptr,readOut); % Extract relevant data - Create midimsg object [ts,msg] = createMessage(message,ts,deltaTime,ticksPerQNote,BPM); % Add midimsg to msgArray msgArray = [msgArray;msg]; % Push pointer to next MIDI message ptr = ptr+messageLen; end % Push chunkIndex to next track chunk chunkIndex = chunkIndex+chunkLength; end disp(msgArray)
MIDI message: NoteOn Channel: 1 Note: 60 Velocity: 127 Timestamp: 0 [ 90 3C 7F ] NoteOff Channel: 1 Note: 60 Velocity: 0 Timestamp: 0.5 [ 80 3C 00 ] NoteOn Channel: 1 Note: 62 Velocity: 127 Timestamp: 0.5 [ 90 3E 7F ] NoteOff Channel: 1 Note: 62 Velocity: 0 Timestamp: 1 [ 80 3E 00 ] NoteOn Channel: 1 Note: 64 Velocity: 127 Timestamp: 1 [ 90 40 7F ] NoteOff Channel: 1 Note: 64 Velocity: 0 Timestamp: 1.5 [ 80 40 00 ] NoteOn Channel: 1 Note: 65 Velocity: 127 Timestamp: 1.5 [ 90 41 7F ] NoteOff Channel: 1 Note: 65 Velocity: 0 Timestamp: 1.75 [ 80 41 00 ] NoteOn Channel: 1 Note: 67 Velocity: 127 Timestamp: 2 [ 90 43 7F ] NoteOff Channel: 1 Note: 67 Velocity: 0 Timestamp: 2.5 [ 80 43 00 ] NoteOn Channel: 1 Note: 69 Velocity: 127 Timestamp: 2.5 [ 90 45 7F ] NoteOff Channel: 1 Note: 69 Velocity: 0 Timestamp: 3 [ 80 45 00 ] NoteOn Channel: 1 Note: 71 Velocity: 127 Timestamp: 3 [ 90 47 7F ] NoteOff Channel: 1 Note: 71 Velocity: 0 Timestamp: 3.5 [ 80 47 00 ] NoteOn Channel: 1 Note: 72 Velocity: 127 Timestamp: 3.5 [ 90 48 7F ] NoteOff Channel: 1 Note: 72 Velocity: 0 Timestamp: 3.75 [ 80 48 00 ] NoteOn Channel: 1 Note: 72 Velocity: 127 Timestamp: 4 [ 90 48 7F ] NoteOff Channel: 1 Note: 72 Velocity: 0 Timestamp: 4.5 [ 80 48 00 ] NoteOn Channel: 1 Note: 71 Velocity: 127 Timestamp: 4.5 [ 90 47 7F ] NoteOff Channel: 1 Note: 71 Velocity: 0 Timestamp: 5 [ 80 47 00 ] NoteOn Channel: 1 Note: 69 Velocity: 127 Timestamp: 5 [ 90 45 7F ] NoteOff Channel: 1 Note: 69 Velocity: 0 Timestamp: 5.5 [ 80 45 00 ] NoteOn Channel: 1 Note: 67 Velocity: 127 Timestamp: 5.5 [ 90 43 7F ] NoteOff Channel: 1 Note: 67 Velocity: 0 Timestamp: 5.75 [ 80 43 00 ] NoteOn Channel: 1 Note: 65 Velocity: 127 Timestamp: 6 [ 90 41 7F ] NoteOff Channel: 1 Note: 65 Velocity: 0 Timestamp: 6.5 [ 80 41 00 ] NoteOn Channel: 1 Note: 64 Velocity: 127 Timestamp: 6.5 [ 90 40 7F ] NoteOff Channel: 1 Note: 64 Velocity: 0 Timestamp: 7 [ 80 40 00 ] NoteOn Channel: 1 Note: 62 Velocity: 127 Timestamp: 7 [ 90 3E 7F ] NoteOff Channel: 1 Note: 62 Velocity: 0 Timestamp: 7.5 [ 80 3E 00 ] NoteOn Channel: 1 Note: 60 Velocity: 127 Timestamp: 7.5 [ 90 3C 7F ] NoteOff Channel: 1 Note: 60 Velocity: 0 Timestamp: 7.75 [ 80 3C 00 ] AllNotesOff Channel: 1 Timestamp: 8 [ B0 7B 00 ]
Этот пример игры проанализировал сообщения MIDI с помощью простого монофонического синтезатора. Чтобы видеть демонстрацию этого синтезатора, см. Проект и Игру Синтезатор MIDI.
% Initialize System objects for playing MIDI messages osc = audioOscillator('square', 'Amplitude', 0,'DutyCycle',0.75); deviceWriter = audioDeviceWriter; simplesynth(msgArray,osc,deviceWriter);
Можно также отправить, проанализировал сообщения MIDI к MIDI-устройству с помощью midisend
. Для получения дополнительной информации о взаимодействии с MIDI-устройствами с помощью MATLAB, смотрите Интерфейс MIDI-устройства.
Времена дельты беговых соревнований MIDI хранятся как значения переменной длины. Эти значения 1 - 4 байта длиной со старшим значащим битом каждого байта, служащего флагом. Старший значащий бит итогового байта установлен в 0, и старший значащий бит любого байта установлен в 1.
На беговых соревнованиях MIDI разовое дельтой всегда помещается перед сообщением MIDI. Нет никакого разрыва между разовым дельтой и концом предыдущего события MIDI.
findVariableLength
функционируйте читает значения переменной длины как времена дельты. Это возвращает длину входного значения и самого значения. Во-первых, функция создает 4-байтовый векторный byteStream
, который установлен во все нули. Затем это продвигает указатель на начало события MIDI. Функция проверяет четыре байта после указателя в цикле. Для каждого байта это проверяет старший значащий бит (MSB). Если MSB является нулем, findVariableLength
добавляет байт к byteStream
и выходит из цикла. В противном случае это добавляет байт в byteStream
и продолжается к следующему байту.
Однажды findVariableLength
функция достигает итогового байта значения переменной длины, это оценивает байты, собранные в byteStream
использование polyval
функция.
function [valueOut,byteLength] = findVariableLength(lengthIndex,readOut) byteStream = zeros(4,1); for i = 1:4 valCheck = readOut(lengthIndex+i); byteStream(i) = bitand(valCheck,127); % Mask MSB for value if ~bitand(valCheck,uint32(128)) % If MSB is 0, no need to append further break end end valueOut = polyval(byteStream(1:i),128); % Base is 128 because 7 bits are used for value byteLength = i; end
Существует три основных типа сообщений в файлах MIDI:
Сообщения Sysex — исключительные Системой сообщения проигнорированы этим примером.
Метасобытия — Могут произойти вместо сообщений MIDI, чтобы обеспечить метаданные для файлов MIDI, включая заголовок песни и темп. midimsg
объект не поддерживает метасобытия. Этот пример игнорирует метасобытия.
Сообщения MIDI — Проанализированный этим примером.
Чтобы интерпретировать сообщение MIDI, считайте байт состояния. Байт состояния является первым байтом сообщения MIDI.
Даже при том, что этот пример игнорирует сообщения Sysex и метасобытия, важно идентифицировать эти сообщения и определить их длины. Длины сообщений Sysex и метасобытий являются ключевыми для определения, где следующее сообщение запускается. Сообщения Sysex имеют 'F0'
или 'F7'
когда байт состояния и метасобытия имеют 'FF'
как байт состояния. Сообщения Sysex и метасобытия могут иметь различные длины. После байта состояния сообщения Sysex и метасобытия задают длины события. Значения длины события являются значениями переменной длины как разовые дельтой значения. Длина события может быть определена с помощью findVariableLength
функция.
Для сообщений MIDI длина сообщения может быть определена значением байта состояния. Однако поддержка файлов MIDI рабочее состояние. Если сообщение MIDI имеет тот же байт состояния как предыдущее сообщение MIDI, байт состояния может быть не использован. Если первый байт входящего сообщения не является допустимым байтом состояния, используйте байт состояния предыдущего сообщения MIDI.
interpretMessage
функция возвращает байт состояния, длину и вектор байтов. Байт состояния возвращен во внутренний цикл в случае, если следующее сообщение является под управлением сообщением о состоянии. Длина возвращена во внутренний цикл, где это задает, как далеко продвинуть указатель внутреннего цикла. Наконец, вектор байтов несет необработанные двоичные данные сообщения MIDI. interpretMessage
требует выхода, даже если функция игнорирует данное сообщение. Для сообщений Sysex и метасобытий, interpretMessage
возвращает -1
вместо вектора байтов.
function [statusOut,lenOut,message] = interpretMessage(statusIn,eventIn,readOut) % Check if running status introValue = readOut(eventIn+1); if isStatusByte(introValue) statusOut = introValue; % New status running = false; else statusOut = statusIn; % Running status—Keep old status running = true; end switch statusOut case 255 % Meta-event (FF)—IGNORE [eventLength, lengthLen] = findVariableLength(eventIn+2, ... readOut); % Meta-events have an extra byte for type of meta-event lenOut = 2+lengthLen+eventLength; message = -1; case 240 % Sysex message (F0)—IGNORE [eventLength, lengthLen] = findVariableLength(eventIn+1, ... readOut); lenOut = 1+lengthLen+eventLength; message = -1; case 247 % Sysex message (F7)—IGNORE [eventLength, lengthLen] = findVariableLength(eventIn+1, ... readOut); lenOut = 1+lengthLen+eventLength; message = -1; otherwise % MIDI message—READ eventLength = msgnbytes(statusOut); if running % Running msgs don't retransmit status—Drop a bit lenOut = eventLength-1; message = uint8([statusOut;readOut(eventIn+(1:lenOut))]); else lenOut = eventLength; message = uint8(readOut(eventIn+(1:lenOut))); end end end % ---- function n = msgnbytes(statusByte) if statusByte <= 191 % hex2dec('BF') n = 3; elseif statusByte <= 223 % hex2dec('DF') n = 2; elseif statusByte <= 239 % hex2dec('EF') n = 3; elseif statusByte == 240 % hex2dec('F0') n = 1; elseif statusByte == 241 % hex2dec('F1') n = 2; elseif statusByte == 242 % hex2dec('F2') n = 3; elseif statusByte <= 243 % hex2dec('F3') n = 2; else n = 1; end end % ---- function yes = isStatusByte(b) yes = b > 127; end
midimsg
объект может сгенерировать сообщение MIDI от struct с помощью формата:
midistruct = struct('RawBytes', [144 65 127 0 0 0 0 0], 'Timestamp',1); msg = midimsg.fromStruct(midiStruct)
Это возвращается:
msg = MIDI message: NoteOn Channel: 1 Note: 65 Velocity: 127 Timestamp: 1 [ 90 41 7F ]
createMessage
функция возвращает midimsg
возразите и метка времени. midimsg
объект требует, чтобы его входной struct имел два поля:
RawBytes—
1 8 вектор байтов
Timestamp—
Время в секундах
Установить RawBytes
поле, возьмите вектор байтов, созданных interpretMessage
и добавьте достаточно нулей, чтобы создать 1 8 вектор байтов.
Установить Timestamp
поле, создайте переменную ts
метки времени. Установите
ts
к 0 прежде, чем проанализировать любые фрагменты дорожки. Для каждого отправленного сообщения MIDI преобразуйте разовое дельтой значение от меток деления до секунд. Затем добавьте то значение в ts
. Чтобы преобразовать метки деления MIDI в секунды, используйте:
Где темп находится в микросекундах (μs) на четвертную ноту. Чтобы преобразовать удары в минуту (BPM) в μs на четвертную ноту, используйте:
Если вы заполняете оба поля struct, создаете midimsg
объект. Возвратите midimsg
возразите и модифицированное значение ts
.
createMessage
функция игнорирует сообщения Sysex и метасобытия. Когда interpretMessage
указатели сообщения Sysex и метасобытия, это возвращает -1
вместо вектора байтов. createMessage
функционируйте затем проверяет на то значение. Если createMessage
идентифицирует сообщение Sysex или метасобытие, это возвращает ts
значение это было дано и пустой midimsg
объект.
function [tsOut,msgOut] = createMessage(messageIn,tsIn,deltaTimeIn,ticksPerQNoteIn,bpmIn) if messageIn < 0 % Ignore Sysex message/meta-event data tsOut = tsIn; msgOut = midimsg(0); return end % Create RawBytes field messageLength = length(messageIn); zeroAppend = zeros(8-messageLength,1); bytesIn = transpose([messageIn;zeroAppend]); % deltaTimeIn and ticksPerQNoteIn are both uints % Recast both values as doubles d = double(deltaTimeIn); t = double(ticksPerQNoteIn); % Create Timestamp field and tsOut msPerQNote = 6e7/bpmIn; timeAdd = d*(msPerQNote/t)/1e6; tsOut = tsIn+timeAdd; % Create midimsg object midiStruct = struct('RawBytes',bytesIn,'Timestamp',tsOut); msgOut = midimsg.fromStruct(midiStruct); end
Этот пример игры проанализировал сообщения MIDI с помощью простого монофонического синтезатора. Чтобы видеть демонстрацию этого синтезатора, см. Проект и Игру Синтезатор MIDI.
Можно также отправить, проанализировал сообщения MIDI к MIDI-устройству с помощью midisend
. Для получения дополнительной информации о взаимодействии с MIDI-устройствами с помощью MATLAB, смотрите Интерфейс MIDI-устройства.
function simplesynth(msgArray,osc,deviceWriter) i = 1; tic endTime = msgArray(length(msgArray)).Timestamp; while toc < endTime if toc >= msgArray(i).Timestamp % At new note, update deviceWriter msg = msgArray(i); i = i+1; if isNoteOn(msg) osc.Frequency = note2freq(msg.Note); osc.Amplitude = msg.Velocity/127; elseif isNoteOff(msg) if msg.Note == msg.Note osc.Amplitude = 0; end end end deviceWriter(osc()); % Keep calling deviceWriter as it is updated end end % ---- function yes = isNoteOn(msg) yes = strcmp(msg.Type,'NoteOn') ... && msg.Velocity > 0; end % ---- function yes = isNoteOff(msg) yes = strcmp(msg.Type,'NoteOff') ... || (strcmp(msg.Type,'NoteOn') && msg.Velocity == 0); end % ---- function freq = note2freq(note) freqA = 440; noteA = 69; freq = freqA * 2.^((note-noteA)/12); end