File Coverage

blib/lib/MIDI/Util.pm
Criterion Covered Total %
statement 112 130 86.1
branch 36 48 75.0
condition 2 11 18.1
subroutine 16 18 88.8
pod 10 10 100.0
total 176 217 81.1


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.1201';
7              
8 1     1   742 use strict;
  1         2  
  1         35  
9 1     1   5 use warnings;
  1         2  
  1         27  
10              
11 1     1   457 use File::Slurper qw(write_text);
  1         14953  
  1         61  
12 1     1   463 use MIDI ();
  1         13155  
  1         26  
13 1     1   724 use MIDI::Simple ();
  1         8189  
  1         43  
14 1     1   415 use Music::Tempo qw(bpm_to_ms);
  1         569  
  1         63  
15 1     1   8 use Exporter 'import';
  1         2  
  1         52  
16              
17             our @EXPORT = qw(
18             midi_dump
19             reverse_dump
20             midi_format
21             set_chan_patch
22             set_time_signature
23             setup_score
24             dura_size
25             ticks
26             timidity_conf
27             play_timidity
28             );
29              
30 1     1   6 use constant TICKS => 96;
  1         2  
  1         1580  
31              
32              
33             sub setup_score {
34 1     1 1 737 my %args = (
35             lead_in => 4,
36             volume => 120,
37             bpm => 100,
38             channel => 0,
39             patch => 0,
40             octave => 4,
41             signature => '4/4',
42             @_,
43             );
44              
45 1         8 my $score = MIDI::Simple->new_score();
46              
47 1         98 set_time_signature($score, $args{signature});
48              
49 1         20 $score->set_tempo( bpm_to_ms($args{bpm}) * 1000 );
50              
51 1         26 $score->Channel(9);
52 1         12 $score->n( 'qn', 42 ) for 1 .. $args{lead_in};
53              
54 1         240 $score->Volume($args{volume});
55 1         11 $score->Channel($args{channel});
56 1         10 $score->Octave($args{octave});
57 1         9 $score->patch_change( $args{channel}, $args{patch} );
58              
59 1         33 return $score;
60             }
61              
62              
63             sub set_chan_patch {
64 1     1 1 3420 my ( $score, $channel, $patch ) = @_;
65              
66 1   50     4 $channel //= 0;
67              
68 1 50       5 $score->patch_change( $channel, $patch )
69             if defined $patch;
70              
71 1         19 $score->noop( 'c' . $channel );
72             }
73              
74              
75             sub midi_dump {
76 15     15 1 11772 my ($key) = @_;
77              
78 15 100       129 if ( lc $key eq 'volume' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
79             return {
80 10         21 map { $_ => $MIDI::Simple::Volume{$_} }
81 1         7 sort { $MIDI::Simple::Volume{$a} <=> $MIDI::Simple::Volume{$b} }
  25         38  
82             keys %MIDI::Simple::Volume
83             };
84             }
85             elsif ( lc $key eq 'length' ) {
86             return {
87 20         39 map { $_ => $MIDI::Simple::Length{$_} }
88 1         8 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  60         80  
89             keys %MIDI::Simple::Length
90             };
91             }
92             elsif ( lc $key eq 'ticks' ) {
93             return {
94 20         45 map { $_ => $MIDI::Simple::Length{$_} * TICKS }
95 1         7 sort { $MIDI::Simple::Length{$a} <=> $MIDI::Simple::Length{$b} }
  60         76  
96             keys %MIDI::Simple::Length
97             };
98             }
99             elsif ( lc $key eq 'note' ) {
100             return {
101 27         51 map { $_ => $MIDI::Simple::Note{$_} }
102 1         11 sort { $MIDI::Simple::Note{$a} <=> $MIDI::Simple::Note{$b} }
  101         127  
103             keys %MIDI::Simple::Note
104             };
105             }
106             elsif ( lc $key eq 'note2number' ) {
107             return {
108 128         260 map { $_ => $MIDI::note2number{$_} }
109 1         28 sort { $MIDI::note2number{$a} <=> $MIDI::note2number{$b} }
  736         921  
110             keys %MIDI::note2number
111             };
112             }
113             elsif ( lc $key eq 'number2note' ) {
114             return {
115 128         260 map { $_ => $MIDI::number2note{$_} }
116 1         20 sort { $a <=> $b }
  737         849  
117             keys %MIDI::number2note
118             };
119             }
120             elsif ( lc $key eq 'patch2number' ) {
121             return {
122 128         230 map { $_ => $MIDI::patch2number{$_} }
123 1         31 sort { $MIDI::patch2number{$a} <=> $MIDI::patch2number{$b} }
  734         904  
124             keys %MIDI::patch2number
125             };
126             }
127             elsif ( lc $key eq 'number2patch' ) {
128             return {
129 128         254 map { $_ => $MIDI::number2patch{$_} }
130 1         18 sort { $a <=> $b }
  736         857  
131             keys %MIDI::number2patch
132             };
133             }
134             elsif ( lc $key eq 'notenum2percussion' ) {
135             return {
136 47         106 map { $_ => $MIDI::notenum2percussion{$_} }
137 1         9 sort { $a <=> $b }
  209         237  
138             keys %MIDI::notenum2percussion
139             };
140             }
141             elsif ( lc $key eq 'percussion2notenum' ) {
142             return {
143 47         102 map { $_ => $MIDI::percussion2notenum{$_} }
144 1         13 sort { $MIDI::percussion2notenum{$a} <=> $MIDI::percussion2notenum{$b} }
  209         262  
145             keys %MIDI::percussion2notenum
146             };
147             }
148             elsif ( lc $key eq 'all_events' ) {
149 1         9 return \@MIDI::Event::All_events;
150             }
151             elsif ( lc $key eq 'midi_events' ) {
152 1         4 return \@MIDI::Event::MIDI_events;
153             }
154             elsif ( lc $key eq 'meta_events' ) {
155 1         5 return \@MIDI::Event::Meta_events;
156             }
157             elsif ( lc $key eq 'text_events' ) {
158 1         3 return \@MIDI::Event::Text_events;
159             }
160             elsif ( lc $key eq 'nontext_meta_events' ) {
161 1         4 return \@MIDI::Event::Nontext_meta_events;
162             }
163             else {
164 0         0 return [];
165             }
166             }
167              
168              
169             sub reverse_dump {
170 0     0 1 0 my ($name, $precision) = @_;
171              
172 0   0     0 $precision //= -1;
173              
174 0         0 my %by_value;
175              
176 0         0 my $dump = midi_dump($name); # dumps an arrayref
177              
178 0         0 for my $key (keys %$dump) {
179             my $val = $name eq 'length' && $precision >= 0
180             ? sprintf('%.*f', $precision, $dump->{$key})
181 0 0 0     0 : $dump->{$key};
182 0         0 $by_value{$val} = $key;
183             }
184              
185 0         0 return \%by_value;
186             }
187              
188              
189             sub midi_format {
190 1     1 1 2585 my (@notes) = @_;
191 1         3 my @formatted;
192 1         2 for my $note (@notes) {
193 4         8 $note =~ s/C##/D/;
194 4         4 $note =~ s/D##/E/;
195 4         7 $note =~ s/F##/G/;
196 4         6 $note =~ s/G##/A/;
197              
198 4         5 $note =~ s/Dbb/C/;
199 4         5 $note =~ s/Ebb/D/;
200 4         5 $note =~ s/Abb/G/;
201 4         6 $note =~ s/Bbb/A/;
202              
203 4         7 $note =~ s/E#/F/;
204 4         5 $note =~ s/B#/C/;
205              
206 4         5 $note =~ s/Cb/B/;
207 4         6 $note =~ s/Fb/E/;
208              
209 4         38 $note =~ s/#/s/;
210 4         7 $note =~ s/b/f/;
211              
212 4         10 push @formatted, $note;
213             }
214 1         6 return @formatted;
215             }
216              
217              
218             sub set_time_signature {
219 1     1 1 3 my ($score, $signature) = @_;
220 1         4 my ($beats, $divisions) = split /\//, $signature;
221 1 50       8 $score->time_signature(
    50          
222             $beats,
223             ($divisions == 8 ? 3 : 2),
224             ($divisions == 8 ? 24 : 18 ),
225             8
226             );
227             }
228              
229              
230             sub dura_size {
231 4     4 1 2558 my ($duration, $ppqn) = @_;
232 4   50     23 $ppqn ||= TICKS;
233 4         5 my $size = 0;
234 4 100       19 if ($duration =~ /^d(\d+)$/) {
235 2         23 $size = sprintf '%0.f', $1 / $ppqn;
236             }
237             else {
238 2         6 $size = $MIDI::Simple::Length{$duration};
239             }
240 4         17 return $size;
241             }
242              
243              
244             sub ticks {
245 1     1 1 4281 my ($score) = @_;
246 1         2 return ${ $score->{Tempo} };
  1         5  
247             }
248              
249              
250             sub timidity_conf {
251 2     2 1 2440 my ($soundfont, $config_file) = @_;
252 2         6 my $config = "soundfont $soundfont\n";
253 2 100       11 write_text($config_file, $config) if $config_file;
254 2         233 return $config;
255             }
256              
257              
258             sub play_timidity {
259 0     0 1   my ($score, $midi, $soundfont, $config) = @_;
260 0           my @cmd;
261 0 0         if ($soundfont) {
262 0   0       $config ||= 'timidity-midi-util.cfg';
263 0 0         timidity_conf($soundfont, $config) if $soundfont;
264 0           @cmd = ('timidity', '-c', $config, $midi);
265             }
266             else {
267 0           @cmd = ('timidity', $midi);
268             }
269 0           $score->write_score($midi);
270 0 0         system(@cmd) == 0 or die "system(@cmd) failed: $?";
271             }
272              
273             1;
274              
275             __END__