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


17

Фон

MIDI-файли сильно відрізняються від аудіофайлів WAV або MP3. Файли MP3 та WAV містять байти, що представляють собою "запис" аудіо, тоді як файли MIDI мають ряд повідомлень MIDI, що зберігаються в подіях MIDI, інформуючи синтезатор MIDI, який віртуальний інструмент для відтворення або MIDI-секвенсор, темп відтворення, який слід використовувати. Ці повідомлення зберігаються у доріжках, а колекція треків складає послідовність MIDI, події яких можуть бути проаналізовані секвенсером та передавати повідомлення від секвенсера до приймача синтезатора.

Більшу частину часу повідомлення MIDI, що зберігаються в подіях MIDI, - це повідомлення Note On, які сповіщають синтезатор відтворювати певну ноту, або повідомлення Note Off, які сповіщають синтезатор припинити відтворення ноти. Ці повідомлення містять два байти даних, перший з яких інформує синтезатор про швидкість ноти (більша швидкість призводить до більш гучної ноти), а другий з них повідомляє синтезатору грати ноту (тобто середню C). Самі події також містять галочки, які служать для того, щоб сказати секвенсору, коли надсилати повідомлення.

Змагання

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

Ваш вхід може бути чотирма окремими масивами або списками, кожен з яких містить ряд цілих значень; двовимірний масив або список, що містить чотири підмасиви / підсписи з низкою цілих значень; або будь-який інший зручний спосіб; це відображає події MIDI колекції з повідомленнями On 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, примітка 60 (середня C) та швидкість примітки 20; і подія біля галочки 2 із повідомленням, яке має команду Note Off із такою самою приміткою та швидкістю.

Правила

На діаграмі повинні бути цифри від 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 є пісочницею, щоб не дозволяти віддалені запити або локальні файли, тому вам доведеться запустити цей код локально, щоб побачити його в дії. Перший [тести] [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-файл "Ода радістю", який можна використовувати тут, завантажений . Приклад використання:

$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 );
}

Вихід 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 Off замінено номери швидкості, тому слід відкривати більшість стандартних файлів.

Коваджі

Це аж ніяк не повна реалізація формату MIDI, проте я протестував це з досить великою колекцією файлів MIDI, і він їх добре читає.

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


Це приголомшлива відповідь. Озираючись на це завдання, я погоджуюся, що сума бонусу, ймовірно, не зменшить балів, щоб врахувати накладні читання файлу MIDI. (Я думаю, що бонуси сьогодні в будь-якому випадку не відволікаються.) Тим не менш, я дуже вражений, що ви взяли на себе бонус. Я можу дати вам це щедро.
ТНТ

@TNT, дякую! Дійсно насолоджувався тим, що робив це, і було цікаво пробувати підпрограми формату файлів для гольфу для чогось такого густого, як SMF. Великий виклик!
640 КБ

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 раніше, тому я не знаю, чому, хоча. Не могли б ви запропонувати спосіб мені правильно бачити вихід?
ТНТ

Це досить дивно. Це працює для мене. Я зараз не за комп’ютером, але ось скріншот з мого телефону: i.stack.imgur.com/3UCyn.jpg
Йорданія,

Дякую за скріншот. Я думаю, що проблемою може бути веб-браузер, який я використовую, тому я спробую його в іншому пізніше. +1 від мене, хоча. :)
ТНТ

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]

@LeakyNun На жаль, мій поганий
Loovjo

Чи можете ви використовувати регулярну заміну вираження? У Рубі щось подібне str.sub(/(?<=.{20}).{3}/,"foo")до str[20,3] = "foo". Звичайно, це означає побудову регулярного вираження за допомогою рядкової інтерполяції / конкатенації зі змінними індексу / довжини, що дешево в байтах 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.