Этот пример показывает, как преобразовать обычные файлы 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 задают деление синхронизации или метками деления на четвертную ноту или кадрами в секунду. Этот пример принимает, что деление синхронизации 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
Чтобы проанализировать беговые соревнования 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-устройства.
Считайте времена Delta
Времена дельты беговых соревнований 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
Существует три основных типа сообщений в файлах 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
Создайте сообщения MIDI
Объект 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