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 @ 9:d3c644cd010e

History | View | Annotate | Download (4.69 KB)

1
#!/bin/perl
2
#
3
#    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
use strict;
30

    
31
# read the output from midifile, sort it into "note column" groups
32

    
33
my $timebase = 120;
34
my $beatsperbar = 4;
35
my $seentimesig = 0;
36

    
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
    } 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
    } 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
	if ($ot <= $t) {
125
	    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
}
164