File Coverage

blib/lib/MIDI/Util.pm
Criterion Covered Total %
statement 123 149 82.5
branch 36 54 66.6
condition 2 17 11.7
subroutine 20 23 86.9
pod 13 13 100.0
total 194 256 75.7


line stmt bran cond sub pod time code
1             package MIDI::Util;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: MIDI Utilities
5              
6             our $VERSION = '0.1304';
7              
8 2     2   828035 use strict;
  2         6  
  2         149  
9 2     2   14 use warnings;
  2         4  
  2         181  
10              
11 2     2   1189 use File::Slurper qw(write_text);
  2         8671  
  2         163  
12 2     2   19 use List::Util qw(first);
  2         4  
  2         193  
13 2     2   1313 use MIDI ();
  2         36955  
  2         103  
14 2     2   1723 use MIDI::Simple ();
  2         22735  
  2         91  
15 2     2   1257 use Music::Tempo qw(bpm_to_ms);
  2         1396  
  2         191  
16 2     2   18 use Exporter 'import';
  2         9  
  2         153  
17              
18             our @EXPORT = qw(
19             midi_dump
20             reverse_dump
21             midi_format
22             set_chan_patch
23             set_time_signature
24             setup_score
25             dura_size
26             ticks
27             timidity_conf
28             play_timidity
29             play_fluidsynth
30             get_microseconds
31             score2events
32             );
33              
34 2     2   18 use constant TICKS => 96;
  2         5  
  2         5091  
