Visualizza una traccia MIDI


17

sfondo

I file MIDI sono abbastanza diversi dai file audio WAV o MP3. I file MP3 e WAV contengono byte che rappresentano una "registrazione" dell'audio, mentre i file MIDI hanno una serie di messaggi MIDI memorizzati in eventi MIDI che informano un sintetizzatore MIDI quale strumento virtuale riprodurre o un sequencer MIDI il tempo di riproduzione che dovrebbe essere usato. Questi messaggi sono memorizzati in tracce e una raccolta di tracce costituisce una sequenza MIDI, i cui eventi possono essere analizzati da un sequencer e trasmessi i suoi messaggi dal sequencer al ricevitore di un sintetizzatore.

Il più delle volte i messaggi MIDI memorizzati negli eventi MIDI sono messaggi Note On che indicano al sintetizzatore di suonare una nota particolare o messaggi Note Off che dicono al sintetizzatore di interrompere l'esecuzione della nota. Questi messaggi contengono due byte di dati, il primo dei quali informa il sintetizzatore della velocità della nota (una velocità più elevata produce una nota più forte) e il secondo indica al sintetizzatore la nota da suonare (ovvero il C centrale). Gli eventi stessi contengono anche segni di spunta che servono allo scopo di dire al sequencer quando inviare i messaggi.

La sfida

La sfida è scrivere un programma completo o una funzione che analizzi una serie di messaggi MIDI Note On e Note Off in una sequenza MIDI a traccia singola e trasmetta a STDOUT un grafico che mostri quando sono attive determinate note, quando sono spente, e il velocità di queste note. L'asse verticale del grafico rappresenta il valore della nota e deve essere etichettato come descritto di seguito, mentre l'asse orizzontale rappresenta il tempo in tick MIDI (anche se dovrebbe rimanere senza etichetta per ridurre la complessità e i problemi di spaziatura).

I dati immessi possono essere quattro matrici o elenchi separati, ciascuno contenente una serie di valori interi; una matrice o lista bidimensionale contenente quattro sotto-matrici / sotto-liste con una serie di valori interi; o qualsiasi altro mezzo conveniente; questo rappresenta gli eventi MIDI della raccolta con i messaggi Note On e Note Off nella traccia. I valori nel primo di questi array specificano la nota, la seconda la velocità, la terza la nota sul tick dell'evento e la quarta il tick dell'evento sulla nota. Ad esempio, dati quattro array come questi:

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

Analizzando il primo elemento di ciascun array si ottengono due eventi: un evento con tick 0 con un messaggio che ha un comando Note On, nota 60 (Middle C) e una velocità nota di 20; e un evento al segno di spunta 2 con un messaggio che ha un comando Note Off con la stessa nota e velocità.

Regole

Il grafico dovrebbe presentare i numeri da 0 a 127 visualizzati in ordine decrescente sul lato sinistro (che rappresenta il valore della nota), quando inizia la nota, la durata di ciascuna nota (nota disattivata meno Nota su attiva) e la velocità della nota. I simboli che rappresentano le note dipendono dalla loro velocità:

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

Puoi assumere quanto segue:

  • I valori per nota e velocità saranno compresi nell'intervallo [0, 127].
  • Le lunghezze di ciascuna delle quattro matrici saranno sempre uguali tra loro.

Ecco alcuni esempi:

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

Ecco un esempio che mostra le prime note di 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  |

Puoi ridurre il tuo punteggio del 25% se l'invio prende una sequenza MIDI effettiva come input, analizza i messaggi Note On e Note Off di qualsiasi traccia di tua scelta purché contenga almeno quattro eventi con messaggi Note On e Note Off e output un grafico come descritto sopra.

Questo è il codice golf, quindi vince il codice più breve. In bocca al lupo!

Risposte:


6

PHP , 127 + 571 = 698 punteggio totale *

Ok, sto richiedendo il bonus. :) Questo richiederà un file MIDI standard e visualizzerà l'output.

Ho suddiviso il punteggio sopra nella sfida principale (analizza la nota on / off e visualizza come grafico) e la sfida bonus (leggi l'input dal MIDI standard) per rendere i punteggi più comparabili.

Principale: 170 byte - 25% = 127

Per il principale, la funzione $d() prende l'array richiesto e visualizza l'output ASCII. Di seguito sono inclusi tutti i test e l'output del file MIDI di test.

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

Provalo online!

Bonus: 761 byte - 25% = 571

La funzione $m()caricherà un file MIDI standard (localmente o tramite URL) e restituirà un array di tracce, ognuna contenente un array nel formato di nota specificato per tutte le tracce del file 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;};

Guardalo online! Ovviamente TIO è in modalità sandbox per non consentire richieste remote o file locali, quindi dovrai eseguire questo codice localmente per vederlo in azione. Il primo [test] [TIO-jrwa60tu] nella funzione di visualizzazione include il risultato dell'array dal file MIDI di test .

