Показать трек MIDI

17

Фон

MIDI-файлы сильно отличаются от аудиофайлов WAV или MP3. Файлы MP3 и WAV содержат байты, представляющие «запись» аудио, в то время как файлы MIDI содержат серию MIDI-сообщений, сохраняемых в событиях MIDI, информирующих синтезатор MIDI, какой виртуальный инструмент следует воспроизводить, или MIDI-секвенсор, темп воспроизведения, который следует использовать. Эти сообщения хранятся в дорожках, и коллекция дорожек составляет MIDI-последовательность, события которой могут быть проанализированы секвенсором, а его сообщения переданы из секвенсора в приемник синтезатора.

В большинстве случаев MIDI-сообщения, хранящиеся в событиях MIDI, представляют собой сообщения Note On, которые говорят синтезатору воспроизводить конкретную ноту, или сообщения Note Off, которые сообщают синтезатору о прекращении воспроизведения ноты. Эти сообщения содержат два байта данных, первый из которых информирует синтезатор о скорости ноты (более высокая скорость приводит к более громкой ноте), а второй из них сообщает синтезатору ноту для воспроизведения (то есть Middle C). Сами события также содержат галочки, которые служат для того, чтобы сообщить секвенсору, когда отправлять сообщения.

Соревнование

Задача состоит в том, чтобы написать полную программу или функцию, которая анализирует серию MIDI-сообщений Note On и Note Off в MIDI-последовательности из одной дорожки и выводит на STDOUT диаграмму, показывающую, когда конкретные ноты включены, когда они выключены, и Скорость этих нот. Вертикальная ось диаграммы представляет значение ноты и должна быть помечена, как описано ниже, а горизонтальная ось представляет время в тиках MIDI (хотя она должна оставаться без метки, чтобы уменьшить сложность и проблемы с пробелами).

Ваш ввод может быть четырьмя отдельными массивами или списками, каждый из которых содержит серию целочисленных значений; двумерный массив или список, содержащий четыре подмассива / подсписка с серией целочисленных значений; или любым другим удобным способом; Это представляет собой набор MIDI-событий с сообщениями Note On и Note Off на дорожке. Значения в первом из этих массивов указывают ноту, вторую скорость, третью отметку о событии и четвертую отметку о событии. Например, с учетом четырех массивов, таких как эти:

{60, 62, 64, 65,  67}
{20, 40, 60, 80, 100}
{ 0,  4,  8, 12,  16}
{ 2,  6, 10, 14,  18}

Анализ первого элемента каждого массива дает два события: событие на отметке 0 с сообщением, содержащим команду Note On, note 60 (Middle C) и скорость ноты 20; и событие на отметке 2 с сообщением, которое имеет команду «Отключить ноту» с той же нотой и скоростью.

правила

Диаграмма должна содержать цифры от 0 до 127, отображаемые в порядке убывания слева (представляющие значение заметки), когда начинается заметка, продолжительность каждой заметки (отметка «Откл. Нота» минус отметка «Примечание на пометке») и скорость заметки. Символы, представляющие ноты, зависят от их скорости:

  • 0-15: O
  • 16-31: =
  • 32-47: #
  • 48-63: -
  • 64-79: @
  • 80-95: +
  • 96-111: 0
  • 112-127: *

Вы можете предположить следующее:

  • Значения для ноты и скорости будут в пределах диапазона [0, 127].
  • Длины каждого из четырех массивов всегда будут равны друг другу.

Вот несколько примеров:

{60, 62, 64, 65,  67}
{20, 40, 60, 80, 100}
{ 0,  4,  8, 12,  16}
{ 2,  6, 10, 14,  18}

127|
126|
125|
...
67 |                00
66 |
65 |            ++
64 |        --
63 |
62 |    ##
61 |
60 |==
59 |
...
2  |
1  |
0  |


{60, 48, 62, 47, 64, 45,  65,  43, 67, 41, 65, 43, 64, 45,  62, 47, 60, 48}
{63, 31, 75, 90, 12, 23, 122, 104, 33, 19, 57, 42,  5, 82, 109, 86, 95, 71}
{0,   0,  2,  2,  4,  4,   6,   6,  8,  8, 10, 10, 12, 12,  14, 14, 16, 16}
{2,   2,  4,  4,  6,  6,   8,   8, 10, 10, 12, 12, 14, 14,  16, 16, 18, 18}

127|
126|
...
68 |
67 |        ##
66 |
65 |      **  --
64 |    OO      OO
63 |
62 |  @@          00
61 |
60 |--              ++
59 |
...
49 |
48 |==              @@
47 |  ++          ++
46 |
45 |    ==      ++
44 |
43 |      00  ##
42 |
41 |        ==
40 |
...
1  |
0  |

Вот пример, который отображает первые несколько нот «Ода радости»:

