changeset 2:1a4000271db6 midifile

* Add note-column calculation script
author cannam
date Thu, 11 Feb 2010 12:26:07 +0000
parents 3e65e0344413
children 5abb6a523d27
files midisort.pl
diffstat 1 files changed, 120 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/midisort.pl	Thu Feb 11 12:26:07 2010 +0000
@@ -0,0 +1,120 @@
+use strict;
+
+# read the output from midifile, sort it into "note column" groups
+
+my $timebase = 120;
+my $beatsperbar = 3;
+
+sub fmtm { # function to convert time to bar number
+    int($_[0] / ($timebase * $beatsperbar)) + 1;
+}
+sub fmt { # function to convert time to beat number
+    my $t = $_[0] / $timebase;
+    while ($t >= $beatsperbar) { $t -= $beatsperbar; }
+    $t + 1;
+}
+sub fmtd { # function to convert duration to beat count
+    $_[0] / $timebase;
+}
+
+my %noteons;
+my %noteoffs;
+my %channels;
+my %grouptimes;
+
+while (<>) {
+
+    my ($t,$c,$d,$p);
+
+    # split events into time, channel, duration, pitch
+    if (/^(\d+): Note: channel (\d+) duration (\d+) pitch (\d+)/) {
+	$t = $1;
+	$c = $2;
+	$d = $3;
+	$p = $4;
+    } else {
+	next;
+    }
+
+    # record note-on times, plus all active channels
+    $noteons{$t}{$c}{$d}{$p}++;
+    $channels{$c}++;
+
+    # start times of note groups: whenever a note starts or ends
+    $grouptimes{$t}++;
+    $grouptimes{$t + $d}++;
+}
+
+my $prevt;
+my %groupdurs;
+
+# work out the duration of each note-group
+for my $t (sort { $a <=> $b } keys %grouptimes) { # sort group starts numerically
+    if (defined $prevt) {
+	# duration of the previous note-group is that from its start
+	# time to that of the current one
+	$groupdurs{$prevt} = $t - $prevt;
+    }
+    $prevt = $t;
+}
+
+my $n = 0;
+
+for my $t (sort { $a <=> $b } keys %groupdurs) {
+
+    printf "NOTEGROUP:%d  Bar:%d  Beat:%.1f  Dur:%.1f",
+	    $n, fmtm($t), fmt($t), fmtd($groupdurs{$t});
+
+    # within this group we want to output a pitch number for every
+    # "known" channel, i.e. everything in keys %channels.  these
+    # pitches may be based on notes starting at this time (via noteons
+    # hash) or notes that have started previously but not yet finished
+    # (via noteoffs hash)
+
+    print "  Midi: [";
+
+    my @pitches; # array of all pitches (ordered by channel) in this group
+
+    for my $ot (keys %noteoffs) {
+	# prune notes that have ended already or are ending now
+	if ($ot le $t) {
+	    delete $noteoffs{$ot};
+	}
+    }
+
+    for my $c (sort { $a <=> $b } keys %channels) {
+
+	my @here; # array of all pitches on this channel
+
+	for my $d (keys %{$noteons{$t}{$c}}) {
+	    for my $p (keys %{$noteons{$t}{$c}{$d}}) {
+		# add notes that begin at this time, indexed by end time
+		$noteoffs{$t + $d}{$c}{$p}++;
+	    }
+	}
+
+	for my $ot (keys %noteoffs) {
+	    # note(s) still sounding, add to group if in channel
+	    for my $p (keys %{$noteoffs{$ot}{$c}}) {
+		push @here, $p;
+	    }
+	}
+
+	if (@here > 0) {
+	    # if we have any notes on this channel in this group,
+	    # prepare to print them separated by spaces
+	    push @pitches, (join " ", @here);
+	} else {
+	    # otherwise prepare to print 'rest'
+	    push @pitches, "'rest'";
+	}
+    }
+
+    # now print the channel note lists separated by comma
+    print join ", ", @pitches;
+
+    print "]\n";
+
+    ++$n;
+}
+