显示MIDI音轨


17

背景

MIDI文件与WAV或MP3音频文件完全不同。MP3和WAV文件包含表示音频“记录”的字节,而MIDI文件具有存储在MIDI事件中的一系列MIDI消息,这些信息通知MIDI合成器要播放的虚拟乐器或MIDI音序器应使用的播放速度。这些消息存储在轨道中,轨道的集合构成一个MIDI序列,其事件可由音序器分析,并将其消息从音序器传输到合成器的接收器。

在大多数情况下,存储在MIDI事件中的MIDI消息是“音符开”消息,它告诉合成器播放特定的音符,或者是“音符关”消息,告诉合成器停止播放音符。这些消息包含两个数据字节,第一个数据字节通知合成器该音符的速度(较高的速度会导致一个更大的音符),而第二个字节则通知合成器该音符的演奏(即中音C)。事件本身还包含刻度,用于告知音序器何时发送消息。

挑战

面临的挑战是编写一个完整的程序或函数,以单轨MIDI序列分析一系列Note On和Note Off MIDI消息,并向STDOUT输出一个图表,该图表显示何时打开特定音符,何时关闭这些音符以及这些音符的速度。图表的纵轴表示音符值,应按以下说明进行标记,横轴表示以MIDI音调为单位的时间(尽管应保持未标记状态,以减少复杂性和间距问题)。

您的输入可能是四个单独的数组或列表,每个数组或列表包含一系列整数值。包含四个带有一系列整数值的子数组/子列表的二维数组或列表;或任何其他方便的方式;这表示音轨中具有“音符开启”和“音符关闭”消息的MIDI事件集合。这些数组中的第一个数组中的值指定音符,第二个数组中的音符指定力度,事件滴答声中的第三个音符,和音符关闭事件音符的第四个音符。例如,给定四个这样的数组:

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

分析每个数组的第一个元素会产生两个事件:在滴答0处发生的事件,其中包含一条消息,其中包含Note On命令,note 60(中间C)和note速度为20;以及在第2滴答处发生的事件,并带有一条带有“音符关闭”命令且音符和力度相同的消息。

规则

图表的左侧应显示降序显示的数字0到127(代表音符值),音符开始时,每个音符的持续时间(音符关闭记号减去音符开启记号)以及音符的力度。表示音符的符号取决于其速度:

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

您可以假设以下内容:

  • 音符和力度的值将在[0,127]范围内。
  • 四个阵列中每个阵列的长度将始终彼此相等。

这里有一些例子:

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

这是一个示例,显示《欢乐颂》的前几个音符:

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

您可以将分数降低25%如果提交的内容以实际的MIDI序列为输入,可以分析您选择的任何音轨的Note On和Note Off消息,前提是它至少包含四个带有Note On和Note Off消息的事件,然后输出,如上所述的图表。

这是代码高尔夫,所以最短的代码获胜。祝好运!

Answers:


6

的PHP,127 + 571 = 698总得分*

好吧,我要领取奖金。:)这将获取标准的MIDI文件并显示输出。

我将上面的得分分为主要挑战(分析音符开/关并显示为图表)和奖金挑战(从标准MIDI读取输入),以使得分更具可比性。

主要:170个字节-25%= 127

对于主函数,该函数$d()采用所需的数组并显示ASCII输出。包括以下所有测试和测试MIDI文件的输出。

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

在线尝试!

奖金:761字节-25%= 571

函数$m()将加载一个标准MIDI文件(本地或通过URL)并返回一个音轨数组,每个音轨包含一个用于所有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;};

在线查看! 显然,TIO被沙箱化,以不允许远程请求或本地文件,因此您必须在本地运行此代码才能看到它的实际效果。显示功能中的第一个[tests] [TIO-jrwa60tu]包含来自测试MIDI文件的阵列结果。

取消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

可在此处下载的“ Ode to Joy”测试MIDI文件。使用示例:

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

“欢乐颂” MIDI文件输出

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

笔记

在MIDI格式中,“音符打开” /“音符关闭”事件是原子的,这意味着您会在某个时间看到给定音符的“音符打开”事件(例如E5),这意味着它将一直播放到另一个E5音符的“音符关闭”事件为止被看到。因此,有必要分析MIDI事件并将给定的“音符开”与“音符关”相匹配,该代码为297184个字节。更为复杂的是,在标准MIDI格式中,通常会看到随后的匹配音符打开,其力度0代表与音符关闭相同的事物。

现在,这将正确读取速度为零的“音符开”而不是“音符关”的文件,因此应打开大多数标准文件。

注意事项

这绝不是MIDI格式的完整实现,但是我已经用大量MIDI文件进行了测试,并且可以很好地读取它们。

这种提交方式尚未达到极限,因此完全有可能将其缩小。我确实认为25%的分数降低奖金不太可能会抵消读取标准MIDI文件所需的代码。作为ASCII显示的(当前)最小提交为106 65个字节,这需要在以下位置实现MIDI文件例程: 25击败21个字节。我会挑战任何人(不使用内置语言或模块)。:)


这是一个了不起的答案。回顾这一挑战,我确实同意奖金数额可能不会减少分数,不足以弥补读取MIDI文件的开销。(无论如何,我认为现在不鼓励奖金。)然而,您参加奖金挑战给我留下了深刻的印象。我可能会为您提供赏金。
TNT

@TNT,谢谢!真的很喜欢它,尝试将文件格式例程用于像SMF这样愚蠢的东西很有趣。巨大的挑战!
640KB

5

Ruby,106个字节

很好玩 我不确定为什么没有人尝试过。

此函数将输入作为四个数组参数,并返回一个字符串数组,图表的每一行一个。

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

注意:这是任意地假设不会超过10,000个刻度。如果您在终端中运行它,建议将其管道输送到它,less以便您可以水平滚动。您可以更改1e4,直到一直到9e9,它们都需要1 TB或2 TB的RAM。

在repl.it上查看:https ://repl.it/Cx4I/1


感谢您的提交!但是奇怪的是,我看不到repl的输出(我只能看到数字127-0 之间有很多回报)。我以前从未使用过repl,所以我也不知道为什么。您能为我提供一种正确查看输出的方法吗?
TNT

真奇怪 这个对我有用。我现在不在电脑
约旦

感谢您的截图。我认为问题可能出在我正在使用的网络浏览器上,因此我稍后将在其他浏览器中进行尝试。不过我还是+1。:)
TNT

2

Python 2中,163个 160 156 145字节

这不是最高尔夫的方法,但这是最简单的方法之一。如果我能弄清楚如何替换部分字符串而不将它们变成列表,替换并将它们重新变成字符串,那么这将非常有帮助。欢迎打高尔夫球。

编辑: 18字节感谢泄漏尼姑。在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哎呀,我不好
Loovjo

可以使用正则表达式替换吗?在Ruby中,类似str.sub(/(?<=.{20}).{3}/,"foo")相当于str[20,3] = "foo"。当然,这意味着要通过使用索引/长度变量的字符串插值/串联来构造正则表达式-在Ruby字节中便宜,但在Python中可能不便宜。
约旦

1

Japt,65个字节

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

在线尝试!

将输入作为格式的注释列表[pitch, start_tick, end_tick, velocity]。如果将输入作为单独的列表是强制性的(即,一个包含所有音高的列表,一个包含所有速度的列表等),则可以以1个字节为代价来实现。

说明:

®Æ"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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.