{48, 55, 64, 64, 65, 67, 55, 67, 65, 64, 62, 52, 55,  60,  60,  62,  64,  55, 64, 62, 62}
{45, 45, 63, 63, 63, 63, 89, 66, 66, 66, 66, 30, 30, 103, 103, 103, 103, 127, 55, 55, 55}
{ 0,  0,  0,  4,  8, 12, 16, 16, 20, 24, 28, 32, 32,  32,  36,  40,  44,  48, 48, 54, 56}
{16, 16,  2,  6, 10, 14, 32, 18, 22, 26, 30, 48, 48,  34,  38,  42,  46,  64, 50, 55, 64}

127|
...
67 |            --  @@
66 |
65 |        --          @@
64 |--  --                  @@                  00  --
63 |
62 |                            @@          00            - --------
61 |
60 |                                00  00
59 |
58 |
57 |
56 |
55 |################++++++++++++++++================****************
54 |
53 |
52 |                                ================
51 |
50 |
49 |
48 |################
...
0  |

Вы можете уменьшить свой счет на 25% если ваша подача принимает фактическую MIDI-последовательность в качестве входных данных, анализирует сообщения Note On и Note Off любой дорожки по вашему выбору при условии, что она содержит как минимум четыре события с сообщениями Note On и Note Off, и выводит диаграмма, как описано выше.

Это код гольф, поэтому выигрывает самый короткий код. Удачи!

тротил
источник

Ответы:

6

PHP , 127 + 571 = 698 баллов *

Хорошо, я требую бонус. :) Это займет стандартный файл MIDI и отобразит вывод.

Я разбил счет выше на основной вызов (анализ вкл / выкл ноты и отображение в виде графика) и бонусный вызов (чтение входных данных из стандартного MIDI), чтобы сделать результаты более сопоставимыми.

Основной: 170 байт - 25% = 127

Для основного, функция $d()берет требуемый массив и отображает вывод ASCII. Включены все тесты и вывод тестового файла MIDI ниже.