Routine di caricamento di file MIDI non modificata:

$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

Un file MIDI di prova di "Ode to Joy" che può essere utilizzato qui scaricato . Esempio di utilizzo:

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

Uscita file MIDI "Ode to Joy"

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

Appunti

Nel formato MIDI, gli eventi Note On / Note Off sono atomici, il che significa che si vede un evento Note On in un determinato momento per una data nota (diciamo E5), ed è implicito che suonerà fino a un evento Note Off per un'altra nota E5 è visto. Come tale, è necessario analizzare gli eventi MIDI e abbinare una nota Note On alla sua Note Off, che è il codice per quello297184 byte. A complicare ulteriormente questo, è abbastanza comune nel formato MIDI standard vedere una successiva Note On corrispondente con una velocità 0 che rappresenta la stessa cosa di una Note Off.

Questo ora leggerà correttamente i file che hanno Note On a velocità zero anziché Note Off, quindi dovrebbe aprire la maggior parte dei file standard.

Avvertenze

Non si tratta affatto di un'implementazione completa del formato MIDI, tuttavia l'ho provato con una raccolta abbastanza estesa di file MIDI e li legge bene.

Questo invio non è stato ancora raggiunto il limite estremo, quindi è molto probabile che questo possa essere ridotto. Penso sia molto improbabile che il bonus di riduzione del punteggio del 25% compensi il codice necessario per leggere un file MIDI standard. Come il più piccolo (attuale) invio che fa solo il display ASCII è106 65 byte, richiederebbe l'implementazione delle routine di file MIDI 2521 byte da battere. Sfiderei chiunque a farlo (senza usare un modulo o un linguaggio integrato). :)


Questa è una risposta fantastica. Guardando indietro a questa sfida, concordo sul fatto che l'ammontare del bonus probabilmente non ridurrà i punteggi abbastanza da tenere conto dell'overhead della lettura di un file MIDI. (Penso che i bonus siano comunque scoraggiati al giorno d'oggi.) Tuttavia sono molto colpito dal fatto che tu abbia accettato la sfida bonus. Potrei darti una buona taglia per questo.
TNT,

@TNT, grazie! Mi è davvero piaciuto farlo ed è stato interessante provare a golfare routine di formato file per qualcosa di sciocco come SMF. Grande sfida!
640 KB

5

Rubino, 106 byte

È stato divertente. Non sono sicuro del perché nessuno ci abbia provato.

Questa funzione accetta input come quattro argomenti di matrice e restituisce una matrice di stringhe, una per ogni riga del grafico.

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

Nota: questo presuppone arbitrariamente che non ci saranno più di 10.000 tick. Se lo esegui nel tuo terminale, ti suggerisco di collegarlo in lessmodo da poter scorrere in orizzontale. Puoi cambiare 1e4se vuoi più tick, fino a quel momento 9e9, ma ciò richiederà un terabyte o due di RAM.

Guardalo su repl.it: https://repl.it/Cx4I/1


Grazie per l'invio! Ma stranamente non riesco a vedere l'output su repl (tutto quello che posso vedere sono i numeri 127-0 con molti ritorni tra di loro). Non ho mai usato sostituire prima quindi non saprei perché, però. Potresti suggerirmi un modo per vedere correttamente l'output?
TNT,

È abbastanza strano. Per me funziona. Al momento non sono al computer, ma ecco uno screenshot dal mio telefono: i.stack.imgur.com/3UCyn.jpg
Giordania,

Grazie per lo screenshot Sto pensando che il problema potrebbe essere il browser web che sto usando, quindi lo proverò in uno diverso in seguito. +1 da me però. :)
TNT,

2

Python 2, 163 160 156 145 byte

Questo non è il modo più golfoso per farlo, ma è stato uno dei più semplici. Se potessi capire come sostituire parti di stringhe senza trasformarle in elenchi, sostituirle e trasformarle in stringhe, ciò sarebbe molto utile qui. Suggerimenti di golf benvenuti.

Modifica: 18 byte grazie a Leaky Nun. Provalo su 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 Whoops, my bad
Loovjo,

Puoi usare la sostituzione delle espressioni regolari? In Ruby qualcosa di simile str.sub(/(?<=.{20}).{3}/,"foo")equivale a str[20,3] = "foo". Ovviamente, ciò significa costruire la regexp mediante interpolazione / concatenazione di stringhe con le variabili index / length - che è economico in byte Ruby, ma forse non in Python.
Giordania,

1

Japt , 65 byte

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

Provalo online!

Accetta input come un elenco di note nel formato [pitch, start_tick, end_tick, velocity]. Se è obbligatorio inserire input come liste separate (cioè una lista contenente tutte le altezze, una contenente tutte le velocità ecc.), Che può essere realizzata al costo di 1 byte .

Spiegazione:

®Æ"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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.