File Coverage

blib/lib/MIDI/Music.pm
Criterion Covered Total %
statement 32 200 16.0
branch 1 108 0.9
condition 8 18 44.4
subroutine 8 16 50.0
pod 4 4 100.0
total 53 346 15.3


line stmt bran cond sub pod time code
1             package MIDI::Music;
2              
3 1     1   742 use strict;
  1         2  
  1         31  
4 1     1   5 use Carp;
  1         2  
  1         92  
5 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD);
  1         2  
  1         96  
6 1     1   5 use Fcntl;
  1         1  
  1         530  
7              
8             require Exporter;
9             require DynaLoader;
10             require AutoLoader;
11              
12             @ISA = qw(Exporter DynaLoader);
13             @EXPORT = qw();
14             $VERSION = '0.01';
15              
16             sub AUTOLOAD {
17              
18 0     0   0 my $constname;
19 0         0 ($constname = $AUTOLOAD) =~ s/.*:://;
20 0 0       0 croak "& not defined" if $constname eq 'constant';
21 0 0       0 my $val = constant($constname, @_ ? $_[0] : 0);
22 0 0       0 if ($! != 0) {
23 0 0       0 if ($! =~ /Invalid/) {
24 0         0 $AutoLoader::AUTOLOAD = $AUTOLOAD;
25 0         0 goto &AutoLoader::AUTOLOAD;
26             }
27             else {
28 0         0 croak "Your vendor has not defined MIDI::Music macro $constname";
29             }
30             }
31 1     1   6 no strict 'refs';
  1         2  
  1         2266  
32 0     0   0 *$AUTOLOAD = sub { $val };
  0         0  
33 0         0 goto &$AUTOLOAD;
34             }
35              
36             bootstrap MIDI::Music $VERSION;
37              
38             sub DESTROY {
39 1     1   2 my $mm = shift;
40 1 50       108 $mm->close() if ($mm->{'_initialized'});
41             }
42              
43             ################################################################################
44             ################################# Constructor ##################################
45             sub new {
46              
47 1     1 1 69 my $class = shift;
48 1         3 my %param = @_;
49              
50             ######################################
51             # Device number (0 == first available)
52 1   50     9 my $device = $param{'device'} || 0; # Device number
53              
54             ##############################
55             # Recording-related parameters
56 1   50     8 my $readbuf = $param{'readbuf'} || 4096; # events per read * 8
57             #my $actsense = $param{'actsense'} || 0; # enable active sensing
58 1   50     7 my $realtime = $param{'realtime'} || 0; # realtime messages on
59             #my $timing = $param{'timing'} || 0; # timer on (requires rt(?))
60              
61             #############################
62             # Playback-related parameters
63             #my $extbuf = $param{'extbuf'} || 0; # ?? not documented
64 1   50     7 my $gmdrum = $param{'gmdrum'} || []; # Drums to cache
65 1   50     7 my $gminstr = $param{'gminstr'} || []; # Patches to cache
66              
67             ###################################
68             # Initial timing parameters
69 1   50     7 my $timebase = $param{'timebase'} || 96; # Ticks per quarter-note
70 1   50     6 my $tempo = $param{'tempo'} || 120; # BPM
71 1   50     10 my $timesig = $param{'timesig'} || [0x04, 0x02, 0x18, 0x08]; # 4/4
72              
73 1         4 my $ppqn_per_clock = $timebase / 24; # Clocks (pulses) per quarter-note
74              
75 1         15 my $self = {'_device' => $device,
76             '_errstr' => '',
77             '_initialized' => 0,
78              
79             '_event_times' => [], # For the storage of
80             '_midistruct' => {}, # MIDI file data
81              
82             '_readbuf' => $readbuf,
83             #'_actsense' => $actsense,
84             '_realtime' => $realtime,
85             #'_timing' => $timing,
86             '_rec_dtime' => 0, # if timing is enabled, this is
87             # calculated and supplied as
88             # 1th element of text events
89              
90             #'_extbuf' => $extbuf,
91             '_gmdrum' => $gmdrum,
92             '_gminstr' => $gminstr,
93              
94             '_tempo' => $tempo,
95             '_timebase' => $timebase,
96             '_ppqn_per_clock' => $ppqn_per_clock,
97              
98             '_timesig' => $timesig,
99             };
100              
101 1         5 return bless $self, $class;
102             }
103              
104             ################################################################################
105             ########################### Misc. ##############################################
106             sub errstr {
107 1     1 1 149 my $mm = shift;
108 1         65 return $mm->{'_errstr'};
109             }
110              
111              
112             ################################################################################
113             ####################### MIDI File Methods ######################################
114             sub _clear {
115              
116 0     0     my $mm = shift;
117 0           $mm->{'_midistruct'} = {};
118 0           $mm->{'_event_times'} = [];
119             }
120              
121             sub _loadmidifile {
122              
123 0     0     my $mm = shift;
124 0           my $midifile = shift;
125              
126 0 0         if (eval 'require MIDI') {
127              
128 0           my $opus = MIDI::Opus->new({'from_file' => $midifile});
129              
130 0           $mm->{'_timebase'} = $opus->ticks();
131 0           my $format = $opus->format();
132 0           my $pos = 0;
133              
134 0           foreach my $track (@{$opus->tracks_r}) {
  0            
135              
136 0           my $events_r = $track->events_r;
137 0 0         $pos = 0 unless ($format == 2);
138              
139 0           foreach my $event (@{$events_r}) {
  0            
140              
141 0           $pos += $event->[1]; # Add current dtime to last position
142              
143 0           push(@{$mm->{'_midistruct'}->{$pos}}, $event);
  0            
144             }
145             }
146 0           $mm->{'_event_times'} = [ sort { $a <=> $b } keys %{$mm->{'_midistruct'}} ];
  0            
  0            
147              
148             } else {
149 0           $mm->{'_errstr'} = ref($mm) . "::_loadmidifile(): require MIDI: $!";
150 0           return 0;
151             }
152 0           return 1;
153             }
154              
155             sub playmidifile {
156              
157 0     0 1   my $mm = shift;
158 0   0       my $midifile = shift || '';
159              
160 0 0         if ($midifile) {
161              
162 0 0         unless ($mm->_loadmidifile($midifile)) {
163 0           $mm->{'_errstr'} = ref($mm) . '::playmidifile(): ' . $mm->{'_errstr'};
164 0           return 0;
165             }
166              
167             } else {
168 0           $mm->{'_errstr'} = ref($mm) . '::playmidifile(): no file supplied';
169 0           return 0;
170             }
171              
172 0 0         unless ($mm->{'_initialized'}) {
173              
174 0 0         unless ($mm->init('mode' => O_WRONLY)) {
175 0           $mm->{'_errstr'} = ref($mm) . '::playmidifile(): ' . $mm->{'_errstr'};
176 0           return 0;
177             }
178             }
179              
180 0 0         $mm->_playloaded() || do {
181 0           $mm->{'_errstr'} = ref($mm) . '::playmidifile(): ' . $mm->{'_errstr'};
182 0           return 0;
183             };
184 0           $mm->close();
185 0           $mm->_clear();
186              
187 0           return 1;
188             }
189              
190             sub _playloaded {
191              
192 0     0     my $mm = shift;
193 0           my $dtime = 0;
194 0           my $last = 0;
195              
196 0           my $events = [];
197 0           for (0 .. $#{$mm->{'_event_times'}}) {
  0            
198              
199 0           my $pos = $mm->{'_event_times'}->[$_];
200 0           my $dtime = $pos - $last;
201              
202 0           foreach my $event (@{ $mm->{'_midistruct'}->{$pos} }) {
  0            
203              
204 0           $event->[1] = $dtime;
205 0           push @$events, $event;
206              
207 0           $dtime = 0;
208             }
209 0           $last = $pos;
210             }
211 0 0         $mm->playevents($events) || do {
212 0           $mm->{'_errstr'} = ref($mm) . '::_playloaded(): ' . $mm->{'_errstr'};
213 0           return 0;
214             };
215 0           $mm->dumpbuf();
216 0           return 1;
217             }
218              
219             ################################################################################
220             ############################## Recording #######################################
221             sub _readevents_OSS {
222              
223 0     0     my $mm = shift;
224 0           my $data = $mm->_readblock();
225 0           my $events = [];
226              
227 0 0         $events = [ map { [ unpack('C8', substr($data, ($_ * 8), 8)) ]
  0            
228             } (0 .. ((length($data) / 8) - 1))
229             ] if ($data);
230              
231 0           return $events;
232             }
233              
234             sub readevents {
235              
236 0     0 1   my $mm = shift;
237 0           my $events = $mm->_readevents_OSS();
238 0           my $decoded = [];
239              
240 0 0         if (@{$events}) {
  0            
241              
242 0           for (0 .. $#{$events}) {
  0            
243              
244 0           my $event = $events->[$_];
245 0           my $ev_decoded = [];
246              
247 0 0         if ($event->[0] == &EV_CHN_VOICE) {
    0          
    0          
    0          
    0          
    0          
248              
249 0 0         if ($event->[2] == &MIDI_NOTEON) {
    0          
    0          
250              
251 0           $ev_decoded->[0] = 'note_on';
252 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
253 0           $ev_decoded->[2] = $event->[3]; # channel
254 0           $ev_decoded->[3] = $event->[4]; # note
255 0           $ev_decoded->[4] = $event->[5]; # velocity
256              
257 0           $mm->{'_rec_dtime'} = 0;
258              
259             } elsif ($event->[2] == &MIDI_NOTEOFF) {
260              
261 0           $ev_decoded->[0] = 'note_off';
262 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
263 0           $ev_decoded->[2] = $event->[3]; # channel
264 0           $ev_decoded->[3] = $event->[4]; # note
265 0           $ev_decoded->[4] = $event->[5]; # velocity
266              
267 0           $mm->{'_rec_dtime'} = 0;
268              
269             } elsif ($event->[2] == &MIDI_KEY_PRESSURE) {
270              
271 0           $ev_decoded->[0] = 'key_after_touch';
272 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
273 0           $ev_decoded->[2] = $event->[3]; # channel
274 0           $ev_decoded->[3] = $event->[4]; # note
275 0           $ev_decoded->[4] = $event->[5]; # velocity
276              
277 0           $mm->{'_rec_dtime'} = 0;
278             }
279              
280             } elsif ($event->[0] == &EV_CHN_COMMON) {
281              
282 0 0         if ($event->[2] == &MIDI_CHN_PRESSURE) {
    0          
    0          
    0          
283              
284 0           $ev_decoded->[0] = 'channel_after_touch';
285 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
286 0           $ev_decoded->[2] = $event->[3]; # channel
287 0           $ev_decoded->[3] = $event->[4]; # velocity
288              
289 0           $mm->{'_rec_dtime'} = 0;
290              
291             } elsif ($event->[2] == &MIDI_PGM_CHANGE) {
292              
293 0           $ev_decoded->[0] = 'patch_change';
294 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
295 0           $ev_decoded->[2] = $event->[3]; # channel
296 0           $ev_decoded->[3] = $event->[4]; # program
297              
298 0           $mm->{'_rec_dtime'} = 0;
299              
300             } elsif ($event->[2] == &MIDI_CTL_CHANGE) { # control change
301              
302 0           $ev_decoded->[0] = 'control_change';
303 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
304 0           $ev_decoded->[2] = $event->[3]; # channel
305 0           $ev_decoded->[3] = $event->[4]; # controller
306 0           $ev_decoded->[4] = $event->[5]; # value
307              
308 0           $mm->{'_rec_dtime'} = 0;
309              
310             } elsif ($event->[2] == &MIDI_PITCH_BEND) {
311              
312 0           $ev_decoded->[0] = 'pitch_wheel_change';
313 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
314 0           $ev_decoded->[2] = $event->[3]; # channel
315 0           $ev_decoded->[3] = ($event->[7] * 256) - 8192; # value
316              
317             }
318              
319             #### fix this #####
320              
321             } elsif ($event->[0] == &EV_SYSEX) {
322              
323 0           $ev_decoded->[0] = 'sysex_f0'; # ???
324 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
325 0           $ev_decoded->[2] = $event->[2]; # data - six bytes at a time! (stupid)
326              
327 0           for (;;) {
328 0           my $events_oss = $mm->_readevent_OSS();
329            
330             }
331              
332 0           $mm->{'_rec_dtime'} = 0;
333              
334             } elsif ($event->[0] == &EV_TIMING) {
335              
336 0 0         if ($event->[1] == &TMR_START) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
337             } elsif ($event->[1] == &TMR_STOP) {
338             } elsif ($event->[1] == &TMR_CONTINUE) {
339             } elsif ($event->[1] == &TMR_WAIT_ABS) {
340             } elsif ($event->[1] == &TMR_WAIT_REL) {
341             } elsif ($event->[1] == &TMR_ECHO) {
342             } elsif ($event->[1] == &TMR_TEMPO) {
343              
344             #### possible need for fix here ###
345             #
346 0           $ev_decoded->[0] = 'set_tempo';
347 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
348 0           $ev_decoded->[2] = $event->[4]; ## not sure if this is correct..
349              
350 0           $mm->{'_rec_dtime'} = 0;
351              
352             } elsif ($event->[1] == &TMR_SPP) { # not sure how this differs
353             # from 0xf2...
354              
355 0           $ev_decoded->[0] = 'song_position';
356 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
357              
358 0           $mm->{'_rec_dtime'} = 0;
359              
360             } elsif ($event->[1] == &TMR_TIMESIG) {
361              
362 0           $ev_decoded->[0] = 'time_signature';
363 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
364              
365 0           my $timesig = $event->[4];
366              
367 0           $ev_decoded->[2] = ($timesig >> 0x18) & 0xff;
368 0           $ev_decoded->[3] = ($timesig >> 0x10) & 0xff;
369 0           $ev_decoded->[4] = ($timesig >> 0x08) & 0xff;
370 0           $ev_decoded->[5] = $timesig & 0xff;
371              
372 0           $mm->{'_rec_dtime'} = 0;
373             }
374              
375             } elsif ($event->[0] == &EV_SEQ_LOCAL) {
376             } elsif ($event->[0] == &EV_SYSTEM) {
377              
378 0 0         if ($event->[2] == 0xf0) { # sysex
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
379             } elsif ($event->[2] == 0xf1) { # MTC Qframe
380              
381             } elsif ($event->[2] == 0xf2) {
382              
383 0           $ev_decoded->[0] = 'song_position';
384 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
385              
386 0           $mm->{'_rec_dtime'} = 0;
387              
388             } elsif ($event->[2] == 0xf3) { # song select
389              
390 0           $ev_decoded->[0] = 'song_select';
391 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
392 0           $ev_decoded->[2] = $event->[3]; # song number
393              
394 0           $mm->{'_rec_dtime'} = 0;
395              
396             } elsif ($event->[2] == 0xf4) {
397             } elsif ($event->[2] == 0xf5) {
398             } elsif ($event->[2] == 0xf6) { # tune request
399              
400 0           $ev_decoded->[0] = 'tune_request';
401 0           $ev_decoded->[1] = $mm->{'_rec_dtime'};
402              
403 0           $mm->{'_rec_dtime'} = 0;
404              
405             } elsif ($event->[2] == 0xf7) { # end-of-sysex
406             } elsif ($event->[2] == 0xf8) { # timing clock
407              
408             # print "Timing clock\n";
409              
410 0           $mm->{'_rec_dtime'} += $mm->{'_ppqn_per_clock'};
411              
412             } elsif ($event->[2] == 0xf9) {
413             } elsif ($event->[2] == 0xfa) { # start
414             } elsif ($event->[2] == 0xfb) { # continue
415             } elsif ($event->[2] == 0xfc) { # stop
416             } elsif ($event->[2] == 0xfd) {
417             } elsif ($event->[2] == 0xfe) { # active sensing
418              
419             # print "Active sensing\n";
420              
421             } elsif ($event->[2] == 0xff) { # reset
422             }
423             }
424 0 0         push(@{$decoded}, $ev_decoded) if (@{$ev_decoded});
  0            
  0            
425             }
426             }
427 0           return $decoded;
428             }
429              
430             1;
431              
432             __END__