Exibir uma faixa MIDI

17

fundo

Os arquivos MIDI são bem diferentes dos arquivos de áudio WAV ou MP3. Os arquivos MP3 e WAV contêm bytes que representam uma "gravação" do áudio, enquanto os arquivos MIDI possuem uma série de mensagens MIDI armazenadas em eventos MIDI, informando ao sintetizador MIDI qual instrumento virtual tocar ou um seqüenciador MIDI o andamento da reprodução. Essas mensagens são armazenadas em trilhas, e uma coleção de trilhas compõe uma sequência MIDI, cujos eventos podem ser analisados ​​por um seqüenciador e têm suas mensagens transmitidas do seqüenciador para o receptor de um sintetizador.

Na maioria das vezes, as mensagens MIDI armazenadas nos eventos MIDI são mensagens Note On que instruem o sintetizador a tocar uma nota específica, ou mensagens Note Off que instruem o sintetizador a parar de tocar a nota. Essas mensagens contêm dois bytes de dados, o primeiro informa o sintetizador da velocidade da nota (velocidade mais alta resulta em uma nota mais alta) e o segundo informa o sintetizador da nota a ser tocada (por exemplo, C médio). Os eventos também contêm marcações que servem para informar ao seqüenciador quando enviar as mensagens.

O desafio

O desafio é escrever um programa completo ou uma função que analise uma série de mensagens MIDI Note On e Note Off em uma sequência MIDI de faixa única e envia para STDOUT um gráfico mostrando quando determinadas notas estão ativadas, quando estão desativadas e as velocidade dessas notas. O eixo vertical do gráfico representa o valor da nota e deve ser rotulado como descrito abaixo, e o eixo horizontal representa o tempo nos ticks MIDI (embora deva permanecer sem rótulo para reduzir problemas de complexidade e espaçamento).

Sua entrada pode ser quatro matrizes ou listas separadas, cada uma contendo uma série de valores inteiros; uma matriz ou lista bidimensional contendo quatro sub-matrizes / sub-listas com uma série de valores inteiros; ou qualquer outro meio conveniente; isso representa os eventos MIDI da coleção com as mensagens Note On e Note Off na faixa. Os valores na primeira dessas matrizes especificam a nota, o segundo a velocidade, a terceira a nota no evento e o quarto na nota desativada. Por exemplo, considerando quatro matrizes como estas:

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

A análise do primeiro elemento de cada matriz fornece dois eventos: um evento no tick 0 com uma mensagem que possui um comando Note On, nota 60 (C média) e velocidade da nota de 20; e um evento no tick 2 com uma mensagem que possui um comando Note Off com a mesma nota e velocidade.

Regras

O gráfico deve apresentar os números de 0 a 127 exibidos em ordem decrescente no lado esquerdo (representando o valor da nota), quando a nota é iniciada, a duração de cada nota (nota desativada menos a nota ativada) e a velocidade da nota. Os símbolos que representam as notas dependem de sua velocidade:

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

Você pode assumir o seguinte:

  • Os valores para nota e velocidade estarão dentro do intervalo [0, 127].
  • Os comprimentos de cada uma das quatro matrizes sempre serão iguais.

Aqui estão alguns exemplos:

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

Aqui está um exemplo que exibe as primeiras 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  |

Você pode reduzir sua pontuação em 25% se o envio enviar uma sequência MIDI real como entrada, analisar as mensagens Note On e Note Off de qualquer faixa de sua escolha, desde que contenha pelo menos quatro eventos com mensagens Note On e Note Off e saídas um gráfico como descrito acima.

Isso é código de golfe, então o código mais curto vence. Boa sorte!

TNT
fonte

Respostas:

6

PHP , 127 + 571 = 698 pontuação total *

Ok, estou reivindicando o bônus. :) Isso pegará um arquivo MIDI padrão e exibirá a saída.

Dividi a pontuação acima no desafio principal (analise as notas ativadas / desativadas e mostre como gráfico) e o desafio dos bônus (leia a entrada do MIDI padrão) para tornar as pontuações mais comparáveis.

Principal: 170 bytes - 25% = 127

Para o principal, a função $d()pega a matriz necessária e exibe a saída ASCII. Estão incluídos todos os testes e saída do arquivo MIDI de teste abaixo.

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

Experimente online!

Bônus: 761 bytes - 25% = 571

A função $m()carrega um arquivo MIDI padrão (localmente ou por URL) e retorna uma matriz de faixas, cada uma contendo uma matriz no formato de nota especificado para todas as faixas do arquivo 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;};

Veja online! Obviamente, o TIO está na caixa de areia para não permitir solicitações remotas ou arquivos locais; portanto, você precisará executar esse código localmente para vê-lo em ação. Os primeiros [testes] [TIO-jrwa60tu] na função de exibição incluem o resultado da matriz do arquivo MIDI de teste .

