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 @ 4:a98a66b43882

History | View | Annotate | Download (4.67 KB)

1

    
2
#    This file copyright 2010 Chris Cannam.
3
#  
4
#    Permission is hereby granted, free of charge, to any person
5
#    obtaining a copy of this software and associated documentation
6
#    files (the "Software"), to deal in the Software without
7
#    restriction, including without limitation the rights to use, copy,
8
#    modify, merge, publish, distribute, sublicense, and/or sell copies
9
#    of the Software, and to permit persons to whom the Software is
10
#    furnished to do so, subject to the following conditions:
11
#
12
#    The above copyright notice and this permission notice shall be
13
#    included in all copies or substantial portions of the Software.
14
#
15
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
19
#    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20
#    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
#    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
#
23
#    Except as contained in this notice, the names of the authors
24
#    shall not be used in advertising or otherwise to promote the sale,
25
#    use or other dealings in this Software without prior written
26
#    authorization.
27

    
28
use strict;
29

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

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

    
36
sub fmtm { # function to convert time to bar number
37
    int($_[0] / ($timebase * $beatsperbar)) + 1;
38
}
39
sub fmt { # function to convert time to beat number
40
    my $t = $_[0] / $timebase;
41
    while ($t >= $beatsperbar) { $t -= $beatsperbar; }
42
    $t + 1;
43
}
44
sub fmtd { # function to convert duration to beat count
45
    $_[0] / $timebase;
46
}
47

    
48
my %noteons;
49
my %noteoffs;
50
my %channels;
51
my %grouptimes;
52

    
53
while (<>) {
54

    
55
    my ($t,$c,$d,$p);
56

    
57
    # split events into time, channel, duration, pitch
58
    if (/^(\d+): Note: channel (\d+) duration (\d+) pitch (\d+)/) {
59
	$t = $1;
60
	$c = $2;
61
	$d = $3;
62
	$p = $4;
63
    } elsif (/^Timing division: (\d+)/) {
64
	$timebase = $1;
65
	print STDERR "Set timebase to $timebase from input data\n";
66
	next;
67
    } elsif (/^(\d+): Time signature: (\d+)\//) {
68
	if ($seentimesig != 0 && $beatsperbar != $2) {
69
	    print STDERR "WARNING: File uses more than one time signature, not supported here\n";
70
	}
71
	$beatsperbar = $2;
72
	$seentimesig++;
73
	print STDERR "Set beat count per bar to $beatsperbar from input data\n";
74
	next;
75
    } elsif (/^SMPTE/) {
76
	print STDERR "WARNING: File uses SMPTE timing, expect incorrect results\n";
77
	next;
78
    } else {
79
	next;
80
    }
81

    
82
    # record note-on times, plus all active channels
83
    $noteons{$t}{$c}{$d}{$p}++;
84
    $channels{$c}++;
85

    
86
    # start times of note groups: whenever a note starts or ends
87
    $grouptimes{$t}++;
88
    $grouptimes{$t + $d}++;
89
}
90

    
91
my $prevt;
92
my %groupdurs;
93

    
94
# work out the duration of each note-group
95
for my $t (sort { $a <=> $b } keys %grouptimes) { # sort group starts numerically
96
    if (defined $prevt) {
97
	# duration of the previous note-group is that from its start
98
	# time to that of the current one
99
	$groupdurs{$prevt} = $t - $prevt;
100
    }
101
    $prevt = $t;
102
}
103

    
104
my $n = 0;
105

    
106
for my $t (sort { $a <=> $b } keys %groupdurs) {
107

    
108
    printf "NOTEGROUP:%d  Bar:%d  Beat:%.1f  Dur:%.1f",
109
	    $n, fmtm($t), fmt($t), fmtd($groupdurs{$t});
110

    
111
    # within this group we want to output a pitch number for every
112
    # "known" channel, i.e. everything in keys %channels.  these
113
    # pitches may be based on notes starting at this time (via noteons
114
    # hash) or notes that have started previously but not yet finished
115
    # (via noteoffs hash)
116

    
117
    print "  Midi: [";
118

    
119
    my @pitches; # array of all pitches (ordered by channel) in this group
120

    
121
    for my $ot (keys %noteoffs) {
122
	# prune notes that have ended already or are ending now
123
	if ($ot <= $t) {
124
	    delete $noteoffs{$ot};
125
	}
126
    }
127

    
128
    for my $c (sort { $a <=> $b } keys %channels) {
129

    
130
	my @here; # array of all pitches on this channel
131

    
132
	for my $d (keys %{$noteons{$t}{$c}}) {
133
	    for my $p (keys %{$noteons{$t}{$c}{$d}}) {
134
		# add notes that begin at this time, indexed by end time
135
		$noteoffs{$t + $d}{$c}{$p}++;
136
	    }
137
	}
138

    
139
	for my $ot (keys %noteoffs) {
140
	    # note(s) still sounding, add to group if in channel
141
	    for my $p (keys %{$noteoffs{$ot}{$c}}) {
142
		push @here, $p;
143
	    }
144
	}
145

    
146
	if (@here > 0) {
147
	    # if we have any notes on this channel in this group,
148
	    # prepare to print them separated by spaces
149
	    push @pitches, (join " ", @here);
150
	} else {
151
	    # otherwise prepare to print 'rest'
152
	    push @pitches, "'rest'";
153
	}
154
    }
155

    
156
    # now print the channel note lists separated by comma
157
    print join ", ", @pitches;
158

    
159
    print "]\n";
160

    
161
    ++$n;
162
}
163