Hiển thị bản nhạc MIDI


17

Lý lịch

Các tệp MIDI khá khác với các tệp âm thanh WAV hoặc MP3. Các tệp MP3 và WAV chứa các byte đại diện cho "bản ghi" âm thanh, trong khi các tệp MIDI có một loạt các thông điệp MIDI được lưu trữ trong các sự kiện MIDI thông báo cho trình tổng hợp MIDI nên sử dụng nhạc cụ ảo nào hoặc trình phát tuần tự MIDI nên sử dụng nhịp phát lại. Các thông báo này được lưu trữ trong các bản nhạc và một tập hợp các bản nhạc tạo thành một chuỗi MIDI, có các sự kiện có thể được phân tích bởi trình sắp xếp chuỗi và các thông điệp của nó được truyền từ trình phân tích đến bộ thu của bộ tổng hợp.

Hầu hết thời gian các tin nhắn MIDI được lưu trữ trong các sự kiện MIDI là các tin nhắn Note On cho phép trình tổng hợp phát một ghi chú cụ thể hoặc các tin nhắn Note Off báo cho bộ tổng hợp ngừng phát ghi chú. Các thông báo này chứa hai byte dữ liệu, trong đó thông báo đầu tiên thông báo cho bộ tổng hợp vận tốc của ghi chú (tốc độ cao hơn dẫn đến ghi chú to hơn) và thông báo thứ hai cho bộ tổng hợp ghi chú phát (ví dụ: Trung C). Bản thân các sự kiện cũng chứa các dấu tick phục vụ mục đích báo cho người sắp xếp biết khi nào nên gửi tin nhắn.

Các thách thức

Thách thức là viết một chương trình đầy đủ hoặc một chức năng phân tích một loạt các thông báo MIDI Bật và Lưu ý Tắt theo trình tự MIDI một bản nhạc và xuất ra STDOUT một biểu đồ hiển thị khi bật các ghi chú cụ thể, khi chúng tắt và vận tốc của các ghi chú. Trục dọc của biểu đồ biểu thị giá trị ghi chú và phải được dán nhãn như được mô tả bên dưới và trục ngang biểu thị thời gian trong dấu tick MIDI (mặc dù vậy nó vẫn không được gắn nhãn để giảm các vấn đề về độ phức tạp và khoảng cách).

Đầu vào của bạn có thể là bốn mảng hoặc danh sách riêng biệt, mỗi mảng chứa một chuỗi các giá trị nguyên; một mảng hoặc danh sách hai chiều chứa bốn mảng phụ / danh sách phụ với một loạt các giá trị nguyên; hoặc bất kỳ phương tiện thuận tiện khác; điều này thể hiện bộ sưu tập các sự kiện MIDI với các thông báo Note On và Note Off trong track. Các giá trị trong mảng đầu tiên chỉ định ghi chú, tốc độ thứ hai, ghi chú thứ ba về đánh dấu sự kiện và thứ tư là ghi chú tắt sự kiện. Chẳng hạn, đưa ra bốn mảng như sau:

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

Việc phân tích phần tử đầu tiên của mỗi mảng sẽ đưa ra hai sự kiện: một sự kiện tại tick 0 với thông báo có lệnh Note On, ghi chú 60 (Trung C) và tốc độ ghi chú là 20; và một sự kiện ở tick 2 với một thông báo có lệnh Note Off với cùng một ghi chú và vận tốc.

Quy tắc

Biểu đồ sẽ hiển thị các số từ 0 đến 127 được hiển thị theo thứ tự giảm dần ở phía bên trái (đại diện cho giá trị ghi chú), khi ghi chú bắt đầu, thời lượng của mỗi ghi chú (Ghi chú Tắt đánh dấu vào Ghi chú) và tốc độ của ghi chú. Các biểu tượng đại diện cho các ghi chú phụ thuộc vào vận tốc của chúng:

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

Bạn có thể giả sử như sau:

  • Các giá trị cho ghi chú và vận tốc sẽ nằm trong phạm vi [0, 127].
  • Độ dài của mỗi trong bốn mảng sẽ luôn bằng nhau.

Đây là vài ví dụ:

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

