Wyświetl ścieżkę MIDI

17

tło

Pliki MIDI różnią się od plików audio WAV lub MP3. Pliki MP3 i WAV zawierają bajty reprezentujące „nagranie” dźwięku, podczas gdy pliki MIDI zawierają szereg komunikatów MIDI przechowywanych w zdarzeniach MIDI informujących syntezator MIDI, który instrument wirtualny grać lub sekwencer MIDI o tempie, które należy zastosować. Wiadomości te są przechowywane w ścieżkach, a zbiór ścieżek tworzy sekwencję MIDI, której zdarzenia mogą być analizowane przez sekwencer, a ich wiadomości są przesyłane z sekwensera do odbiornika syntezatora.

W większości przypadków komunikaty MIDI przechowywane w zdarzeniach MIDI to komunikaty Note On, które każą syntezatorowi odtworzyć konkretną nutę, lub komunikaty Note Off, które informują syntezator, aby przestał odtwarzać nutę. Komunikaty te zawierają dwa bajty danych, z których pierwszy informuje syntezator o prędkości nuty (wyższa prędkość powoduje, że nuta jest głośniejsza), a drugi informuje syntezator o nucie do odtworzenia (tj. Środkowe C). Same zdarzenia zawierają również tiki, które służą do informowania sekwencera, kiedy wysłać wiadomości.

Wyzwanie

Wyzwanie polega na napisaniu pełnego programu lub funkcji, która analizuje serię komunikatów Note On i Note Off MIDI w jednościeżkowej sekwencji MIDI i wysyła do STDOUT tabelę pokazującą, kiedy poszczególne nuty są włączone, kiedy są wyłączone, oraz prędkość tych nut. Oś pionowa wykresu reprezentuje wartość nuty i powinna być oznaczona tak, jak opisano poniżej, a oś pozioma reprezentuje czas w znacznikach MIDI (chociaż powinna pozostać nieznakowana, aby zmniejszyć problemy ze złożonością i odstępami).

Dane wejściowe mogą być czterema oddzielnymi tablicami lub listami, z których każda zawiera szereg wartości całkowitych; dwuwymiarowa tablica lub lista zawierająca cztery podgrupy / podlisty z serią liczb całkowitych; lub w jakikolwiek inny dogodny sposób; reprezentuje to zdarzenia MIDI kolekcji z komunikatami Note On i Note Off na ścieżce. Wartości w pierwszej z tych tablic określają nutę, drugą prędkość, trzecią nutę przy tiku zdarzenia, a czwartą notę ​​przy tiku zdarzenia. Na przykład, biorąc pod uwagę cztery tablice takie jak te:

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

Analiza pierwszego elementu każdej tablicy daje dwa zdarzenia: zdarzenie przy tyknięciu 0 z komunikatem zawierającym polecenie Note On, nota 60 (środek C) i prędkość nuty 20; oraz zdarzenie przy tyknięciu 2 z komunikatem zawierającym polecenie Note Off z tą samą nutą i prędkością.

Zasady

Na wykresie powinny znajdować się cyfry od 0 do 127 wyświetlane w malejącej kolejności po lewej stronie (reprezentujące wartość nuty), kiedy rozpoczyna się nuta, czas trwania każdej nuty (Uwaga Wyłącz tyknięcie minus Uwaga Włącz tyk) i prędkość nuty. Symbole reprezentujące nuty zależą od ich prędkości:

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

Możesz założyć, co następuje:

  • Wartości nuty i prędkości będą w zakresie [0, 127].
  • Długości każdej z czterech tablic zawsze będą sobie równe.

Oto kilka przykładów:

{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  |

Oto przykład, który wyświetla pierwsze nuty Ode to Joy:

{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  |

Możesz obniżyć swój wynik o 25%, jeśli twoje zgłoszenie przyjmuje rzeczywistą sekwencję MIDI jako dane wejściowe, analizuje komunikaty Note On i Note Off dowolnej wybranej ścieżki, pod warunkiem, że zawiera co najmniej cztery zdarzenia z komunikatami Note On i Note Off, oraz wyniki wykres jak opisano powyżej.

To jest kod golfowy, więc wygrywa najkrótszy kod. Powodzenia!

TNT
źródło

Odpowiedzi:

6

PHP , 127 + 571 = 698 łączny wynik *

Okej, żądam premii. :) Spowoduje to pobranie standardowego pliku MIDI i wyświetlenie wyniku.

Podzieliłem powyższy wynik na główne wyzwanie (analizuj włączanie / wyłączanie nuty i wyświetlaj jako wykres) oraz wyzwanie premiowe (czytaj dane ze standardowego MIDI), aby wyniki były bardziej porównywalne.

Główny: 170 bajtów - 25% = 127

Dla głównego funkcja $d()przyjmuje wymaganą tablicę i wyświetla dane wyjściowe ASCII. Uwzględniono wszystkie testy i dane wyjściowe testowego pliku MIDI poniżej.

$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,"
";}}

Wypróbuj online!

Premia: 761 bajtów - 25% = 571

Funkcja $m()załaduje standardowy plik MIDI (lokalnie lub przez URL) i zwróci tablicę ścieżek, z których każda zawiera tablicę w określonym formacie nut dla wszystkich ścieżek plików 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;};

Zobacz online! Oczywiście TIO jest w piaskownicy, aby nie zezwalać na zdalne żądania lub lokalne pliki, więc będziesz musiał uruchomić ten kod lokalnie, aby zobaczyć go w akcji. Pierwsze [testy] [TIO-jrwa60tu] w funkcji wyświetlania obejmują wynik tablicy z testowego pliku MIDI .

