File Coverage

blib/lib/MIDI/Tab.pm
Criterion Covered Total %
statement 107 109 98.1
branch 30 38 78.9
condition 5 9 55.5
subroutine 13 13 100.0
pod 3 3 100.0
total 158 172 91.8


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