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 @ 3:5abb6a523d27

History | View | Annotate | Download (3.35 KB)

1
use strict;
2

    
3
# read the output from midifile, sort it into "note column" groups
4

    
5
my $timebase = 120;
6
my $beatsperbar = 4;
7
my $seentimesig = 0;
8

    
9
sub fmtm { # function to convert time to bar number
10
    int($_[0] / ($timebase * $beatsperbar)) + 1;
11
}
12
sub fmt { # function to convert time to beat number
13
    my $t = $_[0] / $timebase;
14
    while ($t >= $beatsperbar) { $t -= $beatsperbar; }
15
    $t + 1;
16
}
17
sub fmtd { # function to convert duration to beat count
18
    $_[0] / $timebase;
19
}
20

    
21
my %noteons;
22
my %noteoffs;
23
my %channels;
24
my %grouptimes;
25

    
26
while (<>) {
27

    
28
    my ($t,$c,$d,$p);
29

    
30
    # split events into time, channel, duration, pitch
31
    if (/^(\d+): Note: channel (\d+) duration (\d+) pitch (\d+)/) {
32
	$t = $1;
33
	$c = $2;
34
	$d = $3;
35
	$p = $4;
36
    } elsif (/^Timing division: (\d+)/) {
37
	$timebase = $1;
38
	print STDERR "Set timebase to $timebase from input data\n";
39
	next;
40
    } elsif (/^(\d+): Time signature: (\d+)\//) {
41
	if ($seentimesig != 0 && $beatsperbar != $2) {
42
	    print STDERR "WARNING: File uses more than one time signature, not supported here\n";
43
	}
44
	$beatsperbar = $2;
45
	$seentimesig++;
46
	print STDERR "Set beat count per bar to $beatsperbar from input data\n";
47
	next;
48
    } elsif (/^SMPTE/) {
49
	print STDERR "WARNING: File uses SMPTE timing, expect incorrect results\n";
50
	next;
51
    } else {
52
	next;
53
    }
54

    
55
    # record note-on times, plus all active channels
56
    $noteons{$t}{$c}{$d}{$p}++;
57
    $channels{$c}++;
58

    
59
    # start times of note groups: whenever a note starts or ends
60
    $grouptimes{$t}++;
61
    $grouptimes{$t + $d}++;
62
}
63

    
64
my $prevt;
65
my %groupdurs;
66

    
67
# work out the duration of each note-group
68
for my $t (sort { $a <=> $b } keys %grouptimes) { # sort group starts numerically
69
    if (defined $prevt) {
70
	# duration of the previous note-group is that from its start
71
	# time to that of the current one
72
	$groupdurs{$prevt} = $t - $prevt;
73
    }
74
    $prevt = $t;
75
}
76

    
77
my $n = 0;
78

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

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

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

    
90
    print "  Midi: [";
91

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

    
94
    for my $ot (keys %noteoffs) {
95
	# prune notes that have ended already or are ending now
96
	if ($ot <= $t) {
97
	    delete $noteoffs{$ot};
98
	}
99
    }
100

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

    
103
	my @here; # array of all pitches on this channel
104

    
105
	for my $d (keys %{$noteons{$t}{$c}}) {
106
	    for my $p (keys %{$noteons{$t}{$c}{$d}}) {
107
		# add notes that begin at this time, indexed by end time
108
		$noteoffs{$t + $d}{$c}{$p}++;
109
	    }
110
	}
111

    
112
	for my $ot (keys %noteoffs) {
113
	    # note(s) still sounding, add to group if in channel
114
	    for my $p (keys %{$noteoffs{$ot}{$c}}) {
115
		push @here, $p;
116
	    }
117
	}
118

    
119
	if (@here > 0) {
120
	    # if we have any notes on this channel in this group,
121
	    # prepare to print them separated by spaces
122
	    push @pitches, (join " ", @here);
123
	} else {
124
	    # otherwise prepare to print 'rest'
125
	    push @pitches, "'rest'";
126
	}
127
    }
128

    
129
    # now print the channel note lists separated by comma
130
    print join ", ", @pitches;
131

    
132
    print "]\n";
133

    
134
    ++$n;
135
}
136