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.1306';
7              
8 2     2   448283 use strict;
  2         4  
  2         69  
9 2     2   6 use warnings;
  2         4  
  2         116  
10              
11 2     2   748 use File::Slurper qw(write_text);
  2         5899  
  2         108  
12 2     2   12 use List::Util qw(first);
  2         3  
  2         117  
13 2     2   922 use MIDI ();
  2         22415  
  2         64  
14 2     2   1139 use MIDI::Simple ();
  2         13036  
  2         59  
15 2     2   717 use Music::Tempo qw(bpm_to_ms);
  2         879  
  2         115  
16 2     2   14 use Exporter 'import';
  2         3  
  2         91  
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   7 use constant TICKS => 96;
  2         2  
  2         3019  
36              
37              
38             sub setup_score {
39 1     1 1 616 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         6 my $score = MIDI::Simple->new_score();
51              
52 1         68 set_time_signature($score, $args{signature});
53              
54 1         16 $score->set_tempo( bpm_to_ms($args{bpm}) * 1000 );
55              
56 1         18 $score->Channel(9);
57 1         10 $score->n( 'qn', 42 ) for 1 .. $args{lead_in};
58              
59 1         153 $score->Volume($args{volume});
60 1         7 $score->Channel($args{channel});
61 1         7 $score->Octave($args{octave});
62 1         9 $score->patch_change( $args{channel}, $args{patch} );
63              
64 1         15 return $score;
65             }
66              
67              
68             sub set_chan_patch {
69 1     1 1 3722 my ( $score, $channel, $patch ) = @_;
70              
71 1   50     17 $channel //= 0;
72              
73 1 50       4 $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 11876 my ($key) = @_;
82              
83 15 100       126 if ( lc $key eq 'volume' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
84             return {
85 10         16 map { $_ => $MIDI::Simple::Volume{$_} }
86 1         12 sort { $MIDI::Simple::Volume{$a} <=> $MIDI::Simple::Volume{$b} }
  24         28  
87             keys %MIDI::Simple::Volume
88             };
89             }
90             elsif ( lc $key eq 'length' ) {
91             return {
92 20         30 map { $_ => $MIDI::Simple::Length{$_} }
93 1         8 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  65         65  
94             keys %MIDI::Simple::Length
95             };
96             }
97             elsif ( lc $key eq 'ticks' ) {
98             return {
99 20         29 map { $_ => $MIDI::Simple::Length{$_} * TICKS }
100 1         6 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  65         63  
101             keys %MIDI::Simple::Length
102             };
103             }
104             elsif ( lc $key eq 'note' ) {
105             return {
106 27         37 map { $_ => $MIDI::Simple::Note{$_} }
107 1         9 sort { $MIDI::Simple::Note{$a} <=> $MIDI::Simple::Note{$b} }
  96         90  
108             keys %MIDI::Simple::Note
109             };
110             }
111             elsif ( lc $key eq 'note2number' ) {
112             return {
113 128         186 map { $_ => $MIDI::note2number{$_} }
114 1         27 sort { $MIDI::note2number{$a} <=> $MIDI::note2number{$b} }
  736         704  
115             keys %MIDI::note2number
116             };
117             }
118             elsif ( lc $key eq 'number2note' ) {
119             return {
120 128         195 map { $_ => $MIDI::number2note{$_} }
121 1         22 sort { $a <=> $b }
  739         598  
122             keys %MIDI::number2note
123             };
124             }
125             elsif ( lc $key eq 'patch2number' ) {
126             return {
127 128         208 map { $_ => $MIDI::patch2number{$_} }
128 1         20 sort { $MIDI::patch2number{$a} <=> $MIDI::patch2number{$b} }
  738         696  
129             keys %MIDI::patch2number
130             };
131             }
132             elsif ( lc $key eq 'number2patch' ) {
133             return {
134 128         173 map { $_ => $MIDI::number2patch{$_} }
135 1         19 sort { $a <=> $b }
  742         600  
136             keys %MIDI::number2patch
137             };
138             }
139             elsif ( lc $key eq 'notenum2percussion' ) {
140             return {
141 47         71 map { $_ => $MIDI::notenum2percussion{$_} }
142 1         10 sort { $a <=> $b }
  208         169  
143             keys %MIDI::notenum2percussion
144             };
145             }
146             elsif ( lc $key eq 'percussion2notenum' ) {
147             return {
148 47         68 map { $_ => $MIDI::percussion2notenum{$_} }
149 1         11 sort { $MIDI::percussion2notenum{$a} <=> $MIDI::percussion2notenum{$b} }
  208         194  
150             keys %MIDI::percussion2notenum
151             };
152             }
153             elsif ( lc $key eq 'all_events' ) {
154 1         6 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         3 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 3240 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         2 for my $note (@notes) {
202 4         4 $note =~ s/C##/D/;
203 4         4 $note =~ s/D##/E/;
204 4         3 $note =~ s/F##/G/;
205 4         4 $note =~ s/G##/A/;
206              
207 4         4 $note =~ s/Dbb/C/;
208 4         3 $note =~ s/Ebb/D/;
209 4         5 $note =~ s/Abb/G/;
210 4         4 $note =~ s/Bbb/A/;
211              
212 4         4 $note =~ s/E#/F/;
213 4         5 $note =~ s/B#/C/;
214              
215 4         3 $note =~ s/Cb/B/;
216 4         3 $note =~ s/Fb/E/;
217              
218 4 50       5 if ($flag) {
219 4         6 $note =~ s/#/s/;
220 4         5 $note =~ s/b/f/;
221             }
222              
223 4         7 push @formatted, $note;
224             }
225 1         4 return @formatted;
226             }
227              
228              
229             sub set_time_signature {
230 1     1 1 2 my ($score, $signature) = @_;
231 1         3 my ($beats, $divisions) = split /\//, $signature;
232 1 50       6 $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 2519 my ($duration, $ppqn) = @_;
243 4   50     18 $ppqn ||= TICKS;
244 4         5 my $size = 0;
245 4 100       14 if ($duration =~ /^d(\d+)$/) {
246 2         18 $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 4434 my ($score) = @_;
257 1         1 return ${ $score->{Tempo} };
  1         4  
258             }
259              
260              
261             sub timidity_conf {
262 2     2 1 2690 my ($soundfont, $config_file) = @_;
263 2         6 my $config = "soundfont $soundfont\n";
264 2 100       31 write_text($config_file, $config) if $config_file;
265 2         338 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 2363 my ($score) = @_;
305 1     3   19 my $tempo = first { $_->[0] eq 'set_tempo' } @{ $score->{Score} };
  3         5  
  1         19  
306 1         4 return $tempo->[2] / ${ $score->{Tempo} };
  1         4  
307             }
308              
309              
310             sub score2events {
311 1     1 1 2685 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 2708 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__