File Coverage

blib/lib/Music/Harmonica/TabsCreator.pm
Criterion Covered Total %
statement 215 252 85.3
branch 34 46 73.9
condition 13 22 59.0
subroutine 26 29 89.6
pod 2 14 14.2
total 290 363 79.8


line stmt bran cond sub pod time code
1             package Music::Harmonica::TabsCreator;
2              
3 3     3   634062 use 5.036;
  3         12  
4 3     3   15 use strict;
  3         7  
  3         58  
5 3     3   11 use warnings;
  3         5  
  3         109  
6 3     3   28 use utf8;
  3         5  
  3         26  
7              
8 3     3   2737 use English;
  3         8481  
  3         25  
9 3     3   1259 use Exporter qw(import);
  3         5  
  3         90  
10 3     3   1880 use List::MoreUtils qw(first_index);
  3         47635  
  3         22  
11 3     3   3497 use List::Util qw(min max none any);
  3         10  
  3         341  
12 3     3   1584 use Music::Harmonica::TabsCreator::NoteToToneConverter;
  3         14  
  3         154  
13 3     3   1543 use Music::Harmonica::TabsCreator::TabParser;
  3         10  
  3         101  
14 3     3   1347 use Music::Harmonica::TabsCreator::Warning;
  3         8  
  3         93  
15 3     3   46 use Readonly;
  3         6  
  3         212  
16 3     3   20 use Scalar::Util qw(looks_like_number);
  3         5  
  3         11885  