$d=function($a){for($l=max($n=$a[0]);$l>=min($n);){$r=' |';foreach($n as$c=>$e)while($e==$l&$a[2][$c]<$a[3][$c])$r[++$a[2][$c]+1]='O=#-@+0*'[$a[1][$c]/16];echo$l--,$r,"
";}}

Попробуйте онлайн!

Бонус: 761 байт - 25% = 571

Функция $m()загрузит стандартный файл MIDI (локально или по URL-адресу) и вернет массив дорожек, каждая из которых содержит массив в указанном формате нот для всех дорожек файла MIDI.

$m=function($f){$a=function($f){do$s=($s<<7)+(($c=unpack(C,fread($f,1))[1])&127);while($c&128);return$s;};$r=function($n){foreach($n as$e){if($e[4]==9&&$e[1]>0)foreach($n as$y=>$f)if($f[0]==$e[0]&&($f[4]==8||($f[4]==9&&$f[1]==0))){$o[0][]=$e[0];$o[1][]=$e[1];$o[2][]=$e[2];$o[3][]=$f[2];$n[$y][4]=0;break;}}return$o;};$m=fopen($f,r);while($b=fread($m,8)){$z=unpack(N2,$b)[2];if($b[3]==d){$k=unpack(n3,fread($m,$z))[3]/4;}else{$t=0;$n=[];$d=ftell($m)+$z;while(ftell($m)<$d){$t+=$a($m);if(($e=unpack(C,fread($m,1))[1])==255){fread($m,1);if($w=$a($m))fread($m,$w);}else{if($e>127)list(,$e,$h)=unpack('C*',fread($m,($y=(240&$e)>>4)==12?1:2));else$h=unpack(C,fread($m,1))[1];if($y==9|$y==8)$n[]=[$e,$h,(int)round($t/$k),0,$y];}}if($n)$u[]=$r($n);}}fclose($m);return$u;};

Смотрите это онлайн! Очевидно, что TIO находится в «песочнице», чтобы не разрешать удаленные запросы или локальные файлы, поэтому вам придется запускать этот код локально, чтобы увидеть его в действии. Первые [tests] [TIO-jrwa60tu] в функции дисплея включают в себя массив результатов тестового MIDI-файла .

Процедура загрузки MIDI-файла разжата:

$m=fopen($f,'r');                           // m = midi file handle
while($b=fread($m,8)){                      // read chunk header
    $z=unpack('N2',$b)[2];                  // z = current chunk size
    if($b[3]=='d'){                         // is a header chunk?
        $k=unpack('n3',fread($m,$z))[3]/4;  // k = ticks per quarter note (you can change the 4 to 8 or 16 to "zoom in" so each char represents eights or sixteenth notes)
    }else{                                  // is a track chunk?
        $t=0;                               // track/chunk time offset starts at 0
        $d=ftell($m)+$z;                    // d = end of chunk file pos
        while(ftell($m)<$d){                // q = current file pos
            $t+=$a($m);                     // decode var length for event offset and add to current time
            if(($e=unpack('C',fread($m,1))[1])==255){ // is a META event 
                fread($m,1);                // read and discard meta event type
                if($w=$a($m))
                    fread($m,$w);
            }else{                          // is a MIDI event
                if($e>127) {                // is a new event type
                    list(,$e,$h)=unpack('C*',  // if is a prog change (0x0c), event is 1 byte
                        fread($m,($y=(240&$e)>>4)==12?1:2)); // otherwise read 2 bytes
                } else {                    // is a MIDI "streaming" event, same type as last
                    $h=unpack('C',fread($m,1))[1];
                }
                if($y==9|$y==8)             // if is a Note On or Note Off
                    $n[]=[$e,$h,(int)round($t/$k),0,$y];  // add note to output
            }
        }
        if($n)                              // if this track has notes,
            $u[]=$r($n);                    // add to array of output tracks ($u)
    }
}
fclose($m); // yes, could golf this out and rely on PHP GC to close it

Загрузите здесь тестовый MIDI-файл "Ode to Joy", который можно использовать здесь. . Пример использования:

$d( $m( 'beethoven_ode_to_joy.mid' )[0] );      // display first track

$d( $m( 'https://www.8notes.com/school/midi/piano/beethoven_ode_to_joy.mid' )[0] );

foreach( $m( 'multi_track_song.mid' ) as $t ) {  // display all tracks
    $d( $t );
}

"Ode to Joy" выходной файл MIDI

67 |            0000++++                                                        00000000                                                                                                                        00000000
66 |
65 |        0000        ++++                                                0000        0000                                                              @@              @@                                0000        ++++
64 |++++++++                ++++                0000000000          00000000                0000                0000                        @@@@        @@  ----        @@  ----                ++++++++++++                ++++                0000
63 |
62 |                            ++++        0000          00++++++++                            ++++        0000    000000          @@@@----        ----            @@@@        ----    ----                                    ++++        0000    000000
61 |
60 |++++                            ++++0000                        0000                            ++++0000              ++00000000            ----            ----                ----            00000000                        ++++0000    ****      ++00000000
59 |                                                        ++++++++
58 |                                                                                                                                                                                                        00000000
57 |                                                                                                                                                                                ----                            ++++++++
56 |                                                                                                                                                                        --------
55 |++++++++++++++++++++++++00000000000000000000000000000000++++++++00000000000000000000000000000000000000000000000000000000        ----------------------------------------                --------                                        0000    ++++++++00000000
54 |                                                                                                                                                                                    ----
53 |                                                                                                                                                                                                                        ++++++++
52 |                                0000000000000000                                                0000000000000000                                                                                                                ++++0000                00000000
51 |
50 |
49 |
48 |++++++++++++++++                0000000000000000                0000000000000000                0000000000000000        ++++++++                                                                                                                        00000000

Примечания

В формате MIDI события Note On / Note Off являются атомарными, что означает, что вы видите событие Note On в определенное время для данной ноты (скажем, E5), и подразумевается, что оно будет воспроизводиться до события Note Off для другой ноты E5. виден. Таким образом, необходимо проанализировать события MIDI и сопоставить данное Note On с его Note Off, код которого для этого является297184 байта. Еще больше усложняя это, в стандартном формате MIDI довольно часто можно увидеть последующее сопоставление Note On со скоростью 0, представляющей то же самое, что и Note Off.

Теперь это будет правильно читать файлы, которые имеют Note On с нулевой скоростью вместо Note Off, поэтому следует открывать большинство стандартных файлов.

Предостережения

Это ни в коем случае не полная реализация формата MIDI, однако я проверил это с довольно обширной коллекцией файлов MIDI, и он хорошо их читает.

Это представление еще не дошло до крайности, поэтому вполне вероятно, что это можно сделать меньше. Я действительно думаю, что очень маловероятно, что бонус снижения на 25% сместит код, необходимый для чтения стандартного файла MIDI. Поскольку (текущее) наименьшее представление, которое просто отображает ASCII,106 65 байт, это потребует, чтобы подпрограммы MIDI-файла были реализованы в 2521 байт. Я хотел бы, чтобы кто-нибудь сделал это (без использования встроенного языка или модуля). :)

640 КБ
источник
Это потрясающий ответ. Оглядываясь назад на эту проблему, я согласен с тем, что сумма бонуса, вероятно, не уменьшит количество баллов, достаточное для учета накладных расходов на чтение файла MIDI. (Я думаю, что бонусы в настоящее время не поощряются.) Тем не менее, я очень впечатлен тем, что вы приняли бонусное испытание. Я мог бы дать вам хорошую награду за это.
TNT
@ ТНТ, спасибо! Очень понравилось это делать, и было интересно пробовать подпрограммы для форматирования файлов в гольф для чего-то столь же глупого, как SMF. Отличный вызов!
640KB
5

Рубин, 106 байт

Это было весело Я не уверен, почему никто не пытался это сделать.

Эта функция принимает входные данные в виде четырех аргументов массива и возвращает массив строк, по одной для каждой строки диаграммы.

->a,*r{q=(0..z=127).map{|i|"%3d|"%(z-i)+" "*1e4}
a.zip(*r){|n,v,o,f|q[z-n][o+4]="O=#-@+0*"[v/16]*(f-o)}
q}

Замечания: это произвольно предполагает, что будет не более 10 000 тиков. Если вы запускаете его в своем терминале, я предлагаю подключить его к трубке, lessчтобы вы могли прокручивать его горизонтально. Вы можете изменить, 1e4если хотите больше тиков, вплоть до 9e9, но это займет терабайт или два ОЗУ.

Смотрите его на repl.it: https://repl.it/Cx4I/1

Иордания
источник
Спасибо за представление! Но как ни странно, я не могу увидеть вывод на repl (все, что я вижу, это числа 127-0 с большим количеством возвратов между ними). Я никогда раньше не использовал repl, поэтому не знаю почему. Не могли бы вы предложить мне способ увидеть результат правильно?
TNT
Это довольно странно. Меня устраивает. Я сейчас не за компьютером, но вот скриншот с моего телефона: i.stack.imgur.com/3UCyn.jpg
Джордан,
Спасибо за скриншот. Я думаю, что проблема может быть в веб-браузере, который я использую, поэтому позже я попробую его в другом. +1 от меня, хотя. :)
TNT
2

