Zeigen Sie eine MIDI-Spur an

17

Hintergrund

MIDI-Dateien unterscheiden sich erheblich von WAV- oder MP3-Audiodateien. MP3- und WAV-Dateien enthalten Bytes, die eine "Aufnahme" des Audios darstellen, während MIDI-Dateien eine Reihe von MIDI-Meldungen enthalten, die in MIDI-Ereignissen gespeichert sind und einen MIDI-Synthesizer darüber informieren, welches virtuelle Instrument oder ein MIDI-Sequenzer das Wiedergabetempo spielen soll, das verwendet werden soll. Diese Meldungen sind in Spuren gespeichert, und eine Sammlung von Spuren bildet eine MIDI-Sequenz, deren Ereignisse von einem Sequenzer analysiert werden können und deren Meldungen vom Sequenzer an den Empfänger eines Synthesizers übertragen werden.

In den meisten Fällen handelt es sich bei den in MIDI-Events gespeicherten MIDI-Meldungen um Note-On-Meldungen, die den Synthesizer anweisen, eine bestimmte Note zu spielen, oder um Note-Off-Meldungen, die den Synthesizer anweisen, die Note zu stoppen. Diese Nachrichten enthalten zwei Datenbytes, von denen das erste dem Synthesizer die Anschlagstärke der Note mitteilt (höhere Anschlagstärke führt zu einer lauteren Note) und das zweite dem Synthesizer mitteilt, welche Note zu spielen ist (dh mittleres C). Die Ereignisse selbst enthalten auch Häkchen, die dazu dienen, dem Sequenzer mitzuteilen, wann er die Nachrichten senden soll.

Die Herausforderung

Die Herausforderung besteht darin, ein vollständiges Programm oder eine Funktion zu schreiben, die eine Reihe von Note-On- und Note-Off-MIDI-Meldungen in einer einspurigen MIDI-Sequenz analysiert und eine Tabelle an STDOUT ausgibt, in der angezeigt wird, wann bestimmte Noten an sind, wann sie aus sind, und die Geschwindigkeit dieser Noten. Die vertikale Achse des Diagramms stellt den Notenwert dar und sollte wie unten beschrieben beschriftet werden, und die horizontale Achse stellt die Zeit in MIDI-Ticks dar (sollte jedoch unbeschriftet bleiben, um Komplexitäts- und Abstandsprobleme zu verringern).

Ihre Eingabe kann aus vier separaten Arrays oder Listen bestehen, die jeweils eine Reihe von Ganzzahlwerten enthalten. ein zweidimensionales Array oder eine zweidimensionale Liste, die vier Unterarrays / Unterlisten mit einer Reihe von ganzzahligen Werten enthält; oder irgendein anderes geeignetes Mittel; Dies repräsentiert die Sammlung von MIDI-Events mit Note On- und Note Off-Meldungen in der Spur. Der Wert im ersten dieser Arrays gibt die Note an, der zweite die Velocity, der dritte den Note-On-Event-Tick und der vierte den Note-Off-Event-Tick. Zum Beispiel bei vier Arrays wie diesen:

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

Das Analysieren des ersten Elements jedes Arrays ergibt zwei Ereignisse: Ein Ereignis bei Tick 0 mit einer Nachricht, die einen Note-On-Befehl, Note 60 (Mitte C) und eine Note-Velocity von 20 hat; und ein Ereignis bei Tick 2 mit einer Nachricht, die einen Note-Off-Befehl mit derselben Note und Anschlagstärke enthält.

Regeln

In der Tabelle sollten die Zahlen 0 bis 127 in absteigender Reihenfolge auf der linken Seite (die den Notenwert darstellt), die Dauer jeder Note (Note aus Häkchen minus Note an Häkchen) und die Anschlagstärke der Note angezeigt werden. Die Symbole, die die Noten darstellen, hängen von ihrer Geschwindigkeit ab:

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

Sie können folgendes annehmen:

  • Die Werte für Note und Velocity liegen im Bereich [0, 127].
  • Die Längen der vier Arrays sind immer gleich.