Dưới đây là một ví dụ hiển thị một số ghi chú đầu tiên của 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  |

Bạn có thể giảm 25% số điểm của mình nếu bài gửi của bạn lấy chuỗi MIDI thực tế làm đầu vào, phân tích các thông báo Note On và Note Off của bất kỳ bản nhạc nào bạn chọn với điều kiện nó chứa ít nhất bốn sự kiện với tin nhắn Note On và Note Off, và đầu ra một biểu đồ như mô tả ở trên.

Đây là mã golf, vì vậy mã ngắn nhất sẽ thắng. Chúc may mắn!

Câu trả lời:


6

PHP , 127 + 571 = 698 tổng số điểm *

Được rồi, tôi đang nhận tiền thưởng. :) Điều này sẽ lấy một tệp MIDI tiêu chuẩn và hiển thị đầu ra.

Tôi đã chia điểm số ở trên thành thử thách chính (phân tích bật / tắt ghi chú và hiển thị dưới dạng biểu đồ) và thử thách phần thưởng (đọc đầu vào từ MIDI tiêu chuẩn) để làm cho điểm số tương đương hơn.

Chính: 170 byte - 25% = 127

Đối với chính, hàm $d()lấy mảng yêu cầu và hiển thị đầu ra ASCII. Bao gồm tất cả các bài kiểm tra và đầu ra của tập tin MIDI kiểm tra bên dưới.

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

Hãy thử trực tuyến!

Tiền thưởng: 761 byte - 25% = 571

Chức năng $m()sẽ tải một tệp MIDI tiêu chuẩn (cục bộ hoặc bằng URL) và trả về một mảng các bản nhạc, mỗi bản chứa một mảng ở định dạng ghi chú đã chỉ định cho tất cả các bản nhạc tệp 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;};

Xem nó trực tuyến! Rõ ràng TIO được hộp cát để không cho phép các yêu cầu từ xa hoặc các tệp cục bộ, vì vậy bạn sẽ phải chạy mã này cục bộ để xem nó hoạt động. [Thử nghiệm] [TIO-jrwa60tu] đầu tiên trong chức năng hiển thị bao gồm kết quả mảng từ tệp MIDI thử nghiệm .

Thường xuyên tải tập tin MIDI

$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

Một tệp MIDI thử nghiệm của "Ode to Joy" có thể được sử dụng được tải xuống ở đây . Ví dụ sử dụng:

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

Đầu ra tệp 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

Ghi chú

Ở định dạng MIDI, các sự kiện Note On / Note Off là nguyên tử, nghĩa là bạn thấy sự kiện Note On vào một thời điểm nhất định cho một ghi chú nhất định (giả sử E5) và nó ngụ ý rằng nó sẽ phát cho đến khi sự kiện Note Off cho một ghi chú E5 khác được nhìn thấy. Do đó, cần phải phân tích các sự kiện MIDI và kết hợp một Ghi chú đã cho với Ghi chú Tắt, đó là mã cho đó là297184 byte. Làm phức tạp thêm điều này, nó khá phổ biến ở định dạng MIDI tiêu chuẩn để xem Note On phù hợp tiếp theo với vận tốc 0 đại diện cho điều tương tự như Note Note.

Điều này bây giờ sẽ đọc chính xác các tệp có tốc độ 0 của Note On thay vì Note Off, vì vậy nên mở hầu hết các tệp tiêu chuẩn.

Hãy cẩn thận

Đây không phải là một cách thực hiện đầy đủ định dạng MIDI, tuy nhiên tôi đã thử nghiệm điều này với một bộ sưu tập các tệp MIDI khá rộng rãi và nó đọc tất cả chúng một cách độc đáo.

Sự đệ trình này chưa được đánh golf đến mức cực đoan, vì vậy hoàn toàn có khả năng điều này có thể được thực hiện nhỏ hơn. Tôi nghĩ rằng rất khó có khả năng phần thưởng giảm 25% sẽ bù đắp mã cần thiết để đọc tệp MIDI tiêu chuẩn. Là đệ trình nhỏ nhất (hiện tại) chỉ hiển thị ASCII106 65 byte, nó sẽ yêu cầu các thường trình tệp MIDI được thực hiện trong 2521 byte để đánh bại. Tôi sẽ thách thức bất cứ ai làm điều đó (không sử dụng ngôn ngữ tích hợp hoặc mô-đun). :)


