To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Revision:

root / midisort.pl

History | View | Annotate | Download (4.69 KB)

1 9:d3c644cd010e Chris
#!/bin/perl
2
#
3 4:a98a66b43882 Chris
#    This file copyright 2010 Chris Cannam.
4
#
5
#    Permission is hereby granted, free of charge, to any person
6
#    obtaining a copy of this software and associated documentation
7
#    files (the "Software"), to deal in the Software without
8
#    restriction, including without limitation the rights to use, copy,
9
#    modify, merge, publish, distribute, sublicense, and/or sell copies
10
#    of the Software, and to permit persons to whom the Software is
11
#    furnished to do so, subject to the following conditions:
12
#
13
#    The above copyright notice and this permission notice shall be
14
#    included in all copies or substantial portions of the Software.
15
#
16
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
20
#    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21
#    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
#    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
#
24
#    Except as contained in this notice, the names of the authors
25
#    shall not be used in advertising or otherwise to promote the sale,
26
#    use or other dealings in this Software without prior written
27
#    authorization.
28
29 2:1a4000271db6 cannam
use strict;
30
31
# read the output from midifile, sort it into "note column" groups
32
33
my $timebase = 120;
34 3:5abb6a523d27 cannam
my $beatsperbar = 4;
35
my $seentimesig = 0;
36 2:1a4000271db6 cannam
37
sub fmtm { # function to convert time to bar number
38
    int($_[0] / ($timebase * $beatsperbar)) + 1;
39
}
40
sub fmt { # function to convert time to beat number
41
    my $t = $_[0] / $timebase;
42
    while ($t >= $beatsperbar) { $t -= $beatsperbar; }
43
    $t + 1;
44
}
45
sub fmtd { # function to convert duration to beat count
46
    $_[0] / $timebase;
47
}
48
49
my %noteons;
50
my %noteoffs;
51
my %channels;
52
my %grouptimes;
53
54
while (<>) {
55
56
    my ($t,$c,$d,$p);
57
58
    # split events into time, channel, duration, pitch
59
    if (/^(\d+): Note: channel (\d+) duration (\d+) pitch (\d+)/) {
60
	$t = $1;
61
	$c = $2;
62
	$d = $3;
63
	$p = $4;
64 3:5abb6a523d27 cannam
    } elsif (/^Timing division: (\d+)/) {
65
	$timebase = $1;
66
	print STDERR "Set timebase to $timebase from input data\n";
67
	next;
68
    } elsif (/^(\d+): Time signature: (\d+)\//) {
69
	if ($seentimesig != 0 && $beatsperbar != $2) {
70
	    print STDERR "WARNING: File uses more than one time signature, not supported here\n";
71
	}
72
	$beatsperbar = $2;
73
	$seentimesig++;
74
	print STDERR "Set beat count per bar to $beatsperbar from input data\n";
75
	next;
76
    } elsif (/^SMPTE/) {
77
	print STDERR "WARNING: File uses SMPTE timing, expect incorrect results\n";
78
	next;
79 2:1a4000271db6 cannam
    } else {
80
	next;
81
    }
82
83
    # record note-on times, plus all active channels
84
    $noteons{$t}{$c}{$d}{$p}++;
85
    $channels{$c}++;
86
87
    # start times of note groups: whenever a note starts or ends
88
    $grouptimes{$t}++;
89
    $grouptimes{$t + $d}++;
90
}
91
92
my $prevt;
93
my %groupdurs;
94
95
# work out the duration of each note-group
96
for my $t (sort { $a <=> $b } keys %grouptimes) { # sort group starts numerically
97
    if (defined $prevt) {
98
	# duration of the previous note-group is that from its start
99
	# time to that of the current one
100
	$groupdurs{$prevt} = $t - $prevt;
101
    }
102
    $prevt = $t;
103
}
104
105
my $n = 0;
106
107
for my $t (sort { $a <=> $b } keys %groupdurs) {
108
109
    printf "NOTEGROUP:%d  Bar:%d  Beat:%.1f  Dur:%.1f",
110
	    $n, fmtm($t), fmt($t), fmtd($groupdurs{$t});
111
112
    # within this group we want to output a pitch number for every
113
    # "known" channel, i.e. everything in keys %channels.  these
114
    # pitches may be based on notes starting at this time (via noteons
115
    # hash) or notes that have started previously but not yet finished
116
    # (via noteoffs hash)
117
118
    print "  Midi: [";
119
120
    my @pitches; # array of all pitches (ordered by channel) in this group
121
122
    for my $ot (keys %noteoffs) {
123
	# prune notes that have ended already or are ending now
124 3:5abb6a523d27 cannam
	if ($ot <= $t) {
125 2:1a4000271db6 cannam
	    delete $noteoffs{$ot};
126
	}
127
    }
128
129
    for my $c (sort { $a <=> $b } keys %channels) {
130
131
	my @here; # array of all pitches on this channel
132
133
	for my $d (keys %{$noteons{$t}{$c}}) {
134
	    for my $p (keys %{$noteons{$t}{$c}{$d}}) {
135
		# add notes that begin at this time, indexed by end time
136
		$noteoffs{$t + $d}{$c}{$p}++;
137
	    }
138
	}
139
140
	for my $ot (keys %noteoffs) {
141
	    # note(s) still sounding, add to group if in channel
142
	    for my $p (keys %{$noteoffs{$ot}{$c}}) {
143
		push @here, $p;
144
	    }
145
	}
146
147
	if (@here > 0) {
148
	    # if we have any notes on this channel in this group,
149
	    # prepare to print them separated by spaces
150
	    push @pitches, (join " ", @here);
151
	} else {
152
	    # otherwise prepare to print 'rest'
153
	    push @pitches, "'rest'";
154
	}
155
    }
156
157
    # now print the channel note lists separated by comma
158
    print join ", ", @pitches;
159
160
    print "]\n";
161
162
    ++$n;
163
}