Rotina de carregamento de arquivo MIDI desfeita:

$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

Um arquivo MIDI de teste de "Ode to Joy" que pode ser usado baixado aqui . Exemplo 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 );
}

Saída de arquivo 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

No formato MIDI, os eventos Note On / Note Off são atômicos, o que significa que você vê um evento Note On em um determinado momento para uma determinada nota (por exemplo, E5), e está implícito que ele será reproduzido até um evento Note Off para outra nota E5 é visto. Como tal, é necessário analisar os eventos MIDI e combinar uma determinada nota ativada com a nota desativada, cujo código é297184 bytes. Para complicar ainda mais, é bastante comum no formato MIDI padrão ver uma Nota On correspondente correspondente com uma velocidade 0 representando a mesma coisa que uma Nota desativada.

Agora, ele irá ler corretamente os arquivos que possuem notas ativadas com velocidade zero em vez das notas desativadas, portanto, deve abrir a maioria dos arquivos padrão.

Ressalvas

Isso não é de forma alguma uma implementação completa do formato MIDI, no entanto, eu testei isso com uma coleção bastante extensa de arquivos MIDI e os lê bem.

Essa submissão ainda não foi totalmente lançada, então é muito provável que ela possa ser reduzida. Eu acho que é muito improvável que o bônus de redução de 25% compensasse o código necessário para ler um arquivo MIDI padrão. Como o menor envio (atual) que apenas exibe a exibição ASCII é106 65 bytes, seria necessário implementar as rotinas de arquivos MIDI em 2521 bytes para bater. Desafio qualquer um a fazer isso (sem usar um idioma ou módulo interno). :)

640KB
fonte
Esta é uma resposta incrível. Olhando para esse desafio, concordo que o valor do bônus provavelmente não reduzirá pontuações suficientes para explicar a sobrecarga de ler um arquivo MIDI. (Acho que os bônus são desencorajados hoje em dia de qualquer maneira.) No entanto, estou muito impressionado por você ter aceitado o desafio do bônus. Eu posso te dar uma boa recompensa por isso.
TNT
@ TNT, obrigado! Realmente gostei de fazer isso e foi interessante tentar rotinas de formato de arquivo de golfe para algo tão pateta quanto o SMF. Grande desafio!
640KB
5

Ruby, 106 bytes

Isso foi divertido. Não sei por que ninguém tentou.

Essa função recebe a entrada como quatro argumentos de matriz e retorna uma matriz de seqüências de caracteres, uma para cada linha do 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: Isso pressupõe arbitrariamente que não haverá mais de 10.000 ticks. Se você executá-lo em seu terminal, sugiro canalizá-lo para lessque você possa rolar horizontalmente. Você pode mudar 1e4se quiser mais ticks, até o fim 9e9, mas isso levará um terabyte ou dois de RAM.

Veja em repl.it: https://repl.it/Cx4I/1

Jordânia
fonte
Obrigado pela submissão! Mas, estranhamente, não consigo ver a saída no repl (tudo o que posso ver são os números 127-0 com muitos retornos entre eles). Eu nunca usei repl antes, então eu não saberia o porquê. Você poderia sugerir uma maneira de ver a saída corretamente?
TNT
Isso é bem estranho. Funciona para mim. Eu não estou em um computador agora, mas aqui está uma imagem do meu telefone: i.stack.imgur.com/3UCyn.jpg
Jordan
Obrigado pela captura de tela. Eu estou pensando que o problema poderia ser o navegador da web que estou usando, então tentarei em outro diferente mais tarde. +1 de mim. :)
TNT
2

Python 2, 163 160 156 145 bytes

Esta não é a maneira mais divertida de fazer isso, mas foi uma das mais simples. Se eu pudesse descobrir como substituir partes de strings sem transformá-las em listas, substituí-las e transformá-las novamente em strings, isso seria muito útil aqui. Sugestões de golfe são bem-vindas.

Edit: 18 bytes graças a Leaky Nun. Experimente no 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
fonte
@LeakyNun Opa, meu mau
Loovjo
Você pode usar substituição de expressão regular? Em Ruby, algo como str.sub(/(?<=.{20}).{3}/,"foo")é equivalente a str[20,3] = "foo". Obviamente, isso significa construir o regexp por interpolação / concatenação de strings com as variáveis ​​index / length - o que é barato em bytes Ruby, mas talvez não em Python.
Jordânia
1

Japt , 65 bytes

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

Experimente online!

Recebe a entrada como uma lista de notas no formato [pitch, start_tick, end_tick, velocity]. Se a entrada de entrada como listas separadas for obrigatória (ou seja, uma lista contendo todos os arremessos, uma contendo todas as velocidades etc.), isso pode ser realizado ao custo de 1 byte .

Explicação:

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