File Coverage

blib/lib/MIDI/Tab.pm
Criterion Covered Total %
statement 108 110 98.1
branch 30 38 78.9
condition 5 9 55.5
subroutine 14 14 100.0
pod 3 3 100.0
total 160 174 91.9


line stmt bran cond sub pod time code
1             package MIDI::Tab;
2             BEGIN {
3 2     2   45115 $MIDI::Tab::AUTHORITY = 'cpan:GENE';
4             }
5              
6             # ABSTRACT: Generate MIDI from ASCII tablature
7              
8 2     2   20 use strict;
  2         5  
  2         74  
9 2     2   20 use warnings;
  2         4  
  2         65  
10              
11 2     2   2368 use MIDI::Simple;
  2         51215  
  2         701  
12              
13 2     2   59 use base 'Exporter';
  2         5  
  2         298  
14             our @ISA = qw(Exporter);
15             our @EXPORT = qw(
16             from_guitar_tab
17             from_drum_tab
18             from_piano_tab
19             );
20              
21             our $VERSION = '0.04';
22              
23 2     2   12 use constant CONTROL => 'CTL';
  2         6  
  2         3085  
24              
25              
26             # TODO Make a mutator method for this list.
27             # TODO Don't require an made-up "line name" - just use the patch number.
28             our %drum_notes = (
29             ABD => 'n35', # Acoustic Bass Drum
30             BD => 'n36', # Bass Drum 1
31             CA => 'n69', # Cabasa
32             CB => 'n56', # Cowbell
33             CC => 'n52', # Chinese Cymbal
34             CL => 'n75', # Claves
35             CY2 => 'n57', # Crash Cymbal 2
36             CYM => 'n49', # Crash Cymbal 1
37             CYS => 'n55', # Splash Cymbal
38             ESD => 'n40', # Electric Snare
39             HA => 'n67', # High Agogo
40             HB => 'n60', # Hi Bongo
41             HC => 'n39', # Hand Clap
42             HFT => 'n43', # High Floor Tom
43             HH => 'n42', # Closed Hi-Hat
44             HMT => 'n48', # Hi-Mid Tom
45             HT => 'n50', # High Tom
46             HTI => 'n65', # High Timbale
47             HWB => 'n76', # Hi Wood Block
48             LA => 'n68', # Low Agogo
49             LB => 'n61', # Low Bongo
50             LC => 'n64', # Low Conga
51             LFT => 'n41', # Low Floor Tom
52             LG => 'n74', # Long Guiro
53             LMT => 'n47', # Low-Mid Tom
54             LT => 'n45', # Low Tom
55             LTI => 'n66', # Low Timbale
56             LW => 'n72', # Long Whistle
57             LWB => 'n77', # Low Wood Block
58             MA => 'n70', # Maracas
59             MC => 'n78', # Mute Cuica
60             MHC => 'n62', # Mute Hi Conga
61             MT => 'n80', # Mute Triangle
62             OC => 'n79', # Open Cuica
63             OHC => 'n63', # Open Hi Conga
64             OHH => 'n46', # Open Hi-Hat
65             OT => 'n81', # Open Triangle
66             PH => 'n44', # Pedal Hi-Hat
67             RB => 'n53', # Ride Bell
68             RI2 => 'n59', # Ride Cymbal 2
69             RID => 'n51', # Ride Cymbal 1
70             SD => 'n38', # Acoustic Snare
71             SG => 'n73', # Short Guiro
72             SS => 'n37', # Side Stick
73             SW => 'n71', # Short Whistle
74             TAM => 'n54', # Tambourine
75             VS => 'n58', # Vibraslap
76             );
77              
78              
79             sub from_guitar_tab {
80 1     1 1 3650 my ($score, $tab, @noop) = @_;
81              
82             # TODO Set $patch = 24 unless another is provided.
83              
84             # Add the no-ops to the score.
85 1         9 $score->noop(@noop);
86              
87             # Grab the tab lines.
88 1         75 my %lines = _parse_tab($tab);
89              
90             # Create routines for each line.
91 1         4 my @subs;
92 1         7 for my $line (keys %lines) {
93 6         19 my ($base_note_number) = is_absolute_note_spec($line);
94 6 50 33     106 die "Invalid base type: $line"
95             unless $base_note_number || $line eq CONTROL();
96              
97             my $_sub = sub {
98 6     6   71 my $score = shift;
99              
100             # Split tab lines into notes and control.
101 6         8 my @notes = ();
102 6 50       23 @notes = _split_lines(\%lines, $line, $base_note_number)
103             unless $line eq CONTROL();
104              
105             # Collect the noop controls.
106 6         18 my @control = ();
107 6 50       16 @control = _split_lines(\%lines, CONTROL())
108             if exists $lines{CONTROL()};
109              
110             # Keep track of the beat.
111 6         12 my $i = 0;
112              
113             # Add each note, rest and control noop to the score.
114 6         11 for my $n (@notes) {
115             # Set the note noop.
116 192         318 my @ctl = @noop;
117 192 50       371 @ctl = ($control[$i]) if @control;
118              
119             # Add to the score.
120 192 100       319 if (defined $n) {
121 16         46 $score->n($n, @ctl);
122             }
123             else {
124 176         450 $score->r(@ctl);
125             }
126              
127             # Increment the note we are inspecting.
128 192         7516 $i++;
129             }
130 6         27 };
131              
132             # Collect the performace subroutines.
133 6         14 push @subs, $_sub;
134             }
135              
136             # XXX This line looks suspiciously unnecessary. Hmmmmm
137             # Add the part to the score.
138 1         7 $score->synch(@subs);
139             }
140              
141              
142             sub from_drum_tab {
143 2     2 1 1908 my ($score, $tab, @noop) = @_;
144              
145             # Set the drum channel if none has been provided.
146 2         5 my $channel = 'c9';
147 2         5 for (@noop) {
148 2 50       11 if (/^(c\d+)$/) {
149 0         0 $channel = $1;
150 0         0 unshift @noop, $channel;
151             }
152             }
153              
154             # Add the no-ops to the score.
155 2         14 $score->noop(@noop);
156              
157             # Grab the tab lines.
158 2         83 my %lines = _parse_tab($tab, 'drum');
159              
160             # Create routines for each line.
161 2         4 my @subs;
162 2         8 for my $line (keys %lines) {
163             my $_sub = sub {
164 7     7   109 my $score = shift;
165              
166 7 50 66     40 die "Invalid drum type: $line"
167             unless $drum_notes{$line} || $line eq CONTROL();
168 7         16 my $drum = $drum_notes{$line};
169              
170             # Split tab lines into notes and control.
171 7         14 my @notes = ();
172 7 100       35 @notes = _split_lines(\%lines, $line)
173             unless $line eq CONTROL();
174              
175             # Collect the noop controls.
176 7         19 my @control = ();
177 7 100       24 @control = _split_lines(\%lines, CONTROL())
178             if exists $lines{CONTROL()};
179              
180             # Keep track of the beat.
181 7         13 my $i = 0;
182              
183             # Add each note, rest and control noop to the score.
184 7         34 for my $n (@notes) {
185             # Set the note noop.
186 184         322 my @ctl = @noop;
187 184 100       394 @ctl = ($control[$i]) if @control;
188              
189             # Add to the score.
190 184 100       384 if (defined $n) {
191 59         164 $score->n($channel, $drum, $n, @ctl);
192             }
193             else {
194 125         314 $score->r(@ctl);
195             }
196              
197             # Increment the note we are inspecting.
198 184         8482 $i++;
199             }
200 7         33 };
201              
202             # Collect the performace subroutines.
203 7         18 push @subs, $_sub;
204             }
205              
206             # XXX This line looks suspiciously unnecessary. Hmmmmm
207             # Add the part to the score.
208 2         34 $score->synch(@subs);
209             }
210              
211              
212             sub from_piano_tab {
213 1     1 1 2767 my ($score, $tab, @noop) = @_;
214              
215             # Add the no-ops to the score.
216 1         12 $score->noop(@noop);
217              
218             # Grab the tab lines.
219 1         42 my %lines = _parse_tab($tab);
220              
221             # Create routines for each line.
222 1         2 my @subs;
223 1         4 for my $line (keys %lines) {
224             my $_sub = sub {
225 2     2   27 my $score = shift;
226             #die "Invalid note: $line" unless ???;
227              
228             # Split tab lines into notes and control.
229 2         4 my @notes = ();
230 2         5 @notes = _split_lines(\%lines, $line);
231              
232             # Collect the noop controls.
233 2         6 my @control = ();
234 2 50       8 @control = _split_lines(\%lines, CONTROL())
235             if exists $lines{CONTROL()};
236              
237             # Keep track of the beat.
238 2         4 my $i = 0;
239              
240             # Add each note, rest and control noop to the score.
241 2         4 for my $n (@notes) {
242             # Set the note noop.
243 16         30 my @ctl = @noop;
244 16 50       38 @ctl = ($control[$i]) if @control;
245              
246             # Add to the score.
247 16 100       34 if (defined $n) {
248 8         25 $score->n($line, $n, @ctl);
249             }
250             else {
251 8         27 $score->r(@ctl);
252             }
253              
254             # Increment the note we are inspecting.
255 16         981 $i++;
256             }
257 2         10 };
258              
259             # Collect the performace subroutines.
260 2         7 push @subs, $_sub;
261             }
262              
263             # XXX This line looks suspiciously unnecessary. Hmmmmm
264             # Add the part to the score.
265 1         6 $score->synch(@subs);
266             }
267              
268             sub _parse_tab {
269 4     4   12 my($tab, $type) = @_;
270              
271             # Remove bar lines.
272 4         19 $tab =~ s/\|//g;
273              
274             # Set a regular expression to capture parts of the tab.
275 4         25 my $re = qr/^\s*([A-Za-z0-9]+)\:\s*([0-9+-]+)\s+(.*)$/s;
276 4 100 66     39 $re = qr/^\s*([A-Z]{2,3})\:\s*([0-9+-]+)\s+(.*)$/s
277             if $type && $type eq 'drum';
278              
279             # Build lines from the tablature.
280 4         11 my %lines;
281 4         48 while($tab =~ /$re/g) {
282 15         59 my ($note, $line, $remainder) = ($1, $2, $3);
283 15         43 $lines{$note} = $line;
284 15         128 $tab = $remainder;
285             }
286              
287 4         36 return %lines;
288             }
289              
290             sub _split_lines {
291 16     16   30 my($lines, $line, $base) = @_;
292              
293             # Construct a list of notes, volumes or noop controls.
294 16         19 my @items = ();
295              
296 16         130 for my $n (split '', $lines->{$line}) {
297             # Grab the control noop.
298 440 100       1308 if ($line eq CONTROL()) {
    100          
299 48 100       66 if ($n eq '3') {
300 8         14 push @items, 'ten';
301             }
302             else {
303 40         57 push @items, undef;
304             }
305             }
306             # Grab the note, itself.
307             elsif ($n =~ /^[0-9]$/) {
308 83 100       124 if ($base) {
309 16         48 push @items, 'n' . ($base + $n);
310             }
311             else {
312             # XXX This x12 bit looks suspiciously wrong.
313 67         256 push @items, 'V' . ($n * 12);
314             }
315             }
316             else {
317 309         497 push @items, undef;
318             }
319             }
320              
321 16         146 return @items;
322             }
323              
324             1;
325              
326             __END__