Đây là một câu trả lời tuyệt vời. Nhìn lại thử thách này, tôi đồng ý rằng số tiền thưởng có thể sẽ không làm giảm điểm số đủ để tính chi phí cho việc đọc tệp MIDI. (Tôi nghĩ rằng tiền thưởng ngày nay không được khuyến khích.) Tuy nhiên, tôi rất ấn tượng rằng bạn đã thực hiện thử thách tiền thưởng. Tôi có thể cung cấp cho bạn một tiền thưởng tốt cho nó.
TNT

@TNT, cảm ơn! Thực sự rất thích làm điều đó và thật thú vị khi thử các thói quen định dạng tệp golf cho một thứ gì đó ngớ ngẩn như SMF. Thử thách lớn!
640KB

5

Ruby, 106 byte

Đây là niềm vui Tôi không chắc tại sao không ai thử nó.

Hàm này nhận đầu vào là bốn đối số mảng và trả về một chuỗi các chuỗi, một cho mỗi dòng của biểu đồ.

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

Lưu ý: Điều này tùy ý giả định rằng sẽ không có hơn 10.000 tick. Nếu bạn chạy nó trong thiết bị đầu cuối của bạn, tôi khuyên lessbạn nên chuyển nó sang để bạn có thể cuộn theo chiều ngang. Bạn có thể thay đổi 1e4nếu bạn muốn có nhiều tích tắc hơn 9e9, nhưng điều đó sẽ mất một hoặc hai terabyte RAM.

Xem nó trên repl.it: https://repl.it/Cx4I/1


Cảm ơn đã gửi! Nhưng thật kỳ lạ là tôi không thể thấy đầu ra thay thế (tất cả những gì tôi có thể thấy là các con số 127-0 với rất nhiều lợi nhuận giữa chúng). Tôi chưa bao giờ sử dụng thay thế trước đây vì vậy tôi sẽ không biết tại sao. Bạn có thể đề xuất một cách để tôi thấy đầu ra đúng không?
TNT

Điều đó thật lạ. Nó làm việc cho tôi. Tôi mới không ở máy tính, nhưng đây là ảnh chụp màn hình từ điện thoại của tôi: i.stack.imgur.com/3UCyn.jpg
Jordan

Cảm ơn ảnh chụp màn hình. Tôi nghĩ vấn đề có thể là trình duyệt web mà tôi đang sử dụng, vì vậy tôi sẽ thử nó ở một trình duyệt khác sau. +1 từ tôi mặc dù. :)
TNT

2

Python 2, 163 160 156 145 byte

Đây không phải là cách tốt nhất để làm điều đó, nhưng nó là một trong những cách đơn giản nhất. Nếu tôi có thể tìm ra cách thay thế các phần của chuỗi mà không biến chúng thành danh sách, thay thế và biến chúng trở lại thành chuỗi, điều đó sẽ rất hữu ích ở đây. Gợi ý chơi golf chào mừng.

Chỉnh sửa: 18 byte nhờ Leaky Nun. Hãy thử nó trên 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]

@LeakyNun Rất tiếc, xấu của tôi
Loovjo

Bạn có thể sử dụng thay thế biểu thức thường xuyên? Trong Ruby một cái gì đó str.sub(/(?<=.{20}).{3}/,"foo")tương đương với str[20,3] = "foo". Tất nhiên, điều đó có nghĩa là xây dựng biểu thức chính quy bằng phép nội suy / ghép chuỗi với các biến chỉ số / độ dài mà giá rẻ trong các byte Ruby, nhưng có thể không phải bằng Python.
Jordan

1

Japt , 65 byte

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

Hãy thử trực tuyến!

Lấy đầu vào là một danh sách các ghi chú trong định dạng [pitch, start_tick, end_tick, velocity]. Nếu lấy đầu vào làm danh sách riêng là bắt buộc (nghĩa là một danh sách chứa tất cả các nốt, một danh sách chứa tất cả vận tốc, v.v.), có thể được thực hiện với chi phí 1 byte .

Giải trình:

®Æ"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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.