35              
36              
37             sub setup_score {
38 1     1 1 859 my %args = (
39             lead_in => 4,
40             volume => 120,
41             bpm => 100,
42             channel => 0,
43             patch => 0,
44             octave => 4,
45             signature => '4/4',
46             @_,
47             );
48              
49 1         8 my $score = MIDI::Simple->new_score();
50              
51 1         122 set_time_signature($score, $args{signature});
52              
53 1         28 $score->set_tempo( bpm_to_ms($args{bpm}) * 1000 );
54              
55 1         31 $score->Channel(9);
56 1         15 $score->n( 'qn', 42 ) for 1 .. $args{lead_in};
57              
58 1         312 $score->Volume($args{volume});
59 1         15 $score->Channel($args{channel});
60 1         12 $score->Octave($args{octave});
61 1         18 $score->patch_change( $args{channel}, $args{patch} );
62              
63 1         26 return $score;
64             }
65              
66              
67             sub set_chan_patch {
68 1     1 1 6576 my ( $score, $channel, $patch ) = @_;
69              
70 1   50     5 $channel //= 0;
71              
72 1 50       9 $score->patch_change( $channel, $patch )
73             if defined $patch;
74              
75 1         33 $score->noop( 'c' . $channel );
76             }
77              
78              
79             sub midi_dump {
80 15     15 1 20486 my ($key) = @_;
81              
82 15 100       167 if ( lc $key eq 'volume' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
83             return {
84 10         26 map { $_ => $MIDI::Simple::Volume{$_} }
85 1         11 sort { $MIDI::Simple::Volume{$a} <=> $MIDI::Simple::Volume{$b} }
  19         35  
86             keys %MIDI::Simple::Volume
87             };
88             }
89             elsif ( lc $key eq 'length' ) {
90             return {
91 20         92 map { $_ => $MIDI::Simple::Length{$_} }
92 1         13 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  63         104  
93             keys %MIDI::Simple::Length
94             };
95             }
96             elsif ( lc $key eq 'ticks' ) {
97             return {
98 20         50 map { $_ => $MIDI::Simple::Length{$_} * TICKS }
99 1         9 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  63         92  
100             keys %MIDI::Simple::Length
101             };
102             }
103             elsif ( lc $key eq 'note' ) {
104             return {
105 27         68 map { $_ => $MIDI::Simple::Note{$_} }
106 1         14 sort { $MIDI::Simple::Note{$a} <=> $MIDI::Simple::Note{$b} }
  99         181  
107             keys %MIDI::Simple::Note
108             };
109             }
110             elsif ( lc $key eq 'note2number' ) {
111             return {
112 128         362 map { $_ => $MIDI::note2number{$_} }
113 1         44 sort { $MIDI::note2number{$a} <=> $MIDI::note2number{$b} }
  740         1185  
114             keys %MIDI::note2number
115             };
116             }
117             elsif ( lc $key eq 'number2note' ) {
118             return {
119 128         351 map { $_ => $MIDI::number2note{$_} }
120 1         29 sort { $a <=> $b }
  733         973  
121             keys %MIDI::number2note
122             };
123             }
124             elsif ( lc $key eq 'patch2number' ) {
125             return {
126 128         355 map { $_ => $MIDI::patch2number{$_} }
127 1         35 sort { $MIDI::patch2number{$a} <=> $MIDI::patch2number{$b} }
  739         1362  
128             keys %MIDI::patch2number
129             };
130             }
131             elsif ( lc $key eq 'number2patch' ) {
132             return {
133 128         375 map { $_ => $MIDI::number2patch{$_} }
134 1         31 sort { $a <=> $b }
  735         1100  
135             keys %MIDI::number2patch
136             };
137             }
138             elsif ( lc $key eq 'notenum2percussion' ) {
139             return {
140 47         139 map { $_ => $MIDI::notenum2percussion{$_} }
141 1         16 sort { $a <=> $b }
  207         317  
142             keys %MIDI::notenum2percussion
143             };
144             }
145             elsif ( lc $key eq 'percussion2notenum' ) {
146             return {
147 47         130 map { $_ => $MIDI::percussion2notenum{$_} }
148 1         18 sort { $MIDI::percussion2notenum{$a} <=> $MIDI::percussion2notenum{$b} }
  205         376  
149             keys %MIDI::percussion2notenum
150             };
151             }
152             elsif ( lc $key eq 'all_events' ) {
153 1         11 return \@MIDI::Event::All_events;
154             }
155             elsif ( lc $key eq 'midi_events' ) {
156 1         5 return \@MIDI::Event::MIDI_events;
157             }
158             elsif ( lc $key eq 'meta_events' ) {
159 1         5 return \@MIDI::Event::Meta_events;
160             }
161             elsif ( lc $key eq 'text_events' ) {
162 1         17 return \@MIDI::Event::Text_events;
163             }
164             elsif ( lc $key eq 'nontext_meta_events' ) {
165 1         6 return \@MIDI::Event::Nontext_meta_events;
166             }
167             else {
168 0         0 return [];
169             }
170             }
171              
172              
173             sub reverse_dump {
174 0     0 1 0 my ($name, $precision) = @_;
175              
176 0   0     0 $precision //= -1;
177              
178 0         0 my %by_value;
179              
180 0         0 my $dump = midi_dump($name); # dumps an arrayref
181              
182 0         0 for my $key (keys %$dump) {
183             my $val = $name eq 'length' && $precision >= 0
184             ? sprintf('%.*f', $precision, $dump->{$key})
185 0 0 0     0 : $dump->{$key};
186 0         0 $by_value{$val} = $key;
187             }
188              
189 0         0 return \%by_value;
190             }
191              
192              
193             sub midi_format {
194 1     1 1 6023 my (@notes) = @_;
195 1         3 my @formatted;
196 1         3 for my $note (@notes) {
197 4         8 $note =~ s/C##/D/;
198 4         9 $note =~ s/D##/E/;
199 4         5 $note =~ s/F##/G/;
200 4         7 $note =~ s/G##/A/;
201              
202 4         7 $note =~ s/Dbb/C/;
203 4         7 $note =~ s/Ebb/D/;
204 4         6 $note =~ s/Abb/G/;
205 4         7 $note =~ s/Bbb/A/;
206              
207 4         7 $note =~ s/E#/F/;
208 4         8 $note =~ s/B#/C/;
209              
210 4         7 $note =~ s/Cb/B/;
211 4         8 $note =~ s/Fb/E/;
212              
213 4         10 $note =~ s/#/s/;
214 4         11 $note =~ s/b/f/;
215              
216 4         404 push @formatted, $note;
217             }
218 1         9 return @formatted;
219             }
220              
221              
222             sub set_time_signature {
223 1     1 1 4 my ($score, $signature) = @_;
224 1         4 my ($beats, $divisions) = split /\//, $signature;
225 1 50       9 $score->time_signature(
    50          
226             $beats,
227             ($divisions == 8 ? 3 : 2),
228             ($divisions == 8 ? 24 : 18 ),
229             8
230             );
231             }
232              
233              
234             sub dura_size {
235 4     4 1 4685 my ($duration, $ppqn) = @_;
236 4   50     29 $ppqn ||= TICKS;
237 4         8 my $size = 0;
238 4 100       20 if ($duration =~ /^d(\d+)$/) {
239 2         23 $size = sprintf '%0.f', $1 / $ppqn;
240             }
241             else {
242 2         26 $size = $MIDI::Simple::Length{$duration};
243             }
244 4         24 return $size;
245             }
246              
247              
248             sub ticks {
249 1     1 1 6991 my ($score) = @_;
250 1         2 return ${ $score->{Tempo} };
  1         17  
251             }
252              
253              
254             sub timidity_conf {
255 2     2 1 5026 my ($soundfont, $config_file) = @_;
256 2         7 my $config = "soundfont $soundfont\n";
257 2 100       12 write_text($config_file, $config) if $config_file;
258 2         449 return $config;
259             }
260              
261              
262             sub play_timidity {
263 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
264 0         0 $score->write_score($midi);
265 0         0 my @cmd;
266 0 0       0 if ($soundfont) {
267 0   0     0 $config ||= 'timidity-midi-util.cfg';
268 0         0 timidity_conf($soundfont, $config);
269 0         0 @cmd = ('timidity', '-c', $config, '-Od', $midi);
270             }
271             else {
272 0         0 @cmd = ('timidity', '-Od', $midi);
273             }
274 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
275             }
276              
277              
278             sub play_fluidsynth {
279 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
280 0 0 0     0 if (!$config && $^O eq 'darwin') {
    0 0        
    0          
281 0         0 $config = [qw(-a coreaudio -m coremidi -i)];
282             }
283             elsif (!$config && $^O eq 'MSWin32') {
284 0         0 $config = [];
285             }
286             elsif (!$config) { # linux
287 0         0 $config = [qw(-a alsa -m alsa_seq -i)];
288             }
289 0         0 my @cmd;
290 0         0 @cmd = ('fluidsynth', @$config, $soundfont, $midi);
291 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
292             }
293              
294              
295             sub get_microseconds {
296 1     1 1 4379 my ($score) = @_;
297 1     3   7 my $tempo = first { $_->[0] eq 'set_tempo' } @{ $score->{Score} };
  3         9  
  1         11  
298 1         28 return $tempo->[2] / ${ $score->{Tempo} };
  1         6  
299             }
300              
301              
302             sub score2events {
303 1     1 1 4763 my ($score) = @_;
304 1         9 return MIDI::Score::score_r_to_events_r($score->{Score});
305             }
306              
307             1;
308              
309             __END__