File Coverage

lib/ChordPro/Chords/Parser.pm
Criterion Covered Total %
statement 504 611 82.4
branch 154 246 62.6
condition 71 142 50.0
subroutine 77 100 77.0
pod 0 10 0.0
total 806 1109 72.6


line stmt bran cond sub pod time code
1             #! perl
2              
3 81     81   1017 use v5.26;
  81         306  
4 81     81   449 use utf8;
  81         203  
  81         450  
5 81     81   1873 use Carp;
  81         207  
  81         4422  
6 81     81   551 use feature qw( signatures );
  81         169  
  81         6790  
7 81     81   643 no warnings "experimental::signatures";
  81         165  
  81         3483  
8              
9             # package ParserWatch;
10             #
11             # require Tie::Hash;
12             # our @ISA = qw( Tie::StdHash );
13             #
14             # sub STORE {
15             # if ( $_[1] !~ /^[[:alpha:]]+$/ ) {
16             # Carp::cluck("STORE $_[1] " . $_[2]);
17             # ::dump($_[2]);
18             # }
19             # $_[0]->{$_[1]} = $_[2];
20             # }
21              
22 81     81   512 use ChordPro;
  81         194  
  81         92796  
23              
24             my %parsers;
25              
26             # tie %parsers => 'ParserWatch';
27              
28             package ChordPro::Chords::Parser;
29              
30             # The parser analyses a chord and returns an object with the following
31             # attributes:
32             #
33             # name name as passed to the parser (e.g. Cism7)
34             #
35             # root textual decomposition: root part (e.g. Cis)
36             # qual textual decomposition: quality part (e.g. m)
37             # ext textual decomposition: extension part (e.g. 7)
38             # bass textual decomposition: bass part (a root)
39             #
40             # system notation system (common, nashville, user defined)
41             # root_canon canonical root (e.g. Cis => C#)
42             # root_ord root ordinal (e.g. Cis => 1)
43             # root_mod root modifier (e.g. is => # => +1)
44             # qual_canon canonical qualifier (e.g. m => -)
45             # ext_canon canonical extension (e.g. sus => sus4)
46             # bass_canon like root, for bass note
47             # bass_ord like root, for bass note
48             # bass_mod like root, for bass note
49             #
50             # The parsers are one of
51             # ChordPro::Chords::Parser::Common
52             # ChordPro::Chords::Parser::Nashville
53             # ChordPro::Chords::Parser::Roman
54             #
55             # The objects are one of
56             # ChordPro::Chord::Common
57             # ChordPro::Chord::Nashville
58             # ChordPro::Chord::Roman
59              
60             # Creates a parser based on the current (optionally augmented)
61             # context.
62             # Note that the appropriate way is to call
63             # ChordPro::Chords::Parser->get_parser.
64              
65 3     3 0 16 sub new ( $pkg, $init ) {
  3         8  
  3         775  
  2         4  
66              
67 2 50       10 Carp::confess("Missing config?") unless $::config;
68             # Use current config, optionally augmented by $init.
69 2   50     4 my $cfg = { %{$::config//{}}, %{$init//{}} };
  2   50     22  
  2         35  
70              
71             Carp::croak("Missing notes in parser creation")
72 2 50       15 unless $cfg->{notes};
73 2         6 my $system = $cfg->{notes}->{system};
74 2 50       7 Carp::croak("Missing notes system in parser creation")
75             unless $system;
76              
77 2 50       11 if ( $system eq "nashville" ) {
78 0         0 return ChordPro::Chords::Parser::Nashville->new($cfg);
79             }
80 2 50       9 if ( $system eq "roman" ) {
81 0         0 return ChordPro::Chords::Parser::Roman->new($cfg);
82             }
83 2         15 return ChordPro::Chords::Parser::Common->new($cfg);
84             }
85              
86             # The default parser has built-in support for common (dutch) note
87             # names.
88              
89 11505     11505 0 17849 sub default ( $pkg ) {
  11505         17909  
  11505         16333  
90              
91             return $parsers{common} //=
92             ChordPro::Chords::Parser::Common->new
93 11506   33     37967 ( { %{$::config},
  1         2  
94             "notes" =>
95             { "system" => "common",
96             "sharp" => [ "C", [ "C#", "Cis", "C♯" ],
97             "D", [ "D#", "Dis", "D♯" ],
98             "E",
99             "F", [ "F#", "Fis", "F♯" ],
100             "G", [ "G#", "Gis", "G♯" ],
101             "A", [ "A#", "Ais", "A♯" ],
102             "B",
103             ],
104             "flat" => [ "C",
105             [ "Db", "Des", "D♭" ], "D",
106             [ "Eb", "Es", "Ees", "E♭" ], "E",
107             "F",
108             [ "Gb", "Ges", "G♭" ], "G",
109             [ "Ab", "As", "Aes", "A♭" ], "A",
110             [ "Bb", "Bes", "B♭" ], "B",
111             ],
112             },
113             },
114             );
115             }
116              
117             # Cached version of the individual parser's parse_chord.
118 64814     64814 0 97792 sub parse ( $self, $chord ) {
  64814         95574  
  64813         100043  
  64815         86197  
119             #### $self->{chord_cache}->{$chord} //=
120 64815         149423 $self->parse_chord($chord);
121             }
122              
123             # Virtual.
124 2     2 0 3 sub parse_chord ( $self, $chord ) {
  2         5  
  2         8  
  0         0  
125 0         0 Carp::confess("Virtual method 'parse_chord' not defined");
126             }
127              
128             # Fetch a parser for a known system, with fallback.
129             # Default is a parser for the current config.
130 547     547 0 1205 sub get_parser ( $self, $system = undef, $nofallback = undef ) {
  547         1186  
  547         1249  
  549         1050  
  549         989  
131              
132 549   66     2328 $system //= $::config->{notes}->{system};
133 549 100       3049 return $parsers{$system} if $parsers{$system};
134              
135 293 100       2627 if ( $system eq "nashville" ) {
    100          
    50          
    50          
    0          
136 4   33     33 return $parsers{$system} //=
137             ChordPro::Chords::Parser::Nashville->new;
138             }
139             elsif ( $system eq "roman" ) {
140 4   33     33 return $parsers{$system} //=
141             ChordPro::Chords::Parser::Roman->new;
142             }
143             elsif ( $system ne $::config->{notes}->{system} ) {
144 2         21 my $p = ChordPro::Chords::Parser::Common->new
145             ( { notes => $system } );
146 0         0 return $parsers{$system} = $p;
147             }
148             elsif ( $system ) {
149 287         2494 my $p = ChordPro::Chords::Parser::Common->new;
150 287         956 $p->{system} = $system;
151 287         1543 return $parsers{$system} = $p;
152             }
153             elsif ( $nofallback ) {
154 2         14 return;
155             };
156              
157 2         7 Carp::confess("No parser for $system, falling back to default\n");
158 2   0     9 return $parsers{common} //= $self->default;
159             }
160              
161 4     6 0 12 sub have_parser ( $self, $system ) {
  4         6  
  4         13  
  4         6  
162 4         23 exists $parsers{$system};
163             }
164              
165             # The list of instantiated parsers.
166 0     0 0 0 sub parsers ( $self ) {
  0         0  
  0         0  
167 0         0 \%parsers;
168             }
169              
170 209     209 0 500 sub reset_parsers ( $self, @which ) {
  209         581  
  209         545  
  210         433  
171 210 50       1322 @which = keys(%parsers) unless @which;
172 210         3581 delete $parsers{$_} for @which;
173             }
174              
175             # The number of intervals for this note system.
176 372     372 0 647 sub intervals ( $self ) {
  372         621  
  372         539  
177 371         1039 $self->{intervals};
178             }
179              
180 0     0 0 0 sub simplify ( $self ) {
  0         0  
  0         0  
181 0         0 ref($self);
182             }
183              
184             ################ Parsing Common notated chords ################
185              
186             package ChordPro::Chords::Parser::Common;
187              
188             our @ISA = qw( ChordPro::Chords::Parser );
189              
190 81     81   57876 use Storable qw(dclone);
  81         339150  
  81         33232  
191              
192 289     289   783 sub new ( $pkg, $cfg = $::config ) {
  289         627  
  289         681  
  291         588  
193 291         1408 my $self = bless { chord_cache => {} } => $pkg;
194 291         876 bless $self => 'ChordPro::Chords::Parser::Common';
195 291         791 my $notes = $cfg->{notes};
196 291         1582 $self->load_notes($cfg);
197 291         1215 $self->{system} = $notes->{system};
198 291         1075 $self->{target} = 'ChordPro::Chord::Common';
199 291         910 $self->{movable} = $notes->{movable};
200             warn("Chords: Created parser for ", $self->{system},
201             $cfg->{settings}->{chordnames} eq "relaxed"
202             ? ", relaxed" : "",
203 291 0       1551 "\n") if $::options->{verbose} > 1;
    50          
204 291         1414 return $parsers{$self->{system}} = $self;
205             }
206              
207 63795     63795   89908 sub parse_chord ( $self, $chord ) {
  63795         89379  
  63795         96456  
  63795         88512  
208              
209             my $info = { system => $self->{system},
210 63795         207725 parser => $self,
211             name => $chord };
212              
213 63795         107471 my $bass = "";
214 63795 100       325194 if ( $chord =~ m;^(.*)/($self->{n_pat})$; ) {
215 151         470 $chord = $1;
216 151         324 $bass = $2;
217             }
218              
219 63795         107799 my %plus;
220              
221             # Match chord.
222 63793 50 33     736435 if ( $chord eq "" && $bass ne "" ) {
    100 66        
    100 100        
    100 66        
223 0         0 $info->{rootless} = 1;
224             }
225             elsif ( $chord =~ /^$self->{c_pat}$/ ) {
226 39846     79   588577 %plus = %+;
  79         41210  
  79         30809  
  79         197091  
227 39846         158326 $info->{root} = $plus{root};
228             }
229             # Retry with relaxed pattern if requested.
230             elsif ( $self->{c_rpat}
231             && $::config->{settings}->{chordnames} eq "relaxed"
232             && $chord =~ /^$self->{c_rpat}$/ ) {
233 22         407 %plus = %+; # keep it outer
234 22 50       162 return unless $info->{root} = $plus{root};
235             }
236             # Not a chord. Try note name.
237             elsif ( $::config->{settings}->{notenames}
238             && ucfirst($chord) =~ /^$self->{n_pat}$/ ) {
239 6         24 $info->{root} = $chord;
240 6         15 $info->{isnote} = 1;
241             }
242             # Nope.
243             else {
244 23921         440086 return;
245             }
246              
247 39872         89597 bless $info => $self->{target};
248              
249 39872   100     99835 my $q = $plus{qual} // "";
250 39874         73113 $info->{qual} = $q;
251 39872 100 100     143671 $q = "-" if $q eq "m" || $q eq "min";
252 39872 100       77001 $q = "+" if $q eq "aug";
253 39872 100       74994 $q = "0" if $q eq "dim";
254 39872 100       74047 $q = "0" if $q eq "o";
255 39872         73256 $info->{qual_canon} = $q;
256              
257 39872   100     87180 my $x = $plus{ext} // "";
258 39872 100       83035 if ( !$info->{qual} ) {
259 19749 100       39667 if ( $x eq "maj" ) {
260 48         102 $x = "";
261             }
262             }
263 39872         73864 $info->{ext} = $x;
264 39872 100       80271 $x = "sus4" if $x eq "sus";
265 39872         101947 $info->{ext_canon} = $x;
266              
267             my $ordmod = sub {
268 40021     40023   80209 my ( $pfx ) = @_;
269 40021         77178 my $r = $info->{$pfx};
270 40021 100       78724 $r = ucfirst($r) if $info->{isnote};
271 40021 100       105660 if ( defined $self->{ns_tbl}->{$r} ) {
    50          
272 29204         82240 $info->{"${pfx}_ord"} = $self->{ns_tbl}->{$r};
273 29204 100       83607 $info->{"${pfx}_mod"} = defined $self->{nf_tbl}->{$r} ? 0 : 1;
274 29204         94190 $info->{"${pfx}_canon"} = $self->{ns_canon}->[$self->{ns_tbl}->{$r}];
275             }
276             elsif ( defined $self->{nf_tbl}->{$r} ) {
277 10817         30855 $info->{"${pfx}_ord"} = $self->{nf_tbl}->{$r};
278 10817         22917 $info->{"${pfx}_mod"} = -1;
279 10817         35519 $info->{"${pfx}_canon"} = $self->{nf_canon}->[$self->{nf_tbl}->{$r}];
280             }
281             else {
282 0         0 Carp::croak("CANT HAPPEN ($r)");
283 0         0 return;
284             }
285             #### $info->{isflat} = $info->{"${pfx}_mod"} < 0;
286 39872         187794 };
287              
288 39872 50       104525 $ordmod->("root") unless $info->is_rootless;
289              
290 39872 50       135344 cluck("BLESS info for $chord into ", $self->{target}, "\n")
291             unless ref($info) =~ /ChordPro::Chord::/;
292              
293 39872 100       103301 if ( $info->{bass} = $bass ) {
294 149 50       2737 if ( $bass =~ /^$self->{n_pat}$/ ) {
295 149         448 $ordmod->("bass");
296 149 50       382 if ( $info->is_rootless ) {
297 0         0 for ( qw( ord mod canon ) ) {
298 0         0 $info->{"root_$_"} = $info->{"bass_$_"};
299             }
300             }
301             }
302             }
303              
304 39872 50       170354 if ( $::config->{settings}->{'chords-canonical'} ) {
305 0         0 my $t = $info->{name};
306 0         0 $info->{name_canon} = $info->canonical;
307             warn("Parsing chord: \"$chord\" canon \"", $info->canonical, "\"\n" )
308 0 0 0     0 if $info->{name_canon} ne $t and $::config->{debug}->{chords};
309             }
310              
311 39872         584668 return $info;
312             }
313              
314             ################ Chords ################
315              
316             # The following additions are recognized for major chords.
317              
318             my $additions_maj =
319             {
320             map { $_ => $_ }
321             "",
322             "11",
323             "13",
324             "13#11",
325             "13#9",
326             "13b9",
327             "2",
328             "3",
329             "4",
330             "5",
331             "6",
332             "69",
333             "6add9",
334             "7",
335             "711",
336             "7add11",
337             "7#11",
338             "7#5",
339             "7#9",
340             "7#9#11",
341             "7#9#5",
342             "7#9b5",
343             "7alt",
344             "7b13",
345             "7b13sus",
346             "7b5",
347             "7b9",
348             "7b9#11",
349             "7b9#5",
350             "7b9#9",
351             "7b9b13",
352             "7b9b5",
353             "7b9sus",
354             "7-13",
355             "7-13sus",
356             "7-5",
357             "7\\+5",
358             "7-9",
359             "7-9#11",
360             "7-9#5",
361             "7-9#9",
362             "7-9-13",
363             "7-9-5",
364             "7-9sus",
365             "7sus",
366             "7susadd3",
367             "7\\+", # REGEXP!!!
368             "9",
369             "9\\+", # REGEXP!!!
370             "911",
371             "9#11",
372             "9#5",
373             "9b5",
374             "9-5",
375             "9sus",
376             "9add6",
377             ( map { ( "maj$_", "^$_" ) }
378             "",
379             "13",
380             "7",
381             "711",
382             "7#11",
383             "7#5",
384             ( map { "7sus$_" } "", "2", "4" ),
385             "9",
386             "911",
387             "9#11",
388             ),
389             "alt",
390             "h",
391             "h7",
392             "h9",
393             ( map { "add$_" } "2", "4", "9", "11" ),
394             ( map { "sus$_" } "", "2", "4", "9" ),
395             ( map { "6sus$_" } "", "2", "4" ),
396             ( map { "7sus$_" } "", "2", "4" ),
397             ( map { "13sus$_" } "", "2", "4" ),
398             };
399              
400             # The following additions are recognized for minor chords.
401              
402             my $additions_min =
403             {
404             map { $_ => $_ }
405             "",
406             "#5",
407             "11",
408             "13",
409             "6",
410             "69",
411             "7b5",
412             "7-5",
413             ( map { ( "$_", "maj$_", "^$_" ) }
414             "7",
415             "9",
416             ),
417             "9maj7", "9^7",
418             "add9",
419             "b6",
420             "#7",
421             ( map { "sus$_" } "", "4", "9" ),
422             ( map { "7sus$_" } "", "4" ),
423             };
424              
425             # The following additions are recognized for augmented chords.
426              
427             my $additions_aug =
428             {
429             map { $_ => $_ }
430             "",
431             "7",
432             };
433              
434             # The following additions are recognized for diminished chords.
435              
436             my $additions_dim =
437             {
438             map { $_ => $_ }
439             "",
440             "7",
441             };
442              
443             # Build tables and patterns from the "notes" element from the
444             # configuration.
445              
446 289     289   578 sub load_notes ( $self, $init ) {
  289         593  
  289         602  
  291         629  
447 291   50     623 my $cfg = { %{$::config//{}}, %{$init//{}} };
  291   50     3331  
  291         5846  
448 291         1727 my $n = $cfg->{notes};
449 291 50       1320 Carp::confess("No notes?") unless $n->{system};
450 291         849 my ( @ns_canon, %ns_tbl, @nf_canon, %nf_tbl );
451              
452 291         656 my $rix = 0;
453 291         663 foreach my $root ( @{ $n->{sharp} } ) {
  291         1220  
454 3470 100       10620 if ( UNIVERSAL::isa($root, 'ARRAY') ) {
455 1432         3691 $ns_canon[$rix] = $root->[0];
456 1432         8265 $ns_tbl{$_} = $rix foreach @$root;
457             }
458             else {
459 2062         4262 $ns_canon[$rix] = $root;
460 2048         4660 $ns_tbl{$root} = $rix;
461             }
462 3478         5711 $rix++;
463             }
464 303         1098 $rix = 0;
465 303         813 foreach my $root ( @{ $n->{flat} } ) {
  313         1252  
466 3470 100       8470 if ( UNIVERSAL::isa($root, 'ARRAY') ) {
467 1441         3848 $nf_canon[$rix] = $root->[0];
468 1441         9215 $nf_tbl{$_} = $rix foreach @$root;
469             }
470             else {
471 2053         3967 $nf_canon[$rix] = $root;
472 2039         3830 $nf_tbl{$root} = $rix;
473             }
474 3478         5583 $rix++;
475             }
476              
477             # Pattern to match note names.
478 303         1291 my $n_pat = '(?:' ;
479 303         785 my @n;
480 313         2316 foreach ( keys %ns_tbl ) {
481 6305         10493 push( @n, $_ );
482             }
483 291         5128 foreach ( sort keys %nf_tbl ) {
484 6876 100       12730 next if $ns_tbl{$_};
485 5184         8724 push( @n, $_ );
486             }
487              
488 291         3361 $n_pat = '(?:' . join( '|', sort { length($b) <=> length($a) } @n ) . ')';
  46017         67457  
489              
490             # Pattern to match chord names.
491 325         865 my $c_pat;
492             # Accept root, qual, and only known extensions.
493 291         1269 $c_pat = "(?" . $n_pat . ")";
494 615         1543 $c_pat .= "(?:";
495 291         4285 $c_pat .= "(?-|min|m(?!aj))".
496             "(?" . join("|", keys(%$additions_min)) . ")|";
497 291         1927 $c_pat .= "(?\\+|aug)".
498             "(?" . join("|", keys(%$additions_aug)) . ")|";
499 291         1551 $c_pat .= "(?0|o|dim|h)".
500             "(?" . join("|", keys(%$additions_dim)) . ")|";
501 291         10099 $c_pat .= "(?)".
502             "(?" . join("|", keys(%$additions_maj)) . ")";
503 291         1911 $c_pat .= ")";
504 291         115437 $c_pat = qr/$c_pat/;
505 291         25532 $n_pat = qr/$n_pat/;
506              
507             # In relaxed form, we accept anything for extension.
508 291         2474 my $c_rpat = "(?" . $n_pat . ")";
509 291         1908 $c_rpat .= "(?:(?-|min|m(?!aj)|\\+|aug|0|o|dim|)(?.*))";
510 291         30310 $c_rpat = qr/$c_rpat/;
511              
512             # Store in the object.
513 291         2850 $self->{n_pat} = $n_pat;
514 291         958 $self->{c_pat} = $c_pat;
515 291         1087 $self->{c_rpat} = $c_rpat;
516 291         1010 $self->{ns_tbl} = \%ns_tbl;
517 291         840 $self->{nf_tbl} = \%nf_tbl;
518 291         897 $self->{ns_canon} = \@ns_canon;
519 291         1115 $self->{nf_canon} = \@nf_canon;
520 291         3596 $self->{intervals} = @ns_canon;
521             }
522              
523 884     884   1263 sub root_canon ( $self, $root, $sharp = 0, $minor = 0 ) {
  884         1286  
  884         1208  
  882         1416  
  882         1256  
  882         1205  
524 882 100       3499 ( $sharp ? $self->{ns_canon} : $self->{nf_canon} )->[$root];
525             }
526              
527             # Has chord diagrams.
528 181     181   352 sub has_diagrams ( $self ) { !$self->{movable} }
  181         330  
  181         268  
  181         663  
529              
530             # Movable notes system.
531 8     8   13 sub movable ( $self ) { $self->{movable} }
  8         13  
  8         12  
  8         32  
532              
533             ################ Parsing Nashville notated chords ################
534              
535             package ChordPro::Chords::Parser::Nashville;
536              
537             our @ISA = qw(ChordPro::Chords::Parser::Common);
538              
539 81     81   1319 use Storable qw(dclone);
  81         631  
  81         31524  
540              
541             sub new {
542 2     2   22 my ( $pkg, $init ) = @_;
543 2         11 my $self = bless { chord_cache => {} } => $pkg;
544 2         23 $self->{system} = "nashville";
545 2         10 $self->{target} = 'ChordPro::Chord::Nashville';
546             warn("Chords: Created parser for ", $self->{system}, "\n")
547 2 50 33     12 if $::options->{verbose} && $::options->{verbose} > 1;
548 2         20 return $parsers{$self->{system}} = $self;
549             }
550              
551             my $n_pat = qr/(?[b#]?)(?[1-7])/;
552              
553             my %nmap = ( 1 => 0, 2 => 2, 3 => 4, 4 => 5, 5 => 7, 6 => 9, 7 => 11 );
554             my @nmap = ( 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6, 7, 1 );
555              
556             sub parse_chord {
557 408 50   408   1676 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
558 408         848 my ( $self, $chord ) = @_;
559              
560 81     81   632 $chord =~ tr/\x{266d}\x{266f}\x{0394}\x{f8}\x{b0}/b#^h0/;
  81         238  
  81         2063  
  408         1353  
561              
562 408         778 my $bass = "";
563 408 50       1389 if ( $chord =~ m;^(.*)/(.*); ) {
564 0         0 $chord = $1;
565 0         0 $bass = $2;
566             }
567              
568 408 50       4278 return unless $chord =~ /^$n_pat(?-|\+|0|o|aug|m(?!aj)|dim)?(?.*)$/;
569              
570             my $info = { system => "nashville",
571             parser => $self,
572             name => $_[1],
573             root => $+{root},
574 408         4731 };
575 408         1392 bless $info => $self->{target};
576              
577 408   100     1928 my $q = $+{qual} // "";
578 408         1093 $info->{qual} = $q;
579 408 50       963 $q = "-" if $q eq "m";
580 408 50       769 $q = "+" if $q eq "aug";
581 408 50       715 $q = "0" if $q eq "dim";
582 408 50       795 $q = "0" if $q eq "o";
583 408         743 $info->{qual_canon} = $q;
584              
585 408   50     1500 my $x = $+{ext} // "";
586 408         1072 $info->{ext} = $x;
587 408 50       790 $x = "sus4" if $x eq "sus";
588 408         1091 $info->{ext_canon} = $x;
589              
590             my $ordmod = sub {
591 408     408   847 my ( $pfx ) = @_;
592 408         918 my $r = 0 + $info->{$pfx};
593 408         1459 $info->{"${pfx}_ord"} = $nmap{$r};
594 408 100       2632 if ( $+{shift} eq "#" ) {
    100          
595 120         349 $info->{"${pfx}_mod"} = 1;
596 120         251 $info->{"${pfx}_ord"}++;
597             $info->{"${pfx}_ord"} = 0
598 120 50       309 if $info->{"${pfx}_ord"} >= 12;
599             }
600             elsif ( $+{shift} eq "b" ) {
601 120         317 $info->{"${pfx}_mod"} = -1;
602 120         254 $info->{"${pfx}_ord"}--;
603             $info->{"${pfx}_ord"} += 12
604 120 50       330 if $info->{"${pfx}_ord"} < 0;
605             }
606             else {
607 168         458 $info->{"${pfx}_mod"} = 0;
608             }
609 408         1400 $info->{"${pfx}_canon"} = $r;
610 408         2160 };
611              
612 408         1077 $ordmod->("root");
613              
614 408         918 $info->{bass} = $bass;
615 408 50       4094 return $info unless $bass;
616 0 0       0 return unless $bass =~ /^$n_pat$/;
617 0         0 $ordmod->("bass");
618              
619 0         0 return $info;
620             }
621              
622 0     0   0 sub load_notes { Carp::confess("OOPS") }
623              
624             sub root_canon {
625 12 50   12   41 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
626 12         30 my ( $self, $root, $sharp ) = @_;
627 81     81   489864 no warnings 'qw';
  81         296  
  81         18292  
628 12 50       78 $sharp
629             ? qw( 1 #1 2 #2 3 4 #4 5 #5 6 #6 7 )[$root]
630             : qw( 1 b2 2 b3 3 4 b5 5 b6 6 b7 7 )[$root]
631             }
632              
633             # Has chord diagrams.
634             sub has_diagrams {
635 2 50   2   11 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
636 2         9 0;
637             }
638              
639             # Movable notes system.
640             sub movable {
641 2 50   2   11 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
642 2         10 1;
643             }
644              
645             ################ Parsing Roman notated chords ################
646              
647             package ChordPro::Chords::Parser::Roman;
648              
649 81     81   6642 use ChordPro;
  81         255  
  81         27150  
650              
651             our @ISA = qw(ChordPro::Chords::Parser::Common);
652              
653             sub new {
654 2     2   9 my ( $pkg, $init ) = @_;
655 2         10 my $self = bless { chord_cache => {} } => $pkg;
656 2         20 $self->{system} = "roman";
657 2         7 $self->{target} = 'ChordPro::Chord::Roman';
658             warn("Chords: Created parser for ", $self->{system}, "\n")
659 2 50 33     11 if $::options->{verbose} && $::options->{verbose} > 1;
660 2         15 return $parsers{$self->{system}} = $self;
661             }
662              
663             my $r_pat = qr/(?[b#]?)(?(?i)iii|ii|iv|i|viii|vii|vi|v)/;
664              
665             my %rmap = ( I => 0, II => 2, III => 4, IV => 5, V => 7, VI => 9, VII => 11 );
666              
667             sub parse_chord {
668 612 50   612   2439 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
669 612         1414 my ( $self, $chord ) = @_;
670              
671 612         1935 $chord =~ tr/\x{266d}\x{266f}\x{0394}\x{f8}\x{b0}/b#^h0/;
672              
673 612         1102 my $bass = "";
674 612 50       1990 if ( $chord =~ m;^(.*)/(.*); ) {
675 0         0 $chord = $1;
676 0         0 $bass = $2;
677             }
678              
679 612 50       6731 return unless $chord =~ /^$r_pat(?\+|0|o|aug|dim|h)?(?.*)$/;
680 612         5548 my $r = $+{shift}.$+{root};
681              
682 612         3426 my $info = { system => "roman",
683             parser => $self,
684             name => $_[1],
685             root => $r };
686 612         1503 bless $info => $self->{target};
687              
688 612   100     2867 my $q = $+{qual} // "";
689 612         1691 $info->{qual} = $q;
690 612 100       1933 $q = "-" if $r eq lc($r);
691 612 50       1304 $q = "+" if $q eq "aug";
692 612 50       1155 $q = "0" if $q eq "dim";
693 612 50       1180 $q = "0" if $q eq "o";
694 612         1155 $info->{qual_canon} = $q;
695              
696 612   50     2405 my $x = $+{ext} // "";
697 612         1586 $info->{ext} = $x;
698 612 50       1209 $x = "sus4" if $x eq "sus";
699 612 50       1150 $x = "^7" if $x eq "7+";
700 612         1784 $info->{ext_canon} = $x;
701              
702             my $ordmod = sub {
703 612     612   1262 my ( $pfx ) = @_;
704 612         1146 my $r = $info->{$pfx};
705 612         2591 $info->{"${pfx}_ord"} = $rmap{uc $r};
706 612 100       3775 if ( $+{shift} eq "#" ) {
    100          
707 180         449 $info->{"${pfx}_mod"} = 1;
708 180         426 $info->{"${pfx}_ord"}++;
709             $info->{"${pfx}_ord"} = 0
710 180 50       480 if $info->{"${pfx}_ord"} >= 12;
711             }
712             elsif ( $+{shift} eq "b" ) {
713 180         486 $info->{"${pfx}_mod"} = -1;
714 180         431 $info->{"${pfx}_ord"}--;
715             $info->{"${pfx}_ord"} += 12
716 180 50       656 if $info->{"${pfx}_ord"} < 0;
717             }
718             else {
719 252         674 $info->{"${pfx}_mod"} = 0;
720             }
721 612         2166 $info->{"${pfx}_canon"} = $r;
722 612         3249 };
723              
724 612         1766 $ordmod->("root");
725              
726 612         1508 $info->{bass} = uc $bass;
727 612 50       6214 return $info unless $bass;
728 0 0       0 return unless $bass =~ /^$r_pat$/;
729 0         0 $ordmod->("bass");
730              
731 0         0 return $info;
732             }
733              
734 0     0   0 sub load_notes { Carp::confess("OOPS") }
735              
736             sub root_canon {
737 12 50   12   40 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
738 12         32 my ( $self, $root, $sharp, $minor ) = @_;
739 12 50       25 return lc( $self->root_canon( $root, $sharp ) ) if $minor;
740 81     81   74865 no warnings 'qw';
  81         226  
  81         19918  
741 12 50       52 $sharp
742             ? qw( I #I II #II III IV #IV V #V VI #VI VII )[$root]
743             : qw( I bII II bIII III IV bV V bVI VI bVII VII )[$root]
744             }
745              
746             # Has chord diagrams.
747             sub has_diagrams {
748 2 50   2   15 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
749 2         12 0;
750             }
751              
752             # Movable notes system.
753             sub movable {
754 2 50   2   12 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
755 2         10 1;
756             }
757              
758             ################ Chord objects: Common ################
759              
760             package ChordPro::Chord::Base;
761              
762 81     81   1587 use Storable qw(dclone);
  81         215  
  81         113610  
763              
764             sub new {
765 13509     13509   32080 my ( $pkg, $data ) = @_;
766 13509   66     39514 $pkg = ref($pkg) || $pkg;
767 13509         88452 bless { %$data } => $pkg;
768             }
769              
770             sub clone {
771 377 50   377   1438 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
772 377         785 my ( $self ) = shift;
773 377         41719 dclone($self);
774             }
775              
776 40807     40807   115621 sub is_note { $_[0]->{isnote} };
777 0     0   0 sub is_flat { $_[0]->{isflat} };
778 942     942   2692 sub is_keyboard { $_[0]->{iskeyboard} };
779              
780             sub is_nc {
781 942     942   1852 my ( $self ) = @_;
782             # Keyboard...
783 942 50 0     2111 return 1 if $self->is_keyboard && !@{ $self->kbkeys // [1] };
  0   33     0  
784             # Strings...
785 942 100 100     1522 return unless @{ $self->frets // [] };
  942         2093  
786 780         1328 for ( @{ $self->frets } ) {
  780         1378  
787 1621 100       5354 return unless $_ < 0;
788             }
789 14         56 return 1; # all -1 => N.C.
790             }
791              
792             # Can be transposed/transcoded.
793             sub is_xpxc {
794 1339 100 66 1339   6849 defined($_[0]->{root}) || defined($_[0]->{bass}) || $_[0]->is_nc;
795             }
796              
797             sub has_diagram {
798 814     814   1546 my ( $self ) = @_;
799             ( $::config->{instrument}->{type} eq "keyboard" )
800 0   0     0 ? @{ $self->kbkeys // []}
801 814 50 50     1859 : @{ $self->frets // []};
  814         1504  
802             }
803              
804             # For convenience.
805 170247     170247   596809 sub is_chord { defined $_[0]->{root_ord} }
806 79090     79093   257467 sub is_rootless { $_[0]->{rootless} }
807 590     590   2020 sub is_annotation { 0 }
808 3     0   17 sub is_movable { $_[0]->{movable} }
809              
810             # Common accessors.
811             sub name {
812 6442     6442   12233 my ( $self, $np ) = @_;
813             Carp::confess("Double parens")
814 6442 50 33     14893 if $self->{parens} && $self->{name} =~ /^\(.*\)$/;
815 6442 50 33     45680 return $self->{name} if $np || !$self->{parens};
816 0         0 "(" . $self->{name} . ")";
817             }
818              
819 0     0   0 sub canon { $_[0]->{name_canon} }
820 4     4   27 sub root { $_[0]->{root} }
821 4     4   27 sub qual { $_[0]->{qual} }
822 4     4   32 sub ext { $_[0]->{ext} }
823 4     4   26 sub bass { $_[0]->{bass} }
824 3     3   2583 sub base { $_[0]->{base} }
825 2537     2537   9615 sub frets { $_[0]->{frets} }
826 1     1   11 sub fingers { $_[0]->{fingers} }
827 0     0   0 sub display { $_[0]->{display} }
828 0     0   0 sub format { $_[0]->{format} }
829 7     7   55 sub diagram { $_[0]->{diagram} }
830 71     71   240 sub parser { $_[0]->{parser} }
831              
832             sub strings {
833 0     0   0 $_[0]->{parser}->{intervals};
834             }
835              
836             sub kbkeys {
837 1 50 33 1   8 return $_[0]->{keys} if $_[0]->{keys} && @{$_[0]->{keys}};
  1         13  
838 0         0 $_[0]->{keys} = ChordPro::Chords::get_keys($_[0]);
839             }
840              
841 796     796   1212 sub chord_display ( $self, $default ) {
  796         1123  
  796         1327  
  796         1072  
842              
843 81     81   44378 use String::Interpolate::Named;
  81         115717  
  81         37012  
844              
845 796         1832 my $res = $self->name;
846 796         1722 my $args = {};
847 796   33     3822 $self->flat_copy( $args, $self->{display} // $self );
848              
849 796         2329 for my $fmt ( $default,
850             $self->{format},
851             $self->{chordformat} ) {
852 2388 100       546958 next unless $fmt;
853 810 100       2021 $args->{root} = lc($args->{root}) if $self->is_note;
854 810         1913 $args->{formatted} = $res;
855 810         3142 $res = interpolate( { args => $args }, $fmt );
856             }
857              
858             # Substitute musical symbols if wanted.
859 796 100       5922 return $::config->{settings}->{truesf} ? $self->fix_musicsyms($res) : $res;
860             }
861              
862 883     883   1275 sub flat_copy ( $self, $ret, $o, $pfx = "" ) {
  883         1296  
  883         1229  
  883         1227  
  883         1428  
  883         1217  
863 883         3322 while ( my ( $k, $v ) = each %$o ) {
864 17745 100 100     64096 if ( $k eq "orig" || $k eq "xc" || $k eq "xp" ) {
      100        
865 87         407 $self->flat_copy( $ret, $v, "$k.$pfx");
866 87         256 $ret->{"$k.${pfx}formatted"} = $v->chord_display;
867             }
868             else {
869 17658         61855 $ret->{"$pfx$k"} = $v;
870             }
871             }
872 883         1750 $ret;
873             }
874              
875 22     22   39 sub fix_musicsyms ( $self, $str ) {
  22         31  
  22         40  
  22         28  
876              
877 81     81   1291 use ChordPro::Utils qw( splitmarkup );
  81         1723  
  81         188571  
878              
879 22 50       47 return $str unless $::config->{settings}->{truesf};
880              
881 22         64 my @c = splitmarkup($str);
882 22         49 my $res = '';
883 22 100       77 push( @c, '' ) if @c % 2;
884 22         33 my $did = 0; # TODO: not for roman
885 22         46 while ( @c ) {
886 37         71 $_ = shift(@c);
887 37 100       84 if ( $did ) {
888 15         47 s/b/♭/g;
889             }
890             else {
891 22         82 s/(?<=[[:alnum:]])b/♭/g;
892 22         38 $did++;
893             }
894 37         66 s/#/♯/g;
895 37         109 $res .= $_ . shift(@c);
896             }
897 22         145 $res;
898             }
899              
900 0     0   0 sub simplify ( $self ) {
  0         0  
  0         0  
901 0         0 my $c = {};
902 0         0 for ( keys %$self ) {
903 0 0       0 next unless defined $self->{$_};
904 0 0       0 next if defined $c->{$_};
905 0 0 0     0 if ( UNIVERSAL::can( $self->{$_}, "simplify" ) ) {
    0          
906 0         0 $c->{$_} = $self->{$_}->simplify;
907             }
908 0         0 elsif ( ref($self->{$_}) eq 'ARRAY' && @{$self->{$_}} ) {
909 0         0 $c->{$_} = "[ " . join(" ", @{$self->{$_}}) . " ]";
  0         0  
910             }
911             else {
912 0         0 $c->{$_} = $self->{$_};
913             }
914             }
915 0         0 $c;
916             }
917              
918 0     0   0 sub dump ( $self ) {
  0         0  
  0         0  
919 0         0 ::dump($self->simplify);
920             }
921              
922             package ChordPro::Chord::Common;
923              
924             our @ISA = qw( ChordPro::Chord::Base );
925              
926             # Show reconstructs the chord from its constituents.
927             # Result is canonical.
928             sub show {
929 0     0   0 Carp::croak("call canonical instead of show");
930             }
931              
932 515     515   722 sub canonical ( $self ) {
  515         752  
  515         678  
933 515         726 my $res;
934              
935             $res =
936             $self->is_rootless
937             ? ""
938             : $self->is_chord
939             ? $self->{parser}->root_canon( $self->{root_ord},
940             $self->{root_mod} >= 0,
941             $self->{qual} eq '-',
942             # !$self->is_flat ???
943             ) . $self->{qual} . $self->{ext}
944 515 50       1128 : $self->{name};
    50          
945              
946 515 100       1299 if ( $self->is_note ) {
947 4         18 return lcfirst($res);
948             }
949 511 100 66     1280 if ( $self->{bass} && $self->{bass} ne "" ) {
950             $res .= "/" .
951 3 50       19 ($self->{system} eq "roman" ? lc($self->{bass}) : $self->{bass});
952             }
953 511         1456 return $res;
954             }
955              
956             # Returns a representation indepent of notation system.
957 38554     38554   57706 sub agnostic ( $self ) {
  38554         57893  
  38554         53103  
958 38554 100 66     70612 return if $self->is_rootless || $self->is_note;
959             join( " ", "",
960             $self->{root_ord},
961             $self->{root_mod},
962             $self->{qual_canon},
963             $self->{ext_canon},
964 38548   100     218833 $self->{bass_ord} // () );
965             }
966              
967 382     382   627 sub transpose ( $self, $xpose, $dir = 0 ) {
  382         566  
  382         590  
  382         578  
  382         551  
968 382 100       857 return $self unless $xpose;
969 370 100       805 return $self unless $self->is_chord;
970 368   33     879 $dir //= $xpose <=> 0;
971              
972 368         896 my $info = $self->clone;
973 368         281981 my $p = $self->{parser};
974              
975 368 50       1214 unless ( $self->{rootless} ) {
976 368         1423 $info->{root_ord} = ( $self->{root_ord} + $xpose ) % $p->intervals;
977             $info->{root_canon} = $info->{root} =
978             $p->root_canon( $info->{root_ord},
979             $dir > 0,
980 368         1356 $info->{qual_canon} eq "-" );
981             }
982 368 50 66     1257 if ( $self->{bass} && $self->{bass} ne "" && $self->{bass} !~ /^\d+$/ ) {
      66        
983 3         14 $info->{bass_ord} = ( $self->{bass_ord} + $xpose ) % $p->intervals;
984             $info->{bass_canon} = $info->{bass} =
985 3         13 $p->root_canon( $info->{bass_ord}, $xpose > 0 );
986 3         8 $info->{bass_mod} = $dir;
987             }
988 368         628 $info->{root_mod} = $dir;
989 368         1010 $info->{name} = $info->{name_canon} = $info->canonical;
990              
991 368         1815 delete $info->{$_} for qw( copy base frets fingers keys display );
992              
993 368         1106 return $info;
994             }
995              
996 143     143   234 sub transcode ( $self, $xcode, $key_ord = 0 ) {
  143         226  
  143         243  
  143         217  
  143         210  
997 143 100       441 return $self unless $xcode;
998 20 50       51 return $self unless $self->is_chord;
999 20 50       59 return $self if $self->{system} eq $xcode;
1000 20         2029 my $info = $self->dclone;
1001             #warn("_>_XCODE = $xcode, _SELF = $self->{system}, CHORD = $info->{name}");
1002 20         15490 $info->{system} = $xcode;
1003 20         116 my $p = $self->{parser}->get_parser($xcode);
1004 20 50       66 die("OOPS ", $p->{system}, " $xcode") unless $p->{system} eq $xcode;
1005 20         425 $info->{parser} = $p;
1006 20 100 100     95 $info->{root_ord} -= $key_ord if $key_ord && $p->movable;
1007             # $info->{$_} = $p->{$_} for qw( ns_tbl nf_tbl ns_canon nf_canon );
1008             $info->{root_canon} = $info->{root} =
1009             $p->root_canon( $info->{root_ord},
1010             $info->{root_mod} >= 0,
1011 20         153 $info->{qual_canon} eq "-" );
1012 20 50 66     78 if ( $p->{system} eq "roman" && $info->{qual_canon} eq "-" ) {
1013             # Minor quality is in the root name.
1014 0         0 $info->{qual_canon} = $info->{qual} = "";
1015             }
1016 20 50 33     57 if ( $self->{bass} && $self->{bass} ne "" ) {
1017 0 0 0     0 $info->{bass_ord} -= $key_ord if $key_ord && $p->movable;
1018             $info->{bass_canon} = $info->{bass} =
1019 0         0 $p->root_canon( $info->{bass_ord}, $info->{bass_mod} >= 0 );
1020             }
1021 20         59 $info->{name} = $info->{name_canon} = $info->canonical;
1022 20         40 $info->{system} = $p->{system};
1023 20         71 bless $info => $p->{target};
1024             # ::dump($info);
1025             #warn("_<_XCODE = $xcode, CHORD = ", $info->canonical);
1026 20         64 return $info;
1027             }
1028              
1029 788     788   1268 sub chord_display ( $self ) {
  788         1229  
  788         1103  
1030              
1031             $self->SUPER::chord_display
1032             ( $::config->{"chord-formats"}->{common}
1033 788   33     3304 // $::config->{settings}->{"chord-format"}
      0        
1034             // "%{name}" );
1035             }
1036              
1037             ################ Chord objects: Nashville ################
1038              
1039             package ChordPro::Chord::Nashville;
1040              
1041             our @ISA = 'ChordPro::Chord::Base';
1042              
1043 0     0   0 sub transpose ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1044              
1045             sub show {
1046 0     0   0 Carp::croak("call canonical instead of show");
1047             }
1048              
1049 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1050 0         0 my $res = $self->{root_canon} . $self->{qual} . $self->{ext};
1051 0 0 0     0 if ( $self->{bass} && $self->{bass} ne "" ) {
1052 0         0 $res .= "/" . lc($self->{bass});
1053             }
1054 0         0 return $res;
1055             }
1056              
1057 4     4   5 sub chord_display ( $self ) {
  4         11  
  4         4  
1058              
1059             $self->SUPER::chord_display
1060             ( $::config->{"chord-formats"}->{nashville}
1061 4   50     31 // "%{name}" );
1062             }
1063              
1064             ################ Chord objects: Roman ################
1065              
1066             package ChordPro::Chord::Roman;
1067              
1068             our @ISA = 'ChordPro::Chord::Base';
1069              
1070 0     0   0 sub transpose ( $self, $dummy1, $dummy2 ) { $self }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1071              
1072             sub show {
1073 0     0   0 Carp::croak("call canonical instead of show");
1074             }
1075              
1076 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1077 0         0 my $res = $self->{root_canon} . $self->{qual} . $self->{ext};
1078 0 0 0     0 if ( $self->{bass} && $self->{bass} ne "" ) {
1079 0         0 $res .= "/" . lc($self->{bass});
1080             }
1081 0         0 return $res;
1082             }
1083              
1084 4     4   9 sub chord_display ( $self ) {
  4         8  
  4         8  
1085              
1086             $self->SUPER::chord_display
1087             ( $::config->{"chord-formats"}->{roman}
1088 4   50     24 // "%{name}" );
1089             }
1090              
1091             ################ Chord objects: Annotations ################
1092              
1093             package ChordPro::Chord::Annotation;
1094              
1095 81     81   870 use String::Interpolate::Named;
  81         243  
  81         61294  
1096              
1097             our @ISA = 'ChordPro::Chord::Base';
1098              
1099 0     0   0 sub transpose ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1100 0     0   0 sub transcode ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1101              
1102 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1103 0         0 my $res = $self->{text};
1104 0         0 return $res;
1105             }
1106              
1107 2     2   5 sub chord_display ( $self ) {
  2         6  
  2         4  
1108 2         14 return interpolate( { args => $self }, $self->{text} );
1109             }
1110              
1111             # For convenience.
1112 0     0   0 sub is_chord ( $self ) { 0 };
  0         0  
  0         0  
  0         0  
1113 3     3   10 sub is_annotation ( $self ) { 1 };
  3         7  
  3         6  
  3         22  
1114              
1115             ################ Testing ################
1116              
1117             package main;
1118              
1119             unless ( caller ) {
1120             select(STDERR);
1121             binmode(STDERR, ':utf8');
1122             $::config = { settings => { chordnames => "strict" } };
1123             $::options = { verbose => 2 };
1124             foreach ( @ARGV ) {
1125             if ( $_ eq '-' ) {
1126             $::config = { settings => { chordnames => "relaxed" } };
1127             ChordPro::Chords::Parser->reset_parsers("common");
1128             next;
1129             }
1130             my $p0 = ChordPro::Chords::Parser->default;
1131             my $p1 = ChordPro::Chords::Parser->get_parser("common", 1);
1132             die unless $p0 eq $p1;
1133             my $p2 = ChordPro::Chords::Parser->get_parser("nashville", 1);
1134             my $p3 = ChordPro::Chords::Parser->get_parser("roman", 1);
1135             my $info = $p1->parse($_);
1136             $info = $p2->parse($_) if !$info && $p2;
1137             $info = $p3->parse($_) if !$info && $p3;
1138             print( "$_ => OOPS\n" ), next unless $info;
1139             print( "$_ ($info->{system}) =>" );
1140             print( " ", $info->transcode($_)->canonical, " ($_)" )
1141             for qw( common nashville roman );
1142             print( " '", $info->agnostic, "' (agnostic)\n" );
1143             print( "$_ =>" );
1144             print( " ", $info->transpose($_)->canonical, " ($_)" ) for -2..2;
1145             print( "\n" );
1146             # my $clone = $info->clone;
1147             # delete($clone->{parser});
1148             # print( ::dump($clone), "\n" );
1149             }
1150             }
1151              
1152             1;