Python 2 163 160 156 145 байт

Это не самый лучший способ сделать это, но он был одним из самых простых. Если бы я мог понять, как заменить части строк, не превращая их в списки, не заменяя и не превращая их обратно в строки, это было бы очень полезно здесь. Предложения по игре в гольф приветствуются.

Изменить: 18 байтов благодаря Leaky Nun. Попробуйте это на Ideone !

a=input();z=[" "*max(a[3])]*128
for n,v,b,e in zip(*a):z[n]=z[n][:b]+"O=#-@+0*"[v/16]*(e-b)+z[n][e:]
for i in range(128)[::-1]:print"%3d|"%i+z[i]
Sherlock9
источник
@LeakyNun Ой, мой плохой
Loovjo
Можете ли вы использовать подстановку регулярных выражений? В Ruby что-то вроде str.sub(/(?<=.{20}).{3}/,"foo")эквивалентно str[20,3] = "foo". Конечно, это означает создание регулярного выражения путем интерполяции / конкатенации строк с переменными index / length - что дешево в байтах Ruby, но, возможно, не в Python.
Джордан,
1

Japt , 65 байт

®Æ"O=#-@+0*"gXzG
#€Çs ú3 +'|ÃúUmg2 rÔ+5
£VhXÎVgXv)hXÎ+4Xo pXra
Vw

Попробуйте онлайн!

Вводит в виде списка заметок в формате [pitch, start_tick, end_tick, velocity]. Если ввод данных в виде отдельных списков является обязательным (т. Е. Один список, содержащий все высоты, один, содержащий все скорости и т. Д.), Это может быть достигнуто за счет 1 байта .

Объяснение:

®Æ"O=#-@+0*"gXzG          #Gets the velocity character to use for each note
®                         # For each note in the input
 Æ                        # Replace the last item X with:
             XzG          #  Integer divide X by 16
  "O=#-@+0*"g             #  Get the character at that index in the string "O=#-@+0*"

#€Çs ú3 +'|ÃúUmg2 rÔ+5    #Generate the blank chart
#€Ç        à              # For each number X in the range [0...127]:
   s                      #  Turn X into a string
     ú3                   #  Right-pad with spaces until it is 3 characters long
        +'|               #  Add "|" to the end
            ú             # Right pad each of those with spaces to this length:
             Umg2         #  Get all the end_tick values
                  rÔ      #  Find the largest one
                    +5    #  Add 5

£VhXÎVgXv)hXÎ+4Xo pXra    #Put the notes into the chart
£                         # For each note:
     VgXv)                #  Get a line from the chart based on the note's pitch
          h               #  Overwrite part of that line:
           XÎ+4           #   Starting at index start_tick +4
               Xo         #   Overwrite characters with the velocity character
                  pXra    #   For the next end_tick - start_tick characters
 VhXÎ                     #  Put the modified line back into the chart

Vw                        #Print the chart
V                         # Get the chart
 w                        # Reverse it (so 127 is the first line)
                          # Implicitly print it
Камил Дракари
источник