9 my ($filename, @others) = @ARGV;
10 my $opus = MIDI::Opus->new({from_file => $filename});
18 channel_after_touch => 2,
19 pitch_wheel_change => 2
23 set_tempo => sub { 1; },
24 note_off => sub { 1; },
25 note_on => sub { 1; },
26 control_change => sub { $_[3] == 64; },
32 return map { [$_->[0], $t += $_->[1], @{$_}[2..(@$_-1)]]; } @_;
38 return map { my $tsave = $t; $t = $_->[1]; [$_->[0], $t - $tsave, @{$_}[2..(@$_-1)]]; } @_;
43 return reltime grep { ($isclean{$_->[0]} // sub { 0; })->(@$_) } abstime @_;
48 my $opus2 = MIDI::Opus->new({from_file => $_});
49 if($opus2->ticks() != $opus->ticks())
51 my $tickfactor = $opus->ticks() / $opus2->ticks();
54 $_->events(reltime map { $_->[1] = int($_->[1] * $tickfactor + 0.5); $_; } abstime $_->events());
57 $opus->tracks($opus->tracks(), $opus2->tracks());
63 my @arg = split /\s+/, $_;
65 print "Executing: $cmd @arg\n";
70 elsif($cmd eq 'clean')
72 my $tracks = $opus->tracks_r();
73 $tracks->[$_]->events_r([clean($tracks->[$_]->events())])
78 print $opus->dump({ dump_tracks => 1 });
80 elsif($cmd eq 'ticks')
84 $opus->ticks($arg[0]);
88 print "Ticks: ", $opus->ticks(), "\n";
91 elsif($cmd eq 'tricks')
93 print "haha, very funny\n";
95 elsif($cmd eq 'retrack')
97 my $tracks = $opus->tracks_r();
101 for(abstime $tracks->[$_]->events())
103 my $p = $chanpos{$_->[0]};
106 my $c = $_->[$p] + 1;
107 push @{$newtracks[$c]}, $_;
111 push @{$newtracks[0]}, $_;
115 $opus->tracks_r([map { ($_ && @$_) ? MIDI::Track->new({ events => [reltime @$_] }) : () } @newtracks]);
117 elsif($cmd eq 'program')
119 my $tracks = $opus->tracks_r();
120 my ($track, $channel, $program) = @arg;
121 for my $t(($track eq '*') ? (0..@$tracks-1) : $track)
125 for(abstime $tracks->[$t]->events())
127 my $p = $chanpos{$_->[0]};
130 my $c = $_->[$p] + 1;
131 if($channel eq '*' || $c == $channel)
134 if $_->[0] eq 'patch_change';
137 push @events, ['patch_change', $_->[1], $c-1, $program-1]
145 $tracks->[$t]->events_r([reltime @events]);
148 elsif($cmd eq 'control')
150 my $tracks = $opus->tracks_r();
151 my ($track, $channel, $control, $value) = @arg;
152 for my $t(($track eq '*') ? (0..@$tracks-1) : $track)
156 for(abstime $tracks->[$t]->events())
158 my $p = $chanpos{$_->[0]};
161 my $c = $_->[$p] + 1;
162 if($channel eq '*' || $c == $channel)
165 if $_->[0] eq 'control_change' && $_->[3] == $control;
168 push @events, ['control_change', $_->[1], $c-1, $control, $value]
176 $tracks->[$t]->events_r([reltime @events]);
179 elsif($cmd eq 'transpose')
181 my $tracks = $opus->tracks_r();
182 my ($track, $channel, $delta) = @arg;
183 for(($track eq '*') ? (0..@$tracks-1) : $track)
185 for($tracks->[$_]->events())
187 my $p = $chanpos{$_->[0]};
190 my $c = $_->[$p] + 1;
191 if($channel eq '*' ? $c != 10 : $c == $channel)
193 if($_->[0] eq 'note_on' || $_->[0] eq 'note_off')
202 elsif($cmd eq 'channel')
204 my $tracks = $opus->tracks_r();
205 my ($track, %chanmap) = @arg;
206 for(($track eq '*') ? (0..@$tracks-1) : $track)
209 for(abstime $tracks->[$_]->events())
211 my $p = $chanpos{$_->[0]};
217 my $c = $_->[$p] + 1;
218 my @c = split /,/, ($chanmap{$c} // $chanmap{'*'} // $c);
221 if $c == 0; # kill by setting channel to 0
224 push @events, \@copy;
227 $tracks->[$_]->events_r([reltime @events]);
230 elsif($cmd eq 'percussion')
232 my $tracks = $opus->tracks_r();
233 my ($track, $channel, %map) = @arg;
234 for(($track eq '*') ? (0..@$tracks-1) : $track)
237 for(abstime $tracks->[$_]->events())
239 my $p = $chanpos{$_->[0]};
242 my $c = $_->[$p] + 1;
243 if($channel eq '*' || $c == $channel)
245 if($_->[0] eq 'note_on' || $_->[0] eq 'note_off')
247 if(length $map{$_->[3]})
249 $_->[3] = $map{$_->[3]};
251 elsif(exists $map{$_->[3]})
260 $tracks->[$_]->events_r([reltime @events]);
263 elsif($cmd eq 'tracks')
265 my $tracks = $opus->tracks_r();
278 next if $taken{$_}++ and not $force;
279 push @t, $tracks->[$_];
281 $opus->tracks_r(\@t);
296 for($tracks->[$_]->events())
299 $_->[0] = 'note_off' if $_->[0] eq 'note_on' and $_->[4] == 0;
301 my $p = $chanpos{$_->[0]};
304 my $c = $_->[$p] + 1;
305 $channels{$c} //= {};
306 if($_->[0] eq 'patch_change')
308 ++$channels{$c}{$_->[3]};
311 ++$notes if $_->[0] eq 'note_on';
312 $notehash{$_->[2]}{$_->[3]} = $t if $_->[0] eq 'note_on';
313 $notehash{$_->[2]}{$_->[3]} = undef if $_->[0] eq 'note_off';
314 $name = $_->[2] if $_->[0] eq 'track_name';
315 if($_->[0] eq 'note_on')
317 $min = $_->[3] if !defined $min || $_->[3] < $min;
318 $max = $_->[3] if !defined $max || $_->[3] > $max;
321 my $channels = join " ", map { sprintf "%s(%s)", $_, join ",", sort { $a <=> $b } keys %{$channels{$_}} } sort { $a <=> $b } keys %channels;
323 while(my ($k1, $v1) = each %notehash)
325 while(my ($k2, $v2) = each %$v1)
327 push @stuck, sprintf "%d:%d@%.1f%%", $k1+1, $k2, $v2 * 100.0 / $t
331 print " $name" if defined $name;
332 print " (channel $channels)" if $channels ne "";
333 print " ($events events)" if $events;
334 print " ($notes notes [$min-$max])" if $notes;
335 print " (notes @stuck stuck)" if @stuck;
340 elsif($cmd eq 'save')
342 $opus->write_to_file($arg[0]);
346 print "Unknown command, allowed commands:\n";
349 print " ticks [value]\n";
351 print " program <track|*> <channel|*> <program (1-based)>\n";
352 print " control <track|*> <channel|*> <control> <value>\n";
353 print " transpose <track|*> <channel|*> <delta>\n";
354 print " channel <track|*> <channel|*> <channel> [<channel> <channel> ...]\n";
355 print " percussion <track|*> <channel|*> <from> <to> [<from> <to> ...]\n";
356 print " tracks [trackno] [trackno] ...\n";
357 print " save <filename.mid>\n";
359 print "Done with: $cmd @arg\n";