File Coverage

blib/lib/MIDI/Util.pm
Criterion Covered Total %
statement 126 154 81.8
branch 38 58 65.5
condition 2 17 11.7
subroutine 20 23 86.9
pod 13 13 100.0
total 199 265 75.0


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.1305';
7              
8 2     2   460164 use strict;
  2         2  
  2         62  
9 2     2   7 use warnings;
  2         2  
  2         105  
10              
11 2     2   700 use File::Slurper qw(write_text);
  2         5628  
  2         111  
12 2     2   14 use List::Util qw(first);
  2         4  
  2         136  
13 2     2   890 use MIDI ();
  2         21367  
  2         97  
14 2     2   1108 use MIDI::Simple ();
  2         12788  
  2         55  
15 2     2   718 use Music::Tempo qw(bpm_to_ms);
  2         925  
  2         141  
16 2     2   17 use Exporter 'import';
  2         3  
  2         92  
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   6 use constant TICKS => 96;
  2         3  
  2         2964  
35              
36              
37             sub setup_score {
38 1     1 1 603 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         5 my $score = MIDI::Simple->new_score();
50              
51 1         65 set_time_signature($score, $args{signature});
52              
53 1         14 $score->set_tempo( bpm_to_ms($args{bpm}) * 1000 );
54              
55 1         18 $score->Channel(9);
56 1         9 $score->n( 'qn', 42 ) for 1 .. $args{lead_in};
57              
58 1         175 $score->Volume($args{volume});
59 1         7 $score->Channel($args{channel});
60 1         6 $score->Octave($args{octave});
61 1         10 $score->patch_change( $args{channel}, $args{patch} );
62              
63 1         14 return $score;
64             }
65              
66              
67             sub set_chan_patch {
68 1     1 1 3890 my ( $score, $channel, $patch ) = @_;
69              
70 1   50     3 $channel //= 0;
71              
72 1 50       5 $score->patch_change( $channel, $patch )
73             if defined $patch;
74              
75 1         18 $score->noop( 'c' . $channel );
76             }
77              
78              
79             sub midi_dump {
80 15     15 1 19517 my ($key) = @_;
81              
82 15 100       156 if ( lc $key eq 'volume' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
83             return {
84 10         28 map { $_ => $MIDI::Simple::Volume{$_} }
85 1         11 sort { $MIDI::Simple::Volume{$a} <=> $MIDI::Simple::Volume{$b} }
  22         40  
86             keys %MIDI::Simple::Volume
87             };
88             }
89             elsif ( lc $key eq 'length' ) {
90             return {
91 20         55 map { $_ => $MIDI::Simple::Length{$_} }
92 1         10 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  64         104  
93             keys %MIDI::Simple::Length
94             };
95             }
96             elsif ( lc $key eq 'ticks' ) {
97             return {
98 20         72 map { $_ => $MIDI::Simple::Length{$_} * TICKS }
99 1         9 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  64         115  
100             keys %MIDI::Simple::Length
101             };
102             }
103             elsif ( lc $key eq 'note' ) {
104             return {
105 27         69 map { $_ => $MIDI::Simple::Note{$_} }
106 1         14 sort { $MIDI::Simple::Note{$a} <=> $MIDI::Simple::Note{$b} }
  98         175  
107             keys %MIDI::Simple::Note
108             };
109             }
110             elsif ( lc $key eq 'note2number' ) {
111             return {
112 128         387 map { $_ => $MIDI::note2number{$_} }
113 1         41 sort { $MIDI::note2number{$a} <=> $MIDI::note2number{$b} }
  747         1316  
114             keys %MIDI::note2number
115             };
116             }
117             elsif ( lc $key eq 'number2note' ) {
118             return {
119 128         356 map { $_ => $MIDI::number2note{$_} }
120 1         29 sort { $a <=> $b }
  740         1130  
121             keys %MIDI::number2note
122             };
123             }
124             elsif ( lc $key eq 'patch2number' ) {
125             return {
126 128         354 map { $_ => $MIDI::patch2number{$_} }
127 1         33 sort { $MIDI::patch2number{$a} <=> $MIDI::patch2number{$b} }
  742         1316  
128             keys %MIDI::patch2number
129             };
130             }
131             elsif ( lc $key eq 'number2patch' ) {
132             return {
133 128         404 map { $_ => $MIDI::number2patch{$_} }
134 1         28 sort { $a <=> $b }
  738         1132  
135             keys %MIDI::number2patch
136             };
137             }
138             elsif ( lc $key eq 'notenum2percussion' ) {
139             return {
140 47         128 map { $_ => $MIDI::notenum2percussion{$_} }
141 1         15 sort { $a <=> $b }
  204         309  
142             keys %MIDI::notenum2percussion
143             };
144             }
145             elsif ( lc $key eq 'percussion2notenum' ) {
146             return {
147 47         125 map { $_ => $MIDI::percussion2notenum{$_} }
148 1         17 sort { $MIDI::percussion2notenum{$a} <=> $MIDI::percussion2notenum{$b} }
  206         375  
149             keys %MIDI::percussion2notenum
150             };
151             }
152             elsif ( lc $key eq 'all_events' ) {
153 1         12 return \@MIDI::Event::All_events;
154             }
155             elsif ( lc $key eq 'midi_events' ) {
156 1         4 return \@MIDI::Event::MIDI_events;
157             }
158             elsif ( lc $key eq 'meta_events' ) {
159 1         14 return \@MIDI::Event::Meta_events;
160             }
161             elsif ( lc $key eq 'text_events' ) {
162 1         4 return \@MIDI::Event::Text_events;
163             }
164             elsif ( lc $key eq 'nontext_meta_events' ) {
165 1         5 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 3479 my (@notes) = @_;
195 1         1 my $flag = 1;
196 1 50       5 if ($notes[0] =~ /^(\d)$/) {
197 0         0 $flag = shift @notes;
198             }
199 1         2 my @formatted;
200 1         2 for my $note (@notes) {
201 4         4 $note =~ s/C##/D/;
202 4         4 $note =~ s/D##/E/;
203 4         4 $note =~ s/F##/G/;
204 4         4 $note =~ s/G##/A/;
205              
206 4         4 $note =~ s/Dbb/C/;
207 4         5 $note =~ s/Ebb/D/;
208 4         14 $note =~ s/Abb/G/;
209 4         5 $note =~ s/Bbb/A/;
210              
211 4         4 $note =~ s/E#/F/;
212 4         4 $note =~ s/B#/C/;
213              
214 4         5 $note =~ s/Cb/B/;
215 4         2 $note =~ s/Fb/E/;
216              
217 4 50       6 if ($flag) {
218 4         5 $note =~ s/#/s/;
219 4         7 $note =~ s/b/f/;
220             }
221              
222 4         6 push @formatted, $note;
223             }
224 1         4 return @formatted;
225             }
226              
227              
228             sub set_time_signature {
229 1     1 1 2 my ($score, $signature) = @_;
230 1         2 my ($beats, $divisions) = split /\//, $signature;
231 1 50       6 $score->time_signature(
    50          
232             $beats,
233             ($divisions == 8 ? 3 : 2),
234             ($divisions == 8 ? 24 : 18 ),
235             8
236             );
237             }
238              
239              
240             sub dura_size {
241 4     4 1 2583 my ($duration, $ppqn) = @_;
242 4   50     26 $ppqn ||= TICKS;
243 4         6 my $size = 0;
244 4 100       19 if ($duration =~ /^d(\d+)$/) {
245 2         23 $size = sprintf '%0.f', $1 / $ppqn;
246             }
247             else {
248 2         6 $size = $MIDI::Simple::Length{$duration};
249             }
250 4         17 return $size;
251             }
252              
253              
254             sub ticks {
255 1     1 1 4322 my ($score) = @_;
256 1         2 return ${ $score->{Tempo} };
  1         10  
257             }
258              
259              
260             sub timidity_conf {
261 2     2 1 3842 my ($soundfont, $config_file) = @_;
262 2         4 my $config = "soundfont $soundfont\n";
263 2 100       8 write_text($config_file, $config) if $config_file;
264 2         284 return $config;
265             }
266              
267              
268             sub play_timidity {
269 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
270 0         0 $score->write_score($midi);
271 0         0 my @cmd;
272 0 0       0 if ($soundfont) {
273 0   0     0 $config ||= 'timidity-midi-util.cfg';
274 0         0 timidity_conf($soundfont, $config);
275 0         0 @cmd = ('timidity', '-c', $config, '-Od', $midi);
276             }
277             else {
278 0         0 @cmd = ('timidity', '-Od', $midi);
279             }
280 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
281             }
282              
283              
284             sub play_fluidsynth {
285 0     0 1 0 my ($score, $midi, $soundfont, $config) = @_;
286 0         0 $score->write_score($midi);
287 0 0 0     0 if (!$config && $^O eq 'darwin') {
    0 0        
    0          
288 0         0 $config = [qw(-a coreaudio -m coremidi -i)];
289             }
290             elsif (!$config && $^O eq 'MSWin32') {
291 0         0 $config = [];
292             }
293             elsif (!$config) { # linux
294 0         0 $config = [qw(-a alsa -m alsa_seq -i)];
295             }
296 0         0 my @cmd;
297 0         0 @cmd = ('fluidsynth', @$config, $soundfont, $midi);
298 0 0       0 system(@cmd) == 0 or die "system(@cmd) failed: $?";
299             }
300              
301              
302             sub get_microseconds {
303 1     1 1 2251 my ($score) = @_;
304 1     3   4 my $tempo = first { $_->[0] eq 'set_tempo' } @{ $score->{Score} };
  3         5  
  1         22  
305 1         20 return $tempo->[2] / ${ $score->{Tempo} };
  1         4  
306             }
307              
308              
309             sub score2events {
310 1     1 1 2503 my ($score) = @_;
311 1         7 return MIDI::Score::score_r_to_events_r($score->{Score});
312             }
313              
314             1;
315              
316             __END__