Menampilkan Track MIDI

17

Latar Belakang

File MIDI sangat berbeda dari file audio WAV atau MP3. File MP3 dan WAV berisi byte yang mewakili "rekaman" audio, sementara file MIDI memiliki serangkaian pesan MIDI yang disimpan dalam acara MIDI yang menginformasikan synthesizer MIDI instrumen virtual mana yang akan diputar atau MIDI sequencer tempo pemutaran yang harus digunakan. Pesan-pesan ini disimpan dalam trek, dan koleksi trek membentuk urutan MIDI, yang acara-acaranya dapat dianalisis oleh sequencer dan pesannya dikirim dari sequencer ke penerima synthesizer.

Sebagian besar waktu pesan MIDI yang disimpan dalam acara MIDI adalah pesan Note On yang memberi tahu synthesizer untuk memainkan note tertentu, atau pesan Note Off yang memerintahkan synthesizer untuk berhenti memainkan note. Pesan-pesan ini mengandung dua byte data, yang pertama menginformasikan synthesizer dari kecepatan not (kecepatan yang lebih tinggi menghasilkan not yang lebih keras), dan yang kedua memberitahu synthesizer not untuk dimainkan (yaitu Middle C). Peristiwa itu sendiri juga mengandung kutu yang melayani tujuan memberi tahu sequencer kapan harus mengirim pesan.

Tantangan

Tantangannya adalah untuk menulis program lengkap atau fungsi yang menganalisis serangkaian pesan Note On dan Note Off dalam urutan MIDI single-track dan output ke STDOUT bagan yang menunjukkan kapan catatan tertentu hidup, ketika mereka mati, dan kecepatan catatan ini. Sumbu vertikal bagan mewakili nilai nada dan harus diberi label seperti yang dijelaskan di bawah ini, dan sumbu horizontal mewakili waktu dalam kutu MIDI (meskipun itu harus tetap tidak berlabel untuk mengurangi kompleksitas dan masalah spasi).

Input Anda mungkin empat array atau daftar terpisah, masing-masing berisi serangkaian nilai integer; array atau daftar dua dimensi yang berisi empat sub-array / sub-daftar dengan serangkaian nilai integer; atau cara lain yang nyaman; ini mewakili koleksi acara MIDI dengan pesan Note On dan Note Off di trek. Nilai dalam array pertama menentukan catatan, yang kedua kecepatan, yang ketiga catatan pada centang acara, dan yang keempat catatan dari centang centang acara. Misalnya, diberikan empat array seperti ini:

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

Menganalisis elemen pertama dari setiap array memberikan dua peristiwa: peristiwa pada tick 0 dengan pesan yang memiliki perintah Note On, note 60 (Middle C), dan kecepatan note 20; dan acara pada tick 2 dengan pesan yang memiliki perintah Note Off dengan nada dan kecepatan yang sama.

Aturan

Bagan tersebut harus menampilkan angka 0 hingga 127 yang ditampilkan dalam urutan menurun di sisi kiri (mewakili nilai not), ketika not dimulai, durasi setiap not (tick Nonaktif dikurangi tick Non aktif), dan kecepatan nada. Simbol yang mewakili catatan tergantung pada kecepatannya:

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

Anda dapat mengasumsikan sebagai berikut:

  • Nilai untuk catatan dan kecepatan akan berada dalam kisaran [0, 127].
  • Panjang masing-masing dari empat array akan selalu sama satu sama lain.

Berikut ini beberapa contoh:

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

Berikut adalah contoh yang menampilkan beberapa catatan pertama 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  |

Anda dapat mengurangi skor Anda hingga 25% jika kiriman Anda menggunakan urutan MIDI yang sebenarnya sebagai input, menganalisis pesan Note On dan Note Off dari trek apa pun yang Anda pilih asalkan berisi setidaknya empat peristiwa dengan pesan Note On dan Note Off, dan output grafik seperti yang dijelaskan di atas.

Ini kode golf, jadi kode terpendek menang. Semoga berhasil!

TNT
sumber

Jawaban:

6

PHP , 127 + 571 = 698 skor total *

Oke, saya mengklaim bonus. :) Ini akan mengambil file MIDI standar dan menampilkan hasilnya.

Saya telah membagi skor di atas menjadi tantangan utama (menganalisis catatan on / off dan ditampilkan sebagai grafik) dan tantangan bonus (baca input dari MIDI standar) untuk membuat skor lebih sebanding.

Utama: 170 byte - 25% = 127

Untuk main, fungsi $d()mengambil array yang diperlukan dan menampilkan output ASCII. Termasuk semua tes dan output file uji MIDI di bawah ini.

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

Cobalah online!

Bonus: 761 byte - 25% = 571

Fungsi $m()akan memuat file MIDI standar (baik secara lokal atau dengan URL) dan mengembalikan array trek, masing-masing berisi array dalam format catatan yang ditentukan untuk semua trek 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;};

Lihat online! Jelas TIO di-sandbox agar tidak mengizinkan permintaan jarak jauh atau file lokal, jadi Anda harus menjalankan kode ini secara lokal untuk melihatnya beraksi. [Tes] pertama [TIO-jrwa60tu] pada fungsi tampilan termasuk hasil array dari file MIDI tes .

MIDI memuat file dengan rutin:

$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