Hier einige Beispiele:

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

Hier ist ein Beispiel, das die ersten Noten von Ode an die Freude zeigt:

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

Sie können Ihre Partitur um 25% reduzieren, wenn Ihre Einreichung eine tatsächliche MIDI-Sequenz als Eingabe verwendet, die Note-On- und Note-Off-Meldungen einer beliebigen Spur analysiert, sofern sie mindestens vier Ereignisse mit Note-On- und Note-Off-Meldungen sowie Ausgängen enthält ein Diagramm wie oben beschrieben.

Dies ist Codegolf, also gewinnt der kürzeste Code. Viel Glück!

TNT
quelle

Antworten:

6

PHP , 127 + 571 = 698 Gesamtpunktzahl *

Okay, ich beanspruche den Bonus. :) Dies nimmt eine Standard-MIDI-Datei und zeigt die Ausgabe an.

Ich habe die Punktzahl oben in die Hauptherausforderung (Note ein / aus analysieren und als Tabelle anzeigen) und die Bonusherausforderung (Eingabe von Standard-MIDI lesen) unterteilt, um die Punktzahlen vergleichbarer zu machen.

Main: 170 Bytes - 25% = 127

Die Funktion $d()nimmt hauptsächlich das erforderliche Array und zeigt die ASCII-Ausgabe an. Enthalten sind alle Tests und die Ausgabe der Test-MIDI-Datei unten.

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

Probieren Sie es online!

Bonus: 761 Bytes - 25% = 571

Die Funktion $m()lädt eine Standard-MIDI-Datei (entweder lokal oder per URL) und gibt ein Array von Spuren zurück, von denen jede ein Array im angegebenen Notenformat für alle MIDI-Dateispuren enthält.

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

Sehen Sie es online! Offensichtlich ist TIO sandboxed, um keine Remoteanforderungen oder lokalen Dateien zuzulassen. Daher müssen Sie diesen Code lokal ausführen, um ihn in Aktion zu sehen. Das erste [tests] [TIO-jrwa60tu] in der Anzeigefunktion enthält das Array-Ergebnis aus der Test-MIDI-Datei .

MIDI File Load Routine ungolfed:

$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

Eine Test-MIDI-Datei von "Ode to Joy", die hier heruntergeladen werden kann . Beispiel Verwendung:

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

Ausgabe der MIDI-Datei "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

Anmerkungen

Im MIDI-Format sind Note On / Note Off-Ereignisse atomar, dh, Sie sehen ein Note On-Ereignis zu einem bestimmten Zeitpunkt für eine bestimmte Note (z. B. E5), und es wird impliziert, dass es bis zu einem Note Off-Ereignis für eine andere E5-Note abgespielt wird Wurde gesehen. Als solches ist es notwendig, die MIDI-Ereignisse zu analysieren und eine gegebene Note On mit der Note Off abzugleichen, die der Code dafür ist297184 Bytes. Erschwerend kommt hinzu, dass es im Standard-MIDI-Format üblich ist, eine nachfolgende passende Note On mit einer Velocity 0 zu sehen, die das Gleiche wie eine Note Off darstellt.

Hiermit werden nun korrekt Dateien gelesen, die Note Ons mit der Geschwindigkeit Null anstelle von Note Offs aufweisen. Daher sollten die meisten Standarddateien geöffnet werden.

Vorbehalte

Dies ist keineswegs eine vollständige Implementierung des MIDI-Formats, aber ich habe dies mit einer ziemlich umfangreichen Sammlung von MIDI-Dateien getestet und es liest sie alle gut.

Diese Einreichung wurde noch nicht bis zum Äußersten golfen, daher ist es sehr wahrscheinlich, dass dies kleiner gemacht werden kann. Ich halte es für sehr unwahrscheinlich, dass der um 25% reduzierte Bonus den zum Lesen einer Standard-MIDI-Datei erforderlichen Code ausgleicht. Als die (aktuell) kleinste Einreichung, die gerade die ASCII-Anzeige macht, gilt106 65 Bytes würde es erfordern, dass die MIDI-Dateiroutinen in implementiert werden 2521 Bytes zu schlagen. Ich fordere jeden dazu auf (ohne Verwendung einer integrierten Sprache oder eines Moduls). :)

640 KB
quelle
Das ist eine großartige Antwort. Wenn ich auf diese Herausforderung zurückblicke, stimme ich zu, dass der Bonusbetrag die Punktzahl wahrscheinlich nicht genug verringert, um den Mehraufwand beim Lesen einer MIDI-Datei zu berücksichtigen. (Ich denke, Boni werden heutzutage sowieso nicht mehr empfohlen.) Trotzdem bin ich sehr beeindruckt, dass Sie die Bonusherausforderung angenommen haben. Ich könnte dir eine gute Belohnung dafür geben.
TNT
@ TNT, danke! Es hat wirklich Spaß gemacht und es war interessant zu versuchen, Dateiformatroutinen für etwas so doofes wie SMF zu spielen. Große Herausforderung!
5.
5

Rubin, 106 Bytes

Das hat Spaß gemacht. Ich bin mir nicht sicher, warum es niemand versucht hat.

Diese Funktion nimmt Eingaben als vier Array-Argumente entgegen und gibt ein Array von Zeichenfolgen zurück, eine für jede Zeile des Diagramms.

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

Hinweis: Dies setzt willkürlich voraus, dass es nicht mehr als 10.000 Ticks geben wird. Wenn Sie es in Ihrem Terminal ausführen, empfehle ich es weiterzuleiten, lessdamit Sie horizontal scrollen können. Sie können ändern, 1e4ob Sie mehr Ticks möchten, bis zu 9e9, aber das benötigt ein oder zwei Terabyte RAM.

Siehe es auf repl.it: https://repl.it/Cx4I/1

Jordan
quelle
Vielen Dank für die Einreichung! Aber seltsamerweise kann ich die Ausgabe auf repl nicht sehen (alles, was ich sehen kann, sind die Zahlen 127-0 mit vielen Renditen zwischen ihnen). Ich habe repl noch nie benutzt, also würde ich nicht wissen warum. Könnten Sie mir einen Weg vorschlagen, um die Ausgabe richtig zu sehen?
TNT
Das ist ziemlich seltsam. Für mich geht das. Ich bin gerade nicht an einem Computer, aber hier ist ein Screenshot von meinem Handy: i.stack.imgur.com/3UCyn.jpg
Jordan
Danke für den Screenshot. Ich denke, das Problem könnte der von mir verwendete Webbrowser sein, also werde ich es später in einem anderen versuchen. +1 von mir. :)
TNT
2

Python 2, 163 160 156 145 Bytes

Dies ist nicht die Golfspielweise, aber eine der einfachsten. Wenn ich herausfinden könnte, wie Teile von Zeichenfolgen ersetzt werden können, ohne sie in Listen umzuwandeln, zu ersetzen und wieder in Zeichenfolgen umzuwandeln, wäre dies hier sehr hilfreich. Golfvorschläge sind willkommen.

Edit: 18 Bytes dank Leaky Nun. Probieren Sie es auf 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
quelle
@LeakyNun Whoops, meine schlechte
Loovjo
Können Sie reguläre Ausdrücke ersetzen? In Ruby ist so etwas str.sub(/(?<=.{20}).{3}/,"foo")gleichbedeutend mit str[20,3] = "foo". Dies bedeutet natürlich, dass der reguläre Ausdruck durch Zeichenfolgeninterpolation / -verkettung mit den Index- / Längenvariablen erstellt wird - was in Ruby-Bytes billig ist, in Python jedoch möglicherweise nicht.
Jordanien
1

Japt , 65 Bytes

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

Probieren Sie es online!

Übernimmt die Eingabe als Liste von Notizen im Format [pitch, start_tick, end_tick, velocity]. Wenn die Eingabe als separate Listen obligatorisch ist (dh eine Liste mit allen Tonhöhen, eine Liste mit allen Geschwindigkeiten usw.), kann dies auf Kosten von 1 Byte durchgeführt werden .

Erläuterung:

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