Afficher une piste MIDI

17

Contexte

Les fichiers MIDI sont assez différents des fichiers audio WAV ou MP3. Les fichiers MP3 et WAV contiennent des octets représentant un "enregistrement" de l'audio, tandis que les fichiers MIDI contiennent une série de messages MIDI stockés dans des événements MIDI informant un synthétiseur MIDI de l'instrument virtuel à jouer ou un séquenceur MIDI du tempo de lecture à utiliser. Ces messages sont stockés dans des pistes, et une collection de pistes constitue une séquence MIDI, dont les événements peuvent être analysés par un séquenceur et voir ses messages transmis du séquenceur au récepteur d'un synthétiseur.

La plupart du temps, les messages MIDI stockés dans les événements MIDI sont des messages Note On qui indiquent au synthétiseur de jouer une note particulière ou des messages Note Off qui indiquent au synthétiseur d'arrêter de jouer la note. Ces messages contiennent deux octets de données, dont le premier informe le synthétiseur de la vélocité de la note (une vélocité plus élevée donne une note plus forte), et le second indique au synthétiseur la note à jouer (c'est-à-dire le do moyen). Les événements eux-mêmes contiennent également des ticks qui servent à dire au séquenceur quand envoyer les messages.

Le défi

Le défi consiste à écrire un programme complet ou une fonction qui analyse une série de messages MIDI Note On et Note Off dans une séquence MIDI à piste unique et envoie à STDOUT un graphique indiquant quand certaines notes sont activées, lorsqu'elles sont désactivées et vitesse de ces notes. L'axe vertical du graphique représente la valeur de la note et doit être étiqueté comme décrit ci-dessous, et l'axe horizontal représente le temps en ticks MIDI (bien qu'il ne doit pas être étiqueté pour réduire la complexité et les problèmes d'espacement).

Votre entrée peut être constituée de quatre tableaux ou listes distincts, chacun contenant une série de valeurs entières; un tableau ou une liste bidimensionnelle contenant quatre sous-tableaux / sous-listes avec une série de valeurs entières; ou tout autre moyen pratique; cela représente les événements MIDI de la collection avec des messages Note On et Note Off dans la piste. Les valeurs dans le premier de ces tableaux spécifient la note, la seconde la vélocité, la troisième la coche d'événement de note et la quatrième la coche d'événement de note désactivée. Par exemple, compte tenu de quatre tableaux tels que ceux-ci:

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

L'analyse du premier élément de chaque tableau donne deux événements: un événement au tick 0 avec un message qui a une commande Note On, une note 60 (milieu C) et une vitesse de note de 20; et un événement au tick 2 avec un message qui a une commande Note Off avec la même note et la même vitesse.

Règles

Le graphique doit comporter les chiffres de 0 à 127 affichés dans l'ordre décroissant sur le côté gauche (représentant la valeur de la note), au début de la note, la durée de chaque note (note désactivée moins la note activée) et la vélocité de la note. Les symboles représentant les notes dépendent de leur vélocité:

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

Vous pouvez supposer ce qui suit:

  • Les valeurs de note et de vélocité seront dans la plage [0, 127].
  • Les longueurs de chacun des quatre tableaux seront toujours égales.

Voici quelques exemples:

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

Voici un exemple qui affiche les premières notes d'Ode à la joie:

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

Vous pouvez réduire votre score de 25% si votre soumission prend une séquence MIDI réelle en entrée, analyse les messages Note On et Note Off de n'importe quelle piste de votre choix à condition qu'elle contienne au moins quatre événements avec des messages Note On et Note Off, et des sorties un tableau tel que décrit ci-dessus.

C'est le golf de code, donc le code le plus court gagne. Bonne chance!

TNT
la source

Réponses:

6

PHP , 127 + 571 = 698 score total *

D'accord, je réclame le bonus. :) Cela prendra un fichier MIDI standard et affichera la sortie.

J'ai divisé le score ci-dessus en défi principal (analyser la note activée / désactivée et afficher sous forme de graphique) et le défi bonus (lire les entrées du MIDI standard) pour rendre les scores plus comparables.

Principal: 170 octets - 25% = 127

Pour le principal, la fonction $d()prend le tableau requis et affiche la sortie ASCII. Tous les tests et la sortie du fichier MIDI de test ci-dessous sont inclus.

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

Essayez-le en ligne!

Bonus: 761 octets - 25% = 571

La fonction $m()charge un fichier MIDI standard (localement ou par URL) et renvoie un tableau de pistes, chacune contenant un tableau au format de note spécifié pour toutes les pistes du fichier 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;};

Voyez-le en ligne! Évidemment, TIO est mis en sandbox pour ne pas autoriser les requêtes distantes ou les fichiers locaux, vous devrez donc exécuter ce code localement pour le voir en action. Le premier [tests] [TIO-jrwa60tu] de la fonction d'affichage inclut le résultat du tableau du fichier MIDI de test .

Routine de chargement de fichiers MIDI non gérée:

$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 fichier MIDI de test de "Ode to Joy" qui peut être utilisé téléchargé ici . Exemple d'utilisation:

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

Sortie du fichier 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

Remarques

Au format MIDI, les événements Note On / Note Off sont atomiques, ce qui signifie que vous voyez un événement Note On à un certain moment pour une note donnée (disons E5), et cela implique qu'il sera joué jusqu'à un événement Note Off pour une autre note E5 est vu. En tant que tel, il est nécessaire d'analyser les événements MIDI et de faire correspondre une Note On donnée à sa Note Off, dont le code est297184 octets. Pour compliquer davantage les choses, il est assez courant dans le format MIDI standard de voir une note On correspondante ultérieure avec une vélocité 0 représentant la même chose qu'une note Off.

Cela lira désormais correctement les fichiers qui ont des notes On à vitesse nulle au lieu de Note Off, donc devrait ouvrir la plupart des fichiers standard.

Avertissements

Ce n'est en aucun cas une implémentation complète du format MIDI, mais j'ai testé cela avec une collection assez étendue de fichiers MIDI et il les lit tous très bien.

Cette soumission n'a pas encore été poussée à l'extrême, il est donc fort probable qu'elle puisse être réduite. Je pense qu'il est très peu probable que le bonus de réduction de 25% compenserait le code nécessaire pour lire un fichier MIDI standard. Comme la plus petite soumission (actuelle) qui ne fait que l'affichage ASCII est106 65 octets, il faudrait que les routines de fichiers MIDI soient implémentées dans 2521 octets à battre. Je mettrais quiconque au défi de le faire (sans utiliser de module ou de langage intégré). :)

640 Ko
la source
Ceci est une réponse impressionnante. En repensant à ce défi, je conviens que le montant du bonus ne réduira probablement pas suffisamment les scores pour tenir compte des frais généraux de lecture d'un fichier MIDI. (Je pense que les bonus sont découragés de toute façon de nos jours.) Néanmoins, je suis très impressionné que vous ayez relevé le défi des bonus. Je pourrais vous en donner une bonne récompense.
TNT
@TNT, merci! Vraiment apprécié de le faire et était intéressant d'essayer de jouer au golf des routines de format de fichier pour quelque chose d'aussi maladroit que SMF. Grand défi!
640 Ko
5

Ruby, 106 octets

C'était amusant. Je ne sais pas pourquoi personne ne l'a essayé.

Cette fonction prend l'entrée comme quatre arguments de tableau et renvoie un tableau de chaînes, une pour chaque ligne du graphique.

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

Remarque: Cela suppose arbitrairement qu'il n'y aura pas plus de 10 000 ticks. Si vous l'exécutez dans votre terminal, je vous suggère de le canaliser pour lesspouvoir faire défiler horizontalement. Vous pouvez changer 1e4si vous voulez plus de ticks, 9e9mais cela prendra un téraoctet ou deux de RAM.

Voir sur repl.it: https://repl.it/Cx4I/1

Jordan
la source
Merci pour la soumission! Mais étrangement, je ne peux pas voir la sortie sur repl (tout ce que je peux voir, ce sont les chiffres 127-0 avec beaucoup de retours entre eux). Je n'ai jamais utilisé repl auparavant, donc je ne sais pas pourquoi. Pourriez-vous me suggérer un moyen de voir correctement la sortie?
TNT
C'est assez étrange. Ça marche pour moi. Je ne suis pas devant un ordinateur pour l'instant, mais voici une capture d'écran de mon téléphone: i.stack.imgur.com/3UCyn.jpg
Jordan
Merci pour la capture d'écran. Je pense que le problème pourrait être le navigateur Web que j'utilise, donc je vais l'essayer dans un autre plus tard. +1 de moi cependant. :)
TNT
2

Python 2, 163 160 156 156 145 octets

Ce n'est pas la façon la plus golfique de le faire, mais c'était l'une des plus simples. Si je pouvais comprendre comment remplacer des parties de chaînes sans les transformer en listes, les remplacer et les reconvertir en chaînes, ce serait très utile ici. Suggestions de golf bienvenues.

Edit: 18 octets grâce à Leaky Nun. Essayez-le sur 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
la source
@LeakyNun Whoops, my bad
Loovjo
Pouvez-vous utiliser la substitution d'expression régulière? En Ruby, quelque chose comme str.sub(/(?<=.{20}).{3}/,"foo")est équivalent à str[20,3] = "foo". Bien sûr, cela signifie construire l'expression rationnelle par interpolation / concaténation de chaînes avec les variables d'index / de longueur - ce qui est bon marché en octets Ruby, mais peut-être pas en Python.
Jordan
1

Japt , 65 octets

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

Essayez-le en ligne!

Prend la saisie sous forme de liste de notes au format [pitch, start_tick, end_tick, velocity]. Si la saisie de données en tant que listes séparées est obligatoire (c'est-à-dire une liste contenant tous les pas, une contenant toutes les vitesses, etc.), cela peut être accompli au prix d' un octet .

Explication:

®Æ"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
la source