Mostrar una pista MIDI

17

Antecedentes

Los archivos MIDI son bastante diferentes de los archivos de audio WAV o MP3. Los archivos MP3 y WAV contienen bytes que representan una "grabación" del audio, mientras que los archivos MIDI tienen una serie de mensajes MIDI almacenados en eventos MIDI que informan a un sintetizador MIDI qué instrumento virtual tocar o un secuenciador MIDI el tempo de reproducción que se debe usar. Estos mensajes se almacenan en pistas, y una colección de pistas forma una secuencia MIDI, cuyos eventos pueden ser analizados por un secuenciador y transmitir sus mensajes desde el secuenciador al receptor de un sintetizador.

La mayoría de las veces los mensajes MIDI almacenados en eventos MIDI son mensajes Note On que le dicen al sintetizador que toque una nota en particular, o mensajes Note Off que le dicen al sintetizador que deje de tocar la nota. Estos mensajes contienen dos bytes de datos, el primero de los cuales informa al sintetizador de la velocidad de la nota (la mayor velocidad da como resultado una nota más alta), y el segundo le dice al sintetizador la nota que debe tocar (es decir, C central). Los eventos en sí también contienen marcas que tienen el propósito de decirle al secuenciador cuándo enviar los mensajes.

El reto

El desafío es escribir un programa completo o una función que analice una serie de mensajes MIDI Note On y Note Off en una secuencia MIDI de una sola pista y envíe a STDOUT un gráfico que muestre cuándo están activadas las notas particulares, cuándo están desactivadas y velocidad de estas notas. El eje vertical del gráfico representa el valor de la nota y debe etiquetarse como se describe a continuación, y el eje horizontal representa el tiempo en tics MIDI (aunque debe permanecer sin etiquetar para reducir la complejidad y los problemas de espacio).

Su entrada puede ser cuatro matrices o listas separadas, cada una con una serie de valores enteros; una matriz o lista bidimensional que contiene cuatro submatrices / sublistas con una serie de valores enteros; o cualquier otro medio conveniente; esto representa la colección de eventos MIDI con mensajes Note On y Note Off en la pista. Los valores en la primera de estas matrices especifican la nota, la segunda la velocidad, la tercera la nota en el tic del evento y la cuarta la nota en el tic del evento. Por ejemplo, dados cuatro arreglos como estos:

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

Al analizar el primer elemento de cada matriz se obtienen dos eventos: un evento en la marca 0 con un mensaje que tiene un comando Note On, nota 60 (C central) y velocidad de nota de 20; y un evento en la marca 2 con un mensaje que tiene un comando Note Off con la misma nota y velocidad.

Reglas

El gráfico debe presentar los números del 0 al 127 que se muestran en orden decreciente en el lado izquierdo (que representa el valor de la nota), cuando comienza la nota, la duración de cada nota (nota desactivada tick menos nota activada tick) y la velocidad de la nota. Los símbolos que representan las notas dependen de su velocidad:

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

Puede asumir lo siguiente:

  • Los valores para nota y velocidad estarán dentro del rango [0, 127].
  • Las longitudes de cada una de las cuatro matrices siempre serán iguales entre sí.

Aquí están algunos ejemplos:

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

Aquí hay un ejemplo que muestra las primeras notas de 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  |

Puedes reducir tu puntaje en un 25% si su envío toma una secuencia MIDI real como entrada, analiza los mensajes Note On y Note Off de cualquier pista de su elección, siempre que contenga al menos cuatro eventos con mensajes Note On y Note Off, y salidas una tabla como se describe arriba.

Este es el código golf, por lo que gana el código más corto. ¡Buena suerte!

TNT
fuente

Respuestas:

6

PHP , 127 + 571 = 698 puntaje total *

Bien, estoy reclamando el bono. :) Esto tomará un archivo MIDI estándar y mostrará la salida.

He dividido el puntaje anterior en el desafío principal (analizar activar / desactivar nota y mostrarlo como gráfico) y el desafío adicional (leer la entrada del MIDI estándar) para hacer que los puntajes sean más comparables.

Principal: 170 bytes - 25% = 127

Para el principal, la función $d()toma la matriz requerida y muestra la salida ASCII. Se incluyen todas las pruebas y la salida del archivo MIDI de prueba a continuación.

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

Pruébalo en línea!

Bonus: 761 bytes - 25% = 571

La función $m()cargará un archivo MIDI estándar (localmente o por URL) y devolverá una matriz de pistas, cada una de las cuales contiene una matriz en el formato de nota especificado para todas las pistas del archivo 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;};

¡Véalo en línea! Obviamente, TIO está protegido para no permitir solicitudes remotas o archivos locales, por lo que tendrá que ejecutar este código localmente para verlo en acción. Las primeras [pruebas] [TIO-jrwa60tu] en la función de visualización incluyen el resultado de la matriz del archivo MIDI de prueba .