17              
18             our $VERSION = '1.06';
19              
20             our @EXPORT_OK = qw(tune_to_tab get_tuning_details tune_to_tab_rendered
21             transpose_tab transpose_tab_rendered list_tunings);
22              
23             # Options to add:
24             # - print B as H (international convention), but probably not Bb which stays Bb.
25              
26             Readonly my $TONES_PER_SCALE => 12;
27              
28             # TODO: add a param so that the output prefers a tab starting at 1 rather than
29             # 1° when possible.
30 18     18 0 34 sub extend_chromatic_tuning ($tuning, $fix) {
  18         29  
  18         23  
  18         24  
31 18         25 my $size = @{$tuning->{notes}};
  18         34  
32 18         71 for my $i (0 .. $size - 1) {
33 420         580 push @{$tuning->{tabs}}, sprintf('(%s)', $tuning->{tabs}[$i]);
  420         991  
34 420 50       1029 $tuning->{notes}[$i] =~ m/^(\w)(\d)$/ or die 'Unexpected error';
35 420         511 push @{$tuning->{notes}}, "${1}#${2}";
  420         1201  
36             }
37 18 100       67 if ($fix) {
38             # Some brand use a D instead of a C (== B#) as the draw slide in, to give
39             # one more note (instead of duplicating the C). It’s not really an issue if
40             # the harmonica does not have it as the missing C is there anyway on the
41             # harmonica (and worst case, the tab can’t be played if it requires (-12)).
42 9 50       42 die 'Unexpected error' unless $tuning->{notes}[-1] =~ m/^B#(\d+)$/;
43 9         34 $tuning->{notes}[-1] = 'D'.($1 + 1);
44             }
45             # We put the notes with the slides pushed-in at the beginning of the array so
46             # that equivalent notes with the slide out, which comes later, are used by
47             # default.
48 18         25 push @{$tuning->{tabs}}, splice(@{$tuning->{tabs}}, 0, $size);
  18         30  
  18         124  
49 18         64 push @{$tuning->{notes}}, splice(@{$tuning->{notes}}, 0, $size);
  18         34  
  18         87  
50 18         77 @{$tuning->{bends}} = ((1) x ($size + 1), (0) x ($size));
  18         149  
51 18         72 $tuning->{is_chromatic} = 1;
52 18         421 return $tuning;
53             }
54              
55             #<<< let’s not run perltidy over this portion as I want to keep the arrays
56             # aligned for better readability.
57             Readonly my %ALL_TUNINGS => (
58             # Written in the key of C to match the default key used in the note_to_tone
59             # function.
60             # Note that when we have the same note appears multiple time (like -2 and 3 in
61             # Richter scale) we always use only the last appearance one when rendering a
62             # tab (but other appearances are still used when reading a tab).
63             richter => {
64             tags => [qw(diatonic 10-holes major)],
65             name => 'Richter',
66             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
67             notes => [qw(C4 D4 E4 G4 G4 B4 C5 D5 E5 F5 G5 A5 C6 B5 E6 D6 G6 F6 C7 A6)],
68             bends => [qw( 0 1 0 2 0 3 0 1 0 0 0 1 0 0 1 0 1 0 2 0)],
69             key => 'C',
70             },
71             melody_maker => {
72             tags => [qw(diatonic 10-holes major)],
73             name => 'Melody Maker',
74             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
75             notes => [qw(C4 D4 E4 G4 A4 B4 C5 D5 E5 F+5 G5 A5 C6 B5 E6 D6 G6 F+6 C7 A6)],
76             bends => [qw( 0 1 0 2 0 1 0 1 0 1 0 1 0 0 1 0 0 0 2 0)],
77             key => 'G',
78             # TODO: Only the C, D, Eb, E, F, G, A and Bb keys exist (not the Db, F#, Ab
79             # and B ones). But nothing prevents the missing ones from being generated
80             # for now.
81             },
82             natural_minor => {
83             tags => [qw(diatonic 10-holes minor)],
84             name => 'Natural Minor',
85             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
86             notes => [qw(C4 D4 Eb4 G4 G4 Bb4 C5 D5 Eb5 F5 G5 A5 C6 Bb5 Eb6 D6 G6 F6 C7 A6)],
87             bends => [qw( 0 1 0 3 0 2 0 1 0 1 0 1 1 0 0 0 1 0 2 0)],
88             # TODO: The real harmonica is labelled as Gm but we don’t support that
89             # annotation for now.
90             key => 'G',
91             },
92             harmonic_minor => {
93             tags => [qw(diatonic 10-holes minor)],
94             name => 'Harmonic Minor',
95             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
96             notes => [qw(C4 D4 Eb4 G4 G4 B4 C5 D5 Eb5 F5 G5 Ab5 C6 B5 Eb6 D6 G6 F6 C7 Ab6)],
97             bends => [qw( 0 1 0 3 0 3 0 1 0 1 0 0 0 0 0 0 1 0 3 0)],
98             # TODO: check the bends that are actually used, this is just my guess.
99             key => 'C',
100             },
101             solo => {
102             tags => [qw(diatonic 10-holes major)],
103             name => 'Solo',
104             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
105             notes => [qw(C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5 C6 D6 E6 F6)],
106             bends => [qw( 0 1 1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0)],
107             # TODO: check the bends that are actually used, this is just my guess.
108             key => 'C',
109             },
110             paddy => {
111             tags => [qw(diatonic 10-holes major)],
112             name => 'Paddy Richter',
113             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
114             notes => [qw(C4 D4 E4 G4 A4 B4 C5 D5 E5 F5 G5 A5 C6 B5 E6 D6 G6 F6 C7 A6)],
115             bends => [qw( 0 1 0 2 1 1 0 1 0 0 0 1 0 0 1 0 1 0 2 0)],
116             # TODO: check the bends that are actually used, this is just my guess.
117             key => 'C',
118             },
119             solo_8 => extend_chromatic_tuning({
120             tags => [qw(chromatic 8-holes major)],
121             name => 'Solo 8 – Chrometta',
122             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8)],
123             notes => [qw(C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5)],
124             key => 'C',
125             },
126             0
127             ),
128             chrometta_10 => extend_chromatic_tuning({
129             tags => [qw(chromatic 10-holes major)],
130             name => 'Chrometta 10',
131             tabs => [qw( 3° -3° 4° -4° 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8)],
132             notes => [qw(G3 A3 C4 B3 C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5)],
133             key => 'C',
134             },
135             0
136             ),
137             solo_10 => extend_chromatic_tuning({
138             tags => [qw(chromatic 10-holes major)],
139             name => 'Solo 10',
140             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10)],
141             notes => [qw(C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5 C6 D6 E6 F6)],
142             key => 'C',
143             },
144             0
145             ),
146             solo_12 => extend_chromatic_tuning({
147             tags => [qw(chromatic 12-holes major)],
148             name => 'Solo 12',
149             tabs => [qw( 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10 11 -11 12 -12)],
150             notes =>[qw( C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5 C6 D6 E6 F6 G6 A6 C7 B6)],
151             key => 'C',
152             },
153             1
154             ),
155             solo_14 => extend_chromatic_tuning({
156             tags => [qw(chromatic 14-holes major)],
157             name => 'Solo 14',
158             tabs => [qw( 3° -3° 4° -4° 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10 11 -11 12 -12)],
159             notes => [qw(G3 A3 C4 B3 C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5 C6 D6 E6 F6 G6 A6 C7 B6)],
160             key => 'C',
161             avoid_tones => 5,
162             },
163             1
164             ),
165             solo_16 => extend_chromatic_tuning({
166             tags => [qw(chromatic 16-holes major)],
167             name => 'Solo 16',
168             tabs => [qw( 1° -1° 2° -2° 3° -3° 4° -4° 1 -1 2 -2 3 -3 4 -4 5 -5 6 -6 7 -7 8 -8 9 -9 10 -10 11 -11 12 -12)],
169             notes => [qw(C3 D3 E3 F3 G3 A3 C4 B3 C4 D4 E4 F4 G4 A4 C5 B4 C5 D5 E5 F5 G5 A5 C6 B5 C6 D6 E6 F6 G6 A6 C7 B6)],
170             key => 'C',
171             avoid_tones => 12,
172             },
173             1
174             ),
175             xylophone => {
176             tags => [qw(diatonic 12-bars major other)],
177             name => 'Xylophone',
178             tabs => [qw( 1 2 3 4 5 6 7 8 9 10 11 12)],
179             notes => [qw(C4 D4 E4 F4 G4 A4 B4 C5 D5 E5 F5 G5)],
180             bends => [qw( 0 0 0 0 0 0 0 0 0 0 0 0)],
181             key => 'C',
182             },
183             triola => {
184             tags => [qw(diatonic 12-notes major other)],
185             name => 'Triola',
186             tabs => [qw( 1 2 3 4 5 6 7 8 9 10 11 12)],
187             notes => [qw(G3 A3 B3 C4 D4 E4 F4 G4 A4 B4 C5 D5)],
188             bends => [qw( 0 0 0 0 0 0 0 0 0 0 0 0)],
189             key => 'C',
190             },
191             );
192             #>>>
193              
194             # We can’t use qw() because of the # that triggers a warning.
195             Readonly my @KEYS_OFFSET => split / /, q(C Db D Eb E F F# G Ab A Bb B);
196             Readonly my %KEYS_TO_TONE => map { $KEYS_OFFSET[$_] => $_ } 0 .. $#KEYS_OFFSET;
197              
198             Readonly my $MAX_BENDS => 6; # Probably higher than any realistic value.
199              
200 16     16 0 29 sub get_preferred_key (%options) {
  16         42  
  16         27  
201 16   100     98 my $key = $options{preferred_key} // 'C';
202 16         97 my $note_converter = Music::Harmonica::TabsCreator::NoteToToneConverter->new();
203 16         42 my @key_tone = eval { $note_converter->convert($key) };
  16         59  
204 16 50 33     146 die "Invalid key: $key\n" if $@ || @key_tone != 1;
205 16         65 return $key_tone[0] % $TONES_PER_SCALE;
206             }
207              
208 17     17 1 151268 sub tune_to_tab ($sheet, %options) {
  17         50  
  17         50  
  17         35  
209 17         124 my $note_converter = Music::Harmonica::TabsCreator::NoteToToneConverter->new();
210 17         80 my @tones = $note_converter->convert($sheet);
211 16   100     140 my $tunings = generate_tunings($options{max_bends} // 0, $options{tunings} // []);
      100        
212 16         74 return find_matching_tuning(\@tones, $tunings, get_preferred_key(%options));
213             }
214              
215 0     0 0 0 sub transpose_tab ($tab, $tuning_id, $key, %options) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
216 0 0       0 die "Unknown tuning: $tuning_id\n" unless exists $ALL_TUNINGS{$tuning_id};
217             # For the input, we accept any level of bending.
218 0         0 my $tuning = generate_tunings($MAX_BENDS, [$tuning_id])->{$tuning_id};
219 0         0 my %tab_to_tones = map { $tuning->{tabs}[$_] => $tuning->{tones}[$_] } 0 .. $#{$tuning->{tabs}};
  0         0  
  0         0  
220 0         0 my $parser = Music::Harmonica::TabsCreator::TabParser->new(\%tab_to_tones);
221 0         0 my @tones = $parser->parse($tab);
222 0         0 my $note_converter = Music::Harmonica::TabsCreator::NoteToToneConverter->new();
223 0         0 my @key_tone = eval { $note_converter->convert($key) };
  0         0  
224 0 0 0     0 die "Invalid key: $key\n" if $@ || @key_tone != 1;
225 0         0 my $key_tone = $key_tone[0];
226 0 0       0 @tones = map { looks_like_number($_) ? $_ + $key_tone : $_ } @tones;
  0         0  
227 0   0     0 my $tunings = generate_tunings($options{max_bends} // 0, $options{tunings} // []);
      0        
228 0         0 return find_matching_tuning(\@tones, $tunings, get_preferred_key(%options));
229             }
230              
231             # Given the text representation of one note, and a bend level, generate the text
232             # of the bended note.
233 5133     5133 0 7753 sub bend ($tab, $b) {
  5133         8152  
  5133         7721  
  5133         6916  
234 5133 100       16555 return $tab if $b == 0;
235 641         1464 my $bend = ('"' x ($b / 2)).("'" x ($b % 2));
236 641 100       2110 if ($tab =~ m/^\((.+)\)$/) {
237 420         1810 return "(${1}${bend})";
238             } else {
239 221         656 return ${tab}.${bend};
240             }
241             }
242              
243             # We take the global %ALL_TUNINGS and generate a %tunings hash with the same
244             # keys but where the values only have the tab and a new matching 'tone' entries.
245             # But we have added the notes corresponding to the allowed bends.
246 16     16 0 34 sub generate_tunings ($max_bends, $tunings) {
  16         28  
  16         35  
  16         24  
247 16         28 my %out;
248 16         60 my $note_converter = Music::Harmonica::TabsCreator::NoteToToneConverter->new();
249 16         91 while (my ($k, $v) = each %ALL_TUNINGS) {
250 224 100 100 97   4182 next if @{$tunings} && none { $_ eq $k } @{$tunings};
  224         739  
  97         510  
  84         242  
251 146         287 for my $i (0 .. $#{$v->{notes}}) {
  146         526  
252 4492         45440 my $base_tone = ($note_converter->convert($v->{notes}[$i]))[0];
253             # We apply a correction so that we have the tones of a C-harmonica as the
254             # offset we will compute in match_notes_to_tuning is assuming that the
255             # tuning was given for a C-harmonica.
256 4492         17168 $base_tone -= $KEYS_TO_TONE{$v->{key}};
257 4492         52083 my $tab = $v->{tabs}[$i];
258 4492         52985 for my $b (0 .. min($max_bends, $v->{bends}[$i])) {
259 5133         53476 push @{$out{$k}{tones}}, $base_tone - $b;
  5133         14033  
260 5133         7938 push @{$out{$k}{tabs}}, bend($tab, $b);
  5133         11880  
261             }
262 4492         16171 $out{$k}{is_chromatic} = $v->{is_chromatic};
263 4492 100       33606 $out{$k}{avoid_tones} = $v->{avoid_tones} if exists $v->{avoid_tones};
264             }
265             }
266 16         294 return \%out;
267             }
268              
269 16     16 0 164 sub find_matching_tuning ($tones, $tunings, $preferred_key) {
  16         30  
  16         27  
  16         46  
  16         25  
270 16         55 my %all_matches;
271 16         33 while (my ($k, $v) = each %{$tunings}) {
  148         537  
272 133         363 my @matches = match_notes_to_tuning($tones, $v, $preferred_key);
273 132         275 for my $m (@matches) {
274 353         489 push @{$all_matches{$k}{$m->[1]}}, $m->[0];
  353         1238  
275             }
276             }
277 15         939 return %all_matches;
278             }
279              
280 3     3 1 146681 sub tune_to_tab_rendered ($sheet, %options) {
  3         8  
  3         8  
  3         6  
281 3         7 my %tabs = eval { tune_to_tab($sheet, %options) };
  3         32  
282 3 100       60 return $@ if $@;
283 1         7 return render_tabs(%tabs);
284             }
285              
286 0     0 0 0 sub transpose_tab_rendered ($tab, $tuning, $key, %options) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
287 0         0 my %tabs = eval { transpose_tab($tab, $tuning, $key, %options) };
  0         0  
288 0 0       0 return $@ if $@;
289 0         0 return render_tabs(%tabs);
290             }
291              
292 1     1 0 3 sub render_tabs (%tabs) {
  1         7  
  1         2  
293 1 50       5 if (!%tabs) {
294 0         0 return 'No tabs found';
295             }
296              
297 1         2 my $out;
298              
299 1         11 for my $type (sort keys %tabs) {
300 13         38 my %details = get_tuning_details($type);
301 13         319 my @tags = @{$details{tags}};
  13         52  
302 13     41   439 my $other_idx = first_index { $_ eq 'other' } @tags;
  41         78  
303 13 100       53 if ($other_idx == -1) {
304 11         55 $out .= sprintf "For %s %s tuning harmonicas:\n", join(' ', @tags), $details{name};
305             } else {
306 2         6 splice @tags, $other_idx, 1;
307 2         9 $out .= sprintf "For %s %s:\n", join(' ', @tags), $details{name};
308             }
309 13         24 for my $key (sort keys %{$tabs{$type}}) {
  13         55  
310 13         25 $out .= " In the key of ${key}:\n";
311 13         22 for my $tab (@{$tabs{$type}{$key}}) {
  13         31  
312 16         45 my $str_tab;
313 16         31 my $was_nl = 1;
314 16         25 for my $t (@{$tab}) {
  16         31  
315 128 100       459 $str_tab .= ($was_nl ? ' ' : ' ').$t;
316 128         310 $was_nl = $t =~ m/\v\z/;
317             }
318 16         177 $str_tab =~ s/\A\s+|\s+\Z//g;
319 16         93 $out .= ${str_tab}."\n\n";
320             }
321             }
322             }
323              
324 1         73 return $out;
325             }
326              
327 13     13 0 19 sub get_tuning_details ($key) {
  13         25  
  13         22  
328 13         23 return %{$ALL_TUNINGS{$key}}{qw(name tags)};
  13         75  
329             }
330              
331 0     0 0 0 sub list_tunings () {
  0         0  
332 0         0 return map { {id => $_, name => $ALL_TUNINGS{$_}{name}, tags => $ALL_TUNINGS{$_}{tags}} }
  0         0  
333             sort keys %ALL_TUNINGS;
334             }
335              
336             # Given all the tones (with C0 = 0) of a melody and the data of a given
337             # harmonica tuning, returns whether the melody can be played on this
338             # harmonica and, if yes, the octave shift to apply to the melody.
339 133     133 0 203 sub match_notes_to_tuning ($tones, $tuning, $preferred_key) {
  133         229  
  133         221  
  133         185  
  133         190  
340 133         524 my $note_converter = Music::Harmonica::TabsCreator::NoteToToneConverter->new();
341 133         259 my ($scale_min, $scale_max) = (min(@{$tuning->{tones}}), max(@{$tuning->{tones}}));
  133         555  
  133         449  
342 133         260 my @real_tones = grep { looks_like_number($_) } @{$tones};
  944         2011  
  133         306  
343 133 100       742 die Music::Harmonica::TabsCreator::Warning->new('No melody found in input') unless @real_tones;
344 132         394 my ($tones_min, $tones_max) = (min(@real_tones), max(@real_tones));
345 132         250 my %scale_tones = map { $tuning->{tones}[$_] => $tuning->{tabs}[$_] } 0 .. $#{$tuning->{tones}};
  4709         12536  
  132         403  
346 132         942 my ($o_min, $o_max) = ($scale_min - $tones_min, $scale_max - $tones_max);
347 132         224 my @matches;
348              
349             # max_offset is set for Chromatic harmonicas to avoid generating the
350             # same thing over and over again, transposed by a full octave.
351 132         216 my $o_max_low = $o_max;
352 132 100       504 $o_max_low = min($o_max, $o_min + $TONES_PER_SCALE - 1) if $tuning->{is_chromatic};
353             # For some tunings we prefer not to generate the first few tones (typically
354             # anything before middle C on a chromatic harmonica). This settings offset the
355             # search pattern if possible. We note that if is_chromatic was not set, then
356             # this will do nothing because $o_max == $o_max_low.
357 132 100       1591 if (exists $tuning->{avoid_tones}) {
358 21         62 my $o = min($tuning->{avoid_tones}, $o_max - $o_max_low);
359 21         42 $o_max_low += $o;
360 21         36 $o_min += $o;
361             }
362              
363 132         353 for my $o ($o_min .. $o_max_low) { # (min($o_max, $o_min + $TONES_PER_SCALE - 1))) {
364 1571         7783 my @tab = tab_from_tones($tones, $o, %scale_tones);
365 1571 100       5468 if (@tab) {
366 495         1819 my $key = ($TONES_PER_SCALE - $o) % $TONES_PER_SCALE;
367 495         5118 my $match = [\@tab, $KEYS_OFFSET[$key]];
368 495 100 100     4634 if ($tuning->{is_chromatic} && $key == $preferred_key) {
369 51         830 return $match;
370             }
371 444         1045 push @matches, $match;
372             }
373             }
374 81         681 return @matches;
375             }
376              
377 1571     1571 0 2373 sub tab_from_tones($tones, $offset, %scale_tones) {
  1571         2366  
  1571         2246  
  1571         13601  
  1571         2384  
378 1571         2334 my @tab;
379 1571         2280 for my $t (@{$tones}) {
  1571         2916  
380 5336 100       10532 if (looks_like_number($t)) {
381 5315 100       14601 return unless exists $scale_tones{$t + $offset};
382 4239         8883 push @tab, $scale_tones{$t + $offset};
383             } else {
384 21         53 push @tab, $t;
385             }
386             }
387 495         4144 return @tab;
388             }
389              
390             1;
391              
392             # TODO: document the options of the methods.
393              
394             __END__