Procedura ładowania pliku MIDI bez klucza:

$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

Testowy plik MIDI „Ode to Joy”, który można pobrać tutaj . Przykładowe zastosowanie:

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

Wyjście pliku MIDI „Oda do radości”

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

Notatki

W formacie MIDI zdarzenia Note On / Note Off mają charakter atomowy, co oznacza, że ​​w określonym momencie dla danej nuty pojawia się zdarzenie Note On (powiedzmy E5), i sugeruje się, że będzie ono odtwarzane aż do zdarzenia Note Off dla innej nuty E5 jest widziany. Dlatego konieczne jest przeanalizowanie zdarzeń MIDI i dopasowanie danej Note On do Note Off, którego kodem jest297184 bajty. Co bardziej komplikuje to, dość powszechne w standardowym formacie MIDI, aby zobaczyć kolejne dopasowanie Note On z prędkością 0 reprezentującą to samo co Note Off.

Spowoduje to teraz prawidłowe odczytanie plików, które mają Note Note o zerowej prędkości zamiast Note Off, więc powinno otwierać większość standardowych plików.

Ostrzeżenia

Nie jest to bynajmniej pełna implementacja formatu MIDI, jednak przetestowałem to z dość obszerną kolekcją plików MIDI i wszystko ładnie czyta.

To przesłanie nie zostało jeszcze poddane ekstremalnej rozgrywce, więc jest całkiem prawdopodobne, że można to zmniejszyć. Myślę, że jest bardzo mało prawdopodobne, aby premia za zmniejszenie wyniku o 25% zrekompensowała kod potrzebny do odczytania standardowego pliku MIDI. Jako (bieżące) najmniejsze zgłoszenie, które właśnie wyświetla ASCII, jest106 65 bajtów, wymagałoby to implementacji procedur plików MIDI 2521 bajtów do pokonania. Rzuciłbym każdemu wyzwanie (bez użycia wbudowanego języka lub modułu). :)

640 KB
źródło
To niesamowita odpowiedź. Patrząc wstecz na to wyzwanie, zgadzam się, że kwota premii prawdopodobnie nie obniży wyników na tyle, aby uwzględnić obciążenie związane z czytaniem pliku MIDI. (Myślę, że premie i tak są obecnie zniechęcane.) Niemniej jestem pod wrażeniem, że podjąłeś wyzwanie premiowe. Mogę dać ci za to dobrą nagrodę.
TNT
@TNT, dzięki! Naprawdę podobało mi się to i interesująca była próba gry w golfa w formacie plików dla czegoś tak głupiego jak SMF. Świetne wyzwanie!
640 KB
5

Rubinowy, 106 bajtów

To było fajne. Nie jestem pewien, dlaczego nikt tego nie próbował.

Ta funkcja przyjmuje dane wejściowe jako cztery argumenty tablicowe i zwraca tablicę ciągów, po jednym dla każdej linii wykresu.

->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}

Uwaga: To arbitralnie zakłada, że ​​nie będzie więcej niż 10 000 tyknięć. Jeśli uruchomisz go w swoim terminalu, sugeruję podłączenie go w celu lessprzewijania w poziomie. Możesz zmienić, 1e4jeśli chcesz więcej tyknięć, aż do 9e9, ale zajmie to terabajt lub dwa RAM.

Zobacz na repl.it: https://repl.it/Cx4I/1

Jordania
źródło
Dzięki za przesłanie! Ale o dziwo nie jestem w stanie zobaczyć danych wyjściowych przy repl (widzę tylko liczby 127-0 z dużą ilością zwrotów między nimi). Nigdy wcześniej nie korzystałem z repl, więc nie wiedziałbym dlaczego. Czy mógłbyś mi zasugerować sposób, aby poprawnie zobaczyć wynik?
TNT
To dość dziwne. Mi to pasuje. Nie jestem teraz przy komputerze, ale oto zrzut ekranu z mojego telefonu: i.stack.imgur.com/3UCyn.jpg
Jordan
Dzięki za zrzut ekranu. Myślę, że problemem może być przeglądarka internetowa, której używam, więc wypróbuję ją później w innej. +1 ode mnie. :)
TNT
2

Python 2, 163 160 156 145 bajtów

To nie jest najlepszy sposób na golfa, ale był to jeden z najprostszych. Gdybym mógł wymyślić, jak zamieniać części ciągów bez przekształcania ich w listy, zastępowania i przekształcania ich z powrotem w ciągi, byłoby to bardzo pomocne w tym przypadku. Sugestie dotyczące gry w golfa mile widziane.

Edycja: 18 bajtów dzięki Dziurawej Zakonnicy. Wypróbuj na 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
źródło
@LeakyNun Whoops, my bad
Loovjo
Czy możesz użyć podstawiania wyrażeń regularnych? W Ruby coś podobnego str.sub(/(?<=.{20}).{3}/,"foo")jest równoważne str[20,3] = "foo". Oczywiście oznacza to konstruowanie wyrażenia regularnego przez interpolację / łączenie łańcucha ze zmiennymi index / length - co jest tanie w bajtach Rubiego, ale może nie w Pythonie.
Jordan
1

Japt , 65 bajtów

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

Wypróbuj online!

Pobiera dane wejściowe jako listę notatek w formacie [pitch, start_tick, end_tick, velocity]. Jeśli przyjmowanie danych wejściowych jako osobnych list jest obowiązkowe (tj. Jedna lista zawierająca wszystkie wysokości, jedna zawierająca wszystkie prędkości itp.), Można to zrobić kosztem 1 bajtu .

Wyjaśnienie:

®Æ"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
Kamil Drakari
źródło