Rutina de carga de archivos MIDI sin golf:

$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 archivo MIDI de prueba de "Oda a la Alegría" que puede usarse descargado aquí . Ejemplo de uso:

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

Salida de archivo 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

Notas

En formato MIDI, los eventos Note On / Note Off son atómicos, lo que significa que ve un evento Note On en un momento determinado para una nota determinada (por ejemplo, E5), y está implícito que se reproducirá hasta un evento Note Off para otra nota E5 es visto. Como tal, es necesario analizar los eventos MIDI y hacer coincidir una Nota activada con su Nota desactivada, cuyo código es297184 bytes. Para complicar aún más esto, es bastante común en el formato MIDI estándar ver un Note On coincidente posterior con una velocidad 0 que representa lo mismo que un Note Off.

Esto ahora leerá correctamente los archivos que tienen Note On de velocidad cero en lugar de Note Off, por lo que debería abrir la mayoría de los archivos estándar.

Advertencias

Esto no es una implementación completa del formato MIDI, sin embargo, lo he probado con una colección bastante extensa de archivos MIDI y los lee muy bien.

Esta presentación aún no se ha llevado al extremo, por lo que es muy probable que se pueda hacer más pequeña. Creo que es muy poco probable que el bono de reducción de puntuación del 25% compense el código necesario para leer un archivo MIDI estándar. Como el envío más pequeño (actual) que solo muestra la pantalla ASCII es106 65 bytes, requeriría que las rutinas del archivo MIDI se implementen en 2521 bytes para vencer. Desafiaría a cualquiera a hacer eso (sin usar un lenguaje incorporado o un módulo). :)

640 KB
fuente
Esta es una respuesta asombrosa. Mirando hacia atrás a este desafío, estoy de acuerdo en que el monto de la bonificación probablemente no reducirá los puntajes lo suficiente como para tener en cuenta la sobrecarga de leer un archivo MIDI. (Creo que las bonificaciones se desaconsejan hoy en día de todos modos). Sin embargo, estoy muy impresionado de que hayas aceptado el desafío de bonificaciones. Podría darte una buena recompensa por ello.
TNT
@TNT, gracias! Realmente disfruté haciéndolo y fue interesante intentar desarrollar rutinas de formato de archivo para algo tan tonto como SMF. ¡Gran reto!
640 KB
5

Ruby, 106 bytes

Esto fue divertido. No estoy seguro de por qué nadie lo intentó.

Esta función toma la entrada como cuatro argumentos de matriz y devuelve una matriz de cadenas, una para cada línea del gráfico.

->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: Esto supone arbitrariamente que no habrá más de 10,000 ticks. Si lo ejecuta en su terminal, le sugiero que lo conecte para lessque pueda desplazarse horizontalmente. Puede cambiar 1e4si desea más ticks, hasta el final 9e9, pero eso tomará un terabyte o dos de RAM.

Véalo en repl.it: https://repl.it/Cx4I/1

Jordán
fuente
Gracias por la presentación! Pero, curiosamente, no puedo ver la salida en la respuesta (todo lo que puedo ver son los números 127-0 con muchos retornos entre ellos). Nunca he usado respuestas antes, así que no sabría por qué. ¿Podría sugerirme una forma de ver la salida correctamente?
TNT
Eso es bastante extraño Esto funciona para mi. No estoy en una computadora en este momento, pero aquí hay una captura de pantalla de mi teléfono: i.stack.imgur.com/3UCyn.jpg
Jordania
Gracias por la captura de pantalla. Estoy pensando que el problema podría ser el navegador web que estoy usando, así que lo intentaré en otro más tarde. +1 de mi parte sin embargo. :)
TNT
2

Pitón 2, 163 160 156 145 bytes

Esta no es la forma más elegante de hacerlo, pero fue una de las más simples. Si pudiera descubrir cómo reemplazar partes de cadenas sin convertirlas en listas, reemplazarlas y volverlas a convertir en cadenas, sería muy útil aquí. Sugerencias de golf bienvenidas.

Editar: 18 bytes gracias a Leaky Nun. Pruébalo en 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
fuente
@LeakyNun Whoops, my bad
Loovjo
¿Puedes usar la sustitución de expresiones regulares? En Ruby, algo así str.sub(/(?<=.{20}).{3}/,"foo")es equivalente a str[20,3] = "foo". Por supuesto, eso significa construir la expresión regular mediante interpolación / concatenación de cadenas con las variables de índice / longitud, lo cual es barato en bytes Ruby, pero tal vez no en Python.
Jordania
1

Japt , 65 bytes

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

Pruébalo en línea!

Toma la entrada como una lista de notas en el formato [pitch, start_tick, end_tick, velocity]. Si es obligatorio tomar la entrada como listas separadas (es decir, una lista que contiene todos los tonos, una que contiene todas las velocidades, etc.), eso se puede lograr a costa de 1 byte .

Explicación:

®Æ"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
fuente