use strict;
use warnings;
-use MIDI;
+use MIDI::Event;
use MIDI::Opus;
+use MIDI::Track;
my ($filename, @others) = @ARGV;
my $opus = MIDI::Opus->new({from_file => $filename});
while(<STDIN>)
{
chomp;
- my @arg = split /\s+/, $_;
+ my @arg = grep { $_ ne '' } split /\s+/, $_;
my $cmd = shift @arg;
print "Executing: $cmd @arg\n";
- if($cmd eq 'clean')
+ if($cmd eq '#')
+ {
+ # Just a comment.
+ }
+ elsif($cmd eq 'clean')
{
my $tracks = $opus->tracks_r();
$tracks->[$_]->events_r([clean($tracks->[$_]->events())])
{
my $tracks = $opus->tracks_r();
my ($track, $channel, $program) = @arg;
- for(($track eq '*') ? (0..@$tracks-1) : $track)
+ for my $t(($track eq '*') ? (0..@$tracks-1) : $track)
{
my @events = ();
- my $added = 0;
- for(abstime $tracks->[$_]->events())
+ my %added = ();
+ for(abstime $tracks->[$t]->events())
{
my $p = $chanpos{$_->[0]};
if(defined $p)
{
next
if $_->[0] eq 'patch_change';
- if(!$added)
+ if(!$added{$t}{$c})
{
push @events, ['patch_change', $_->[1], $c-1, $program-1]
if $program;
- $added = 1;
+ $added{$t}{$c} = 1;
}
}
}
push @events, $_;
}
- $tracks->[$_]->events_r([reltime @events]);
+ $tracks->[$t]->events_r([reltime @events]);
+ }
+ }
+ elsif($cmd eq 'control')
+ {
+ my $tracks = $opus->tracks_r();
+ my ($track, $channel, $control, $value) = @arg;
+ for my $t(($track eq '*') ? (0..@$tracks-1) : $track)
+ {
+ my @events = ();
+ my %added = ();
+ for(abstime $tracks->[$t]->events())
+ {
+ my $p = $chanpos{$_->[0]};
+ if(defined $p)
+ {
+ my $c = $_->[$p] + 1;
+ if($channel eq '*' || $c == $channel)
+ {
+ next
+ if $_->[0] eq 'control_change' && $_->[3] == $control;
+ if(!$added{$t}{$c})
+ {
+ push @events, ['control_change', $_->[1], $c-1, $control, $value]
+ if $value ne '';
+ $added{$t}{$c} = 1;
+ }
+ }
+ }
+ push @events, $_;
+ }
+ $tracks->[$t]->events_r([reltime @events]);
}
}
elsif($cmd eq 'transpose')
if(defined $p)
{
my $c = $_->[$p] + 1;
- if($channel eq '*' || $c == $channel)
+ if($channel eq '*' ? $c != 10 : $c == $channel)
{
if($_->[0] eq 'note_on' || $_->[0] eq 'note_off')
{
for(abstime $tracks->[$_]->events())
{
my $p = $chanpos{$_->[0]};
- if(defined $p)
+ if(!defined $p)
{
- my $c = $_->[$p] + 1;
- $c = $chanmap{$c} // $chanmap{'*'} // $c;
+ push @events, $_;
+ next;
+ }
+ my $c = $_->[$p] + 1;
+ my @c = split /,/, ($chanmap{$c} // $chanmap{'*'} // $c);
+ for my $c(@c) {
next
if $c == 0; # kill by setting channel to 0
- $_->[$p] = $c - 1;
+ my @copy = @$_;
+ $copy[$p] = $c - 1;
+ push @events, \@copy;
}
- push @events, $_;
}
$tracks->[$_]->events_r([reltime @events]);
}
my %notehash = ();
my $t = 0;
my $events = 0;
+ my $min = undef;
+ my $max = undef;
for($tracks->[$_]->events())
{
++$events;
$notehash{$_->[2]}{$_->[3]} = $t if $_->[0] eq 'note_on';
$notehash{$_->[2]}{$_->[3]} = undef if $_->[0] eq 'note_off';
$name = $_->[2] if $_->[0] eq 'track_name';
+ if($_->[0] eq 'note_on')
+ {
+ $min = $_->[3] if !defined $min || $_->[3] < $min;
+ $max = $_->[3] if !defined $max || $_->[3] > $max;
+ }
}
my $channels = join " ", map { sprintf "%s(%s)", $_, join ",", sort { $a <=> $b } keys %{$channels{$_}} } sort { $a <=> $b } keys %channels;
my @stuck = ();
print " $name" if defined $name;
print " (channel $channels)" if $channels ne "";
print " ($events events)" if $events;
- print " ($notes notes)" if $notes;
+ print " ($notes notes [$min-$max])" if $notes;
print " (notes @stuck stuck)" if @stuck;
print "\n";
}
print " ticks [value]\n";
print " retrack\n";
print " program <track|*> <channel|*> <program (1-based)>\n";
+ print " control <track|*> <channel|*> <control> <value>\n";
print " transpose <track|*> <channel|*> <delta>\n";
print " channel <track|*> <channel|*> <channel> [<channel> <channel> ...]\n";
print " percussion <track|*> <channel|*> <from> <to> [<from> <to> ...]\n";
print " tracks [trackno] [trackno] ...\n";
+ print " save <filename.mid>\n";
}
print "Done with: $cmd @arg\n";
}