7rpn’s blog: うわああああな日常

好きなことをつらつらと。

RubyとPerl比較: MIDIを読み込んで編集する

MIDIを編集したい。

MIDIをプログラム組んで編集したいなって思ったとき,これまでは過去に英華を極めたPerlMIDI読み込みをやってました。時代的にPerlが一番強いときにMIDIもかなり流行ったと思うので。
それをRubyでやったら超楽にできちゃったやんっていう話。

とりあえずダイナミクスを可能な限り広げるスクリプトPerlRubyで書いてみました。 具体的には,MIDIの内容がベロシティ60-100の間の音しかない場合,それをベロシティ1-127の範囲に広げるって感じのスクリプト

Perlから。

#!/usr/bin/perl
use MIDI;
use MIDI::Simple;
use MIDI::Score;
use Data::Dumper;
use List::Util qw(max);
use List::Util qw(min);

local $Data::Dumper::Indent = 0;

my $midi = shift;
if( !defined($midi) ) {
  print "ABORTED!\nUsage:\n\$ perl midi.pl something.midi\ninput midi file.";
  die;
}

print $midi;
print "\n";
my @s;
my @channel;
my @v_ch;
my @mtrack;
my $opus = MIDI::Opus->new({ "from_file" => $midi || die });
my $i = 0;
foreach my $t ($opus->tracks) {
  if($t->events_r and @{$t->events_r}) {
    my($score_r, $ticks) = MIDI::Score::events_r_to_score_r( MIDI::Event::copy_structure( $t->events_r )); # event kara score he
    
    @notes = ();
    my $count = 0;
    while ($count < scalar(@$score_r)){
      if(${$score_r}[$count]->[0]=~/note/){
        print ${$score_r}[$count]->[5];
        print "-";
        print ${$score_r}[$count]->[4];
        print "\n";
        push(@notes,${$score_r}[$count]->[5]);
      } 
      $count ++;
    }
    my $min_note = min @notes;
    my $max_note = max @notes;

    $count = 0;
    while ($count < scalar(@$score_r)){
      if(${$score_r}[$count]->[0]=~/note/){
        ${$score_r}[$count]->[5] = 1 + int ((${$score_r}[$count]->[5] - $min_note) * (126/($max_note-$min_note)));
      } 
      $count ++;
    }
      
    my($events_r, $ticks2) = MIDI::Score::score_r_to_events_r(MIDI::Event::copy_structure( $score_r)); # score kara event he
    push @s,$events_r;
    my $x = MIDI::Track->new({ 'events' => $events_r });
    push @v_ch , $x;
    $i++;

    push @ticks, $ticks;
    if(my $time_now) {
      unshift @{$t->events_r}, ['text_event', $time_now, "_"];
    }
  } 

  else {
    DEBUG > 1 and print " * Track is eventless.  Skipping.\n";
  }
  push @out_tracks, $t;
}
push @granularities, $opus->ticks;

print $ticks[3];
my $o = MIDI::Opus->new(
{ 'format' => 1, 'ticks' => 480, 'tracks' => [ @v_ch ] } );
$o->write_to_file( 'output.mid' );

Perlだとこんな感じ。ざっと約80行。 正直Perlはわけわからないので酷い書き方してると思います。笑

これと同じ処理をRubyで書いたら。

require 'midilib'

seq = MIDI::Sequence.new()

File.open(ARGV[0], 'rb') do |file|
    seq.read(file) do |track, num_tracks, i|
        puts "read track #{i} of #{num_tracks}"
    end
end

seq.each do |track|
    notes = track.map{|event| event.velocity if MIDI::NoteEvent === event and event.note}.compact 
    track.each do |event|      
        if MIDI::NoteEvent === event and event.note
            puts event.velocity.to_s + "-" + event.note.to_s
            event.velocity = 1 + ((event.velocity - notes.min) * (126/(notes.max-notes.min))).to_i
        end
    end
end

File.open('output.midi', 'wb') { |file| seq.write(file) }

なんと20行!!びびる

Perlはよく知らないのでもっと行減らせるんだろうなーとは思ってますが,それでも4倍の差がでるのはすごいなーと思います。
まぁPerlやってる人が書いたら20行くらいに行減らせそうですけどね...

というかコードの行数とかどうでも良くて,自分にとっての分かりやすさが段違いです。

MIDIの読み込みでPerl以外の良い方法をこれまで知らなかったのでそのためだけにPerl使ってました。
が,ついにPerlともお別れの時が来たようです。

さようならPerl。君のオブジェクト指向は自分にはわけが分からないよ。