File Coverage

blib/lib/MIDI/Util.pm
Criterion Covered Total %
statement 127 155 81.9
branch 38 58 65.5
condition 2 17 11.7
subroutine 21 24 87.5
pod 14 14 100.0
total 202 268 75.3


line stmt bran cond sub pod time code
1             package MIDI::Util;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: MIDI and music utilities
5              
6             our $VERSION = '0.1307';
7              
8 2     2   463564 use strict;
  2         3  
  2         63  
9 2     2   7 use warnings;
  2         3  
  2         120  
10              
11 2     2   799 use File::Slurper qw(write_text);
  2         5782  
  2         108  
12 2     2   13 use List::Util qw(first);
  2         3  
  2         118  
13 2     2   884 use MIDI ();
  2         22553  
  2         82  
14 2     2   1146 use MIDI::Simple ();
  2         12668  
  2         56  
15 2     2   710 use Music::Tempo qw(bpm_to_ms);
  2         848  
  2         113  
16 2     2   15 use Exporter 'import';
  2         2  
  2         103  
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             scale_names
33             );
34              
35 2     2   9 use constant TICKS => 96;
  2         2  
  2         3007  
36              
37              
38             sub setup_score {
39 1     1 1 588 my %args = (
40             lead_in => 4,
41             volume => 120,
42             bpm => 100,
43             channel => 0,
44             patch => 0,
45             octave => 4,
46             signature => '4/4',
47             @_,
48             );
49              
50 1         7 my $score = MIDI::Simple->new_score();
51              
52 1         105 set_time_signature($score, $args{signature});
53              
54 1         25 $score->set_tempo( bpm_to_ms($args{bpm}) * 1000 );
55              
56 1         28 $score->Channel(9);
57 1         14 $score->n( 'qn', 42 ) for 1 .. $args{lead_in};
58              
59 1         216 $score->Volume($args{volume});
60 1         8 $score->Channel($args{channel});
61 1         6 $score->Octave($args{octave});
62 1         10 $score->patch_change( $args{channel}, $args{patch} );
63              
64 1         16 return $score;
65             }
66              
67              
68             sub set_chan_patch {
69 1     1 1 4151 my ( $score, $channel, $patch ) = @_;
70              
71 1   50     5 $channel //= 0;
72              
73 1 50       6 $score->patch_change( $channel, $patch )
74             if defined $patch;
75              
76 1         18 $score->noop( 'c' . $channel );
77             }
78              
79              
80             sub midi_dump {
81 15     15 1 12885 my ($key) = @_;
82              
83 15 100       105 if ( lc $key eq 'volume' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
84             return {
85 10         17 map { $_ => $MIDI::Simple::Volume{$_} }
86 1         10 sort { $MIDI::Simple::Volume{$a} <=> $MIDI::Simple::Volume{$b} }
  23         26  
87             keys %MIDI::Simple::Volume
88             };
89             }
90             elsif ( lc $key eq 'length' ) {
91             return {
92 20         31 map { $_ => $MIDI::Simple::Length{$_} }
93 1         8 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  59         62  
94             keys %MIDI::Simple::Length
95             };
96             }
97             elsif ( lc $key eq 'ticks' ) {
98             return {
99 20         31 map { $_ => $MIDI::Simple::Length{$_} * TICKS }
100 1         7 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  59         57  
101             keys %MIDI::Simple::Length
102             };
103             }
104             elsif ( lc $key eq 'note' ) {
105             return {
106 27         39 map { $_ => $MIDI::Simple::Note{$_} }
107 1         23 sort { $MIDI::Simple::Note{$a} <=> $MIDI::Simple::Note{$b} }
  102         97  
108             keys %MIDI::Simple::Note
109             };
110             }
111             elsif ( lc $key eq 'note2number' ) {
112             return {
113 128         206 map { $_ => $MIDI::note2number{$_} }
114 1         26 sort { $MIDI::note2number{$a} <=> $MIDI::note2number{$b} }
  737         699  
115             keys %MIDI::note2number
116             };
117             }
118             elsif ( lc $key eq 'number2note' ) {
119             return {
120 128         250 map { $_ => $MIDI::number2note{$_} }
121 1         20 sort { $a <=> $b }
  735         601  
122             keys %MIDI::number2note
123             };
124             }
125             elsif ( lc $key eq 'patch2number' ) {
126             return {
127 128         210 map { $_ => $MIDI::patch2number{$_} }
128 1         20 sort { $MIDI::patch2number{$a} <=> $MIDI::patch2number{$b} }
  745         766  
129             keys %MIDI::patch2number
130             };
131             }
132             elsif ( lc $key eq 'number2patch' ) {
133             return {
134 128         261 map { $_ => $MIDI::number2patch{$_} }
135 1         20 sort { $a <=> $b }
  736         611  
136             keys %MIDI::number2patch
137             };
138             }
139             elsif ( lc $key eq 'notenum2percussion' ) {
140             return {
141 47         94 map { $_ => $MIDI::notenum2percussion{$_} }
142 1         10 sort { $a <=> $b }
  202         166  
143             keys %MIDI::notenum2percussion
144             };
145             }
146             elsif ( lc $key eq 'percussion2notenum' ) {
147             return {
148 47         70 map { $_ => $MIDI::percussion2notenum{$_} }
149 1         10 sort { $MIDI::percussion2notenum{$a} <=> $MIDI::percussion2notenum{$b} }
  203         210  
150             keys %MIDI::percussion2notenum
151             };
152             }
153             elsif ( lc $key eq 'all_events' ) {
154 1         7 return \@MIDI::Event::All_events;
155             }
156             elsif ( lc $key eq 'midi_events' ) {
157 1         9 return \@MIDI::Event::MIDI_events;
158             }
159             elsif ( lc $key eq 'meta_events' ) {
160 1         2 return \@MIDI::Event::Meta_events;
161             }
162             elsif ( lc $key eq 'text_events' ) {
163 1         3 return \@MIDI::Event::Text_events;
164             }
165             elsif ( lc $key eq 'nontext_meta_events' ) {
166 1         2 return \@MIDI::Event::Nontext_meta_events;
167             }
168             else {
169 0         0 return [];
170             }
171             }
172              
173              
174             sub reverse_dump {
175 0     0 1 0 my ($name, $precision) = @_;
176              
177 0   0     0 $precision //= -1;
178              
179 0         0 my %by_value;
180              
181 0         0 my $dump = midi_dump($name); # dumps an arrayref
182              
183 0         0 for my $key (keys %$dump) {
184             my $val = $name eq 'length' && $precision >= 0
185             ? sprintf('%.*f', $precision, $dump->{$key})
186 0 0 0     0 : $dump->{$key};
187 0         0 $by_value{$val} = $key;
188             }
189              
190 0         0 return \%by_value;
191             }
192              
193              
194             sub midi_format {
195 1     1 1 3434 my (@notes) = @_;
196 1         2 my $flag = 1;
197 1 50       5 if ($notes[0] =~ /^(\d)$/) {
198 0         0 $flag = shift @notes;
199             }
200 1         2 my @formatted;
201 1         1 for my $note (@notes) {
202 4         5 $note =~ s/C##/D/;
203 4         6 $note =~ s/D##/E/;
204 4         5 $note =~ s/F##/G/;
205 4         3 $note =~ s/G##/A/;
206              
207 4         4 $note =~ s/Dbb/C/;
208 4         3 $note =~ s/Ebb/D/;
209 4         4 $note =~ s/Abb/G/;
210 4         4 $note =~ s/Bbb/A/;
211              
212 4         4 $note =~ s/E#/F/;
213 4         4 $note =~ s/B#/C/;
214              
215 4         4 $note =~ s/Cb/B/;
216 4         4 $note =~ s/Fb/E/;
217              
218 4 50       7 if ($flag) {
219 4         6 $note =~ s/#/s/;
220 4         3 $note =~ s/b/f/;
221             }
222              
223 4         6 push @formatted, $note;
224             }
225 1         3 return @formatted;
226             }
227              
228              
229             sub set_time_signature {
230 1     1 1 2 my ($score, $signature) = @_;
231 1         5 my ($beats, $divisions) = split /\//, $signature;
232 1 50       8 $score->time_signature(
    50          
233             $beats,
234             ($divisions == 8 ? 3 : 2),
235             ($divisions == 8 ? 24 : 18 ),
236             8
237             );
238             }
239              
240              
241             sub dura_size {
242 4     4 1 3285 my ($duration, $ppqn) = @_;
243 4   50     18 $ppqn ||= TICKS;
244 4         5 my $size = 0;
245 4 100       13 if ($duration =~ /^d(\d+)$/) {
246 2         20 $size = sprintf '%0.f', $1 / $ppqn;
247             }
248             else {
249 2         5 $size = $MIDI::Simple::Length{$duration};
250             }
251 4         14 return $size;
252             }
253              
254              
255             sub ticks {
256 1     1 1 5433 my ($score) = @_;
257 1         3 return ${ $score->{Tempo} };
  1         15  
258             }
259              
260              
261             sub timidity_conf {
262 2     2 1 2787 my ($soundfont, $config_file) = @_;
263 2         3 my $config = "soundfont $soundfont\n";
264 2 100       9 write_text($config_file, $config) if $config_file;
265 2         474 return $config;
266             }
267              
268              
269             sub play_timidity {
270 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
271 0         0 $score->write_score($midi);
272 0         0 my @cmd;
273 0 0       0 if ($soundfont) {
274 0   0     0 $config ||= 'timidity-midi-util.cfg';
275 0         0 timidity_conf($soundfont, $config);
276 0         0 @cmd = ('timidity', '-c', $config, '-Od', $midi);
277             }
278             else {
279 0         0 @cmd = ('timidity', '-Od', $midi);
280             }
281 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
282             }
283              
284              
285             sub play_fluidsynth {
286 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
287 0         0 $score->write_score($midi);
288 0 0 0     0 if (!$config && $^O eq 'darwin') {
    0 0        
    0          
289 0         0 $config = [qw(-a coreaudio -m coremidi -i)];
290             }
291             elsif (!$config && $^O eq 'MSWin32') {
292 0         0 $config = [];
293             }
294             elsif (!$config) { # linux
295 0         0 $config = [qw(-a alsa -m alsa_seq -i)];
296             }
297 0         0 my @cmd;
298 0         0 @cmd = ('fluidsynth', @$config, $soundfont, $midi);
299 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
300             }
301              
302              
303             sub get_microseconds {
304 1     1 1 3141 my ($score) = @_;
305 1     3   5 my $tempo = first { $_->[0] eq 'set_tempo' } @{ $score->{Score} };
  3         21  
  1         18  
306 1         4 return $tempo->[2] / ${ $score->{Tempo} };
  1         4  
307             }
308              
309              
310             sub score2events {
311 1     1 1 2660 my ($score) = @_;
312 1         6 return MIDI::Score::score_r_to_events_r($score->{Score});
313             }
314              
315              
316             sub scale_names {
317 1     1 1 4078 return [qw(
318             ionian major
319             hypolydian
320             dorian
321             hypomyxolydian
322             phrygian
323             hypoaeolian
324             lydian
325             hypolocrian
326             mixolydian
327             hypoionian
328             aeolian minor m
329             hypodorian
330             locrian
331             hypophrygian
332             harmonicminor hm
333             melodicminor mm
334             blues
335             pentatonic pmaj
336             chromatic
337             diminished
338             wholetone
339             augmented
340             hungarianminor
341             3semitone
342             4semitone
343             neapolitanminor nmin
344             neapolitanmajor nmaj
345             todi
346             marva
347             persian
348             oriental
349             romanian
350             pelog
351             iwato
352             hirajoshi
353             egyptian
354             pminor pentatonicminor
355             )];
356             }
357              
358             1;
359              
360             __END__