File uji MIDI "Ode to Joy" yang dapat digunakan diunduh di sini . Contoh penggunaan:

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

Output 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

Catatan

Dalam format MIDI, acara Note On / Note Off bersifat atomik, artinya Anda melihat acara Note On pada waktu tertentu untuk catatan yang diberikan (katakanlah E5), dan itu tersirat bahwa ia akan diputar hingga peristiwa Note Off untuk catatan E5 lainnya terlihat. Karena itu, perlu untuk menganalisis peristiwa MIDI dan mencocokkan Note On yang diberikan dengan Note Off, yang kode untuk itu adalah297184 byte Lebih rumit lagi, ini cukup umum dalam format MIDI standar untuk melihat pencocokan Note On berikutnya dengan kecepatan 0 yang mewakili hal yang sama dengan Note Off.

Ini sekarang akan dengan benar membaca file yang memiliki kecepatan nol Note On daripada Note Off, jadi harus membuka sebagian besar file standar.

Peringatan

Ini sama sekali bukan implementasi lengkap dari format MIDI, namun saya telah mengujinya dengan koleksi file MIDI yang cukup luas dan membacanya dengan baik.

Pengajuan ini belum golf hingga ekstrem, jadi sangat mungkin ini bisa dibuat lebih kecil. Saya pikir itu sangat tidak mungkin bahwa bonus pengurangan skor 25% akan mengimbangi kode yang diperlukan untuk membaca file MIDI standar. Sebagai pengajuan terkecil (saat ini) yang hanya menampilkan ASCII106 65 byte, itu akan membutuhkan rutinitas file MIDI untuk diimplementasikan di 2521 byte untuk dikalahkan. Saya akan menantang siapa pun untuk melakukan itu (tanpa menggunakan bahasa built-in atau modul). :)

640KB
sumber
Ini jawaban yang luar biasa. Melihat kembali tantangan ini, saya setuju bahwa jumlah bonus mungkin tidak akan mengurangi skor yang cukup untuk memperhitungkan overhead membaca file MIDI. (Saya pikir bonus tidak dianjurkan saat ini.) Namun demikian saya sangat terkesan bahwa Anda mengambil tantangan bonus. Saya mungkin memberi Anda hadiah yang bagus untuk itu.
TNT
@TNT, terima kasih! Sangat menikmati melakukannya dan menarik mencoba format file golf untuk sesuatu yang konyol seperti SMF. Tantangan besar!
640KB
5

Ruby, 106 byte

Ini sangat menyenangkan. Saya tidak yakin mengapa tidak ada yang mencobanya.

Fungsi ini mengambil input sebagai empat argumen array dan mengembalikan array string, satu untuk setiap baris grafik.

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

Catatan: Ini secara sewenang-wenang mengasumsikan bahwa tidak akan ada lebih dari 10.000 kutu. Jika Anda menjalankannya di terminal, saya sarankan untuk memipainya lessagar Anda dapat menggulir secara horizontal. Anda dapat mengubah 1e4jika Anda menginginkan lebih banyak kutu 9e9, namun, itu akan membutuhkan satu atau dua terabyte RAM.

Lihat di repl.it: https://repl.it/Cx4I/1

Yordania
sumber
Terima kasih atas kirimannya! Tapi anehnya saya tidak bisa melihat output pada repl (yang bisa saya lihat adalah angka 127-0 dengan banyak pengembalian di antara mereka). Saya tidak pernah menggunakan repl sebelumnya jadi saya tidak akan tahu mengapa. Bisakah Anda menyarankan cara bagi saya untuk melihat output dengan benar?
TNT
Cukup aneh. Ini bekerja untuk saya. Saya tidak di depan komputer sekarang, tapi ini screenshot dari telepon saya: i.stack.imgur.com/3UCyn.jpg
Jordan
Terima kasih untuk tangkapan layarnya. Saya pikir masalahnya bisa berupa peramban web yang saya gunakan, jadi saya akan mencoba yang lain nanti. +1 dari saya. :)
TNT
2

Python 2, 163 160 156 145 byte

Ini bukan cara golf untuk melakukannya, tetapi itu adalah salah satu yang paling sederhana. Jika saya bisa mencari cara untuk mengganti bagian-bagian string tanpa mengubahnya menjadi daftar, mengganti, dan mengubahnya kembali menjadi string, itu akan sangat membantu di sini. Saran bermain golf diterima.

Sunting: 18 bytes berkat Leaky Nun. Cobalah di 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
sumber
@LeakyNun Whoops, my bad
Loovjo
Bisakah Anda menggunakan substitusi ekspresi reguler? Di Ruby sesuatu yang mirip str.sub(/(?<=.{20}).{3}/,"foo")dengan str[20,3] = "foo". Tentu saja, itu berarti membangun regexp dengan interpolasi string / penggabungan dengan variabel indeks / panjang — yang murah dalam byte Ruby, tetapi mungkin tidak dalam Python.
Jordan
1

Japt , 65 byte

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

Cobalah online!

Mengambil input sebagai daftar catatan dalam format [pitch, start_tick, end_tick, velocity]. Jika mengambil input sebagai daftar terpisah adalah wajib (yaitu satu daftar yang berisi semua pitch, satu berisi semua kecepatan dll), yang dapat dicapai dengan biaya 1 byte .

Penjelasan:

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