File Coverage

blib/lib/Data/iRealPro/Output/MMA.pm
Criterion Covered Total %
statement 307 374 82.0
branch 120 182 65.9
condition 39 67 58.2
subroutine 25 25 100.0
pod 0 9 0.0
total 491 657 74.7


line stmt bran cond sub pod time code
1             #! perl
2              
3             # Data::iRealPro::Output::MMA -- produce MMA song
4              
5             # Author : Johan Vromans
6             # Created On : Mon Jan 7 08:20:16 2019
7             # Last Modified By: Johan Vromans
8             # Last Modified On: Sun Jan 20 22:22:42 2019
9             # Update Count : 481
10             # Status : Unknown, Use with caution!
11              
12             ################ Common stuff ################
13              
14 2     2   2842 use strict;
  2         5  
  2         60  
15 2     2   11 use warnings;
  2         3  
  2         50  
16 2     2   9 use Carp;
  2         5  
  2         88  
17 2     2   588 use utf8;
  2         15  
  2         10  
18              
19             package Data::iRealPro::Output::MMA;
20              
21             my $VERSION = "0.01";
22              
23 2     2   133 use parent qw( Data::iRealPro::Output::Base );
  2         4  
  2         16  
24              
25 2     2   88 use Data::iRealPro::URI;
  2         5  
  2         40  
26 2     2   1188 use Data::Dumper;
  2         13030  
  2         123  
27 2     2   15 use Encode qw(encode_utf8);
  2         5  
  2         8980  
28              
29             my $regtest = $ENV{IRP_REGTEST};
30              
31             my %stype = ( i => "Intro",
32             v => "Verse",
33             coda => "Coda",
34             );
35              
36             sub options {
37 2     2 0 5 my $self = shift;
38 2         5 [ @{ $self->SUPER::options }, qw( split dir ) ];
  2         11  
39             }
40              
41             sub process {
42 2     2 0 2097 my ( $self, $u, $options ) = @_;
43              
44 2         8 for ( qw( trace debug verbose ) ) {
45 6 50       19 $self->{$_} = $options->{$_} if exists $options->{$_};
46             }
47              
48 2 50       8 unless ( $self->{split} ) {
49              
50 2   50     19 $self->{output} ||= $options->{output} || "__new__.mma";
      33        
51              
52 2 50       8 if ( $u->{transpose} ) {
53 0         0 foreach my $song ( @{ $u->{playlist}->{songs} } ) {
  0         0  
54             # Do not change key to actual.
55 0         0 local $song->{_transpose} = 0;
56              
57 0   0     0 $song->{key} = $song->xpose($song->{key} // "C");
58 0 0       0 if ( $song->{actual_key} ne '' ) {
59             $song->{actual_key} =
60 0         0 ( $song->{actual_key} + $song->{transpose} ) % 12;
61             }
62 0         0 $song->tokenize;
63 0 0       0 $song->{data} = $song->{dataxp} if $song->{dataxp};
64             }
65             }
66              
67 2 50       10 if ( ref( $self->{output} ) ) {
68 2         10 ${ $self->{output} } = $self->to_mma($u);
  2         7  
69             }
70             else {
71             open( my $fd, ">:utf8", $self->{output} )
72 0 0       0 or croak( "Cannot create ", $self->{output}, " [$!]\n" );
73 0         0 print $fd $self->to_mma($u);
74 0         0 close($fd);
75             }
76 2         7 return;
77             }
78              
79 0   0     0 my $outdir = $self->{dir} || "";
80 0 0 0     0 $outdir .= "/" if $outdir && $outdir !~ m;/$;;
81 0 0       0 make_path( $outdir, {} ) unless -d $outdir;
82              
83 0         0 foreach my $song ( @{ $u->{playlist}->{songs} } ) {
  0         0  
84             # Do not change key to actual.
85 0         0 local $song->{_transpose} = 0;
86              
87 0 0       0 if ( $song->{transpose} ) {
88 0   0     0 $song->{key} = $song->xpose($song->{key} // "C");
89 0 0       0 if ( $song->{actual_key} ne '' ) {
90             $song->{actual_key} =
91 0         0 ( $song->{actual_key} + $song->{transpose} ) % 12;
92             }
93 0         0 $song->tokenize;
94 0 0       0 $song->{data} = $song->{dataxp} if $song->{dataxp};
95             }
96              
97             # Make a playlist with just this song.
98 0         0 my $pls = Data::iRealPro::Playlist->new( song => $song );
99              
100             # Make an URI for this playlist.
101 0         0 my $uri = Data::iRealPro::URI->new( playlist => $pls );
102              
103             # Write it out.
104 0         0 my $title = $song->{title};
105             # Mask dangerous characters.
106 0         0 $title =~ s/[:?\\\/*"<>|]/@/g;
107 0         0 my $file = $outdir.$title.".mma";
108 0         0 my $out = encode_utf8($file);
109 0 0       0 open( my $fd, '>:utf8', $out )
110             or die( "$out: $!\n" );
111 0         0 print $fd $self->to_mma($uri);
112 0         0 close($fd);
113             warn( "Wrote $out\n" )
114 0 0       0 if $self->{verbose};
115             }
116             }
117              
118             sub to_mma {
119 2     2 0 5 my ( $self, $u ) = @_;
120              
121 2         5 my $pl = $u->{playlist};
122 2         5 my $title;
123 2 50       6 if ( $pl->{name} ) {
124 0         0 $title = $pl->{name};
125             }
126             else {
127 2         6 $title = $pl->{songs}->[0]->{title};
128             }
129 2 50       19 my $vv = $regtest ? "" : " version $Data::iRealPro::VERSION";
130              
131 2         7 my $song = $u->{playlist}->{songs}->[0];
132 2   50     8 my $tempo = $song->{actual_tempo} || 120;
133              
134 2         6 my $time_d;
135             my $time_n;
136 2         5 my $time = "4/4";
137 2         4 foreach ( @{ $song->tokens } ) {
  2         9  
138 27 100       57 next unless /time (\d\/\d)/;
139 1         3 $time = $1;
140 1         3 last;
141             }
142 2         15 ( $time_d, $time_n ) = $time =~ m;^(\d+)/(\d+)$;;
143              
144 2         11 my @t = split( ' ', $song->{composer} );
145 2 50       14 @t[0,1] = @t[1,0] if @t == 2;
146 2         8 my $me = "Data::iRealPro $Data::iRealPro::VERSION";
147 2         5 my @mma = ( <
148 2         10 // Title: @{[ $song->{title} ]}
149 2         14 // Style: @{[ $song->{style} ]}
150             // Composer: @t
151             // Converted from iReal by $me
152              
153 2         10 MIDIText @{[ $song->{title} ]}
154             MIDIText MMA input generated by $me
155              
156 2         24 KeySig @{[ key2mma($song->{key}) ]}
157             Time $time_d
158             TimeSig $time
159             Tempo $tempo
160              
161             EOD
162              
163 2         7 my $mma = "";
164 2         9 my $s = $self->interpret($song);
165 2         5 my $mcnt = 0;
166 2         3 my $in_intro = 1;
167 2   100     9 my $grepeat = $song->{actual_repeats} || 3;
168              
169 2         14 my $has_jumps;
170 2         5 foreach ( @{ $s->{content} } ) {
  2         6  
171 4 100       19 next unless $_->{hasjumps};
172 1         3 $has_jumps++;
173 1         2 last;
174             }
175              
176 2         11 push( @mma, "Set SongForms $grepeat\n" );
177 2 100       7 push( @mma, "\nLabel Capo\n" ) if $has_jumps;
178              
179 2         5 foreach my $section ( @{ $s->{content} } ) {
  2         5  
180             #print "section ", $section->{type}, "\n";
181 5 50       8 next unless @{ $section->{content} };
  5         15  
182 5         11 my $type = $section->{type};
183 5 50       22 next if $type eq "hspace";
184              
185 5 100       14 if ( $mma ) {
186 3         6 push( @mma, $mma );
187             }
188 5         11 $mma = "";
189              
190 5 100 100     29 if ( $in_intro && $type ne 'i' ) {
    50 66        
191 2         8 $mma .= "\nRepeat // song form\n";
192 2         4 $in_intro = 0;
193             }
194              
195             # iRealPro has two styles of song form repeats. Normally,
196             # the coda part comes after the song form. However, when
197             # jumps are used, the coda part becomes part of the song form.
198             elsif ( $type eq "coda" && !$has_jumps ) {
199 0         0 $mma .= "\n" . endrepeat('$SongForms');
200 0         0 $grepeat = 0;
201             }
202              
203 5   66     35 $mma .= "\n// Section: " . ($stype{$type}//$type) . "\n";
204 5 100 100     20 $mma .= "Label Coda\n" if $has_jumps && $type eq "coda";
205 5         22 $mma .= "Groove " . style2mma( $song->{style} ) . "\n";
206 5         10 foreach my $item ( @{ $section->{content} } ) {
  5         14  
207             #print "item ", $item->{type}, "\n";
208              
209 52 100       138 if ( $item->{type} eq "measure" ) {
    100          
    50          
210 39         45 $mcnt++;
211 39         96 my $res = sprintf("%3d", $mcnt);
212 39         56 my @chords;
213 39         52 foreach my $i ( @{ $item->{content} } ) {
  39         68  
214             # printf( "%3d %s\n", $mcnt, "@$i" );
215 81         124 foreach my $j ( @$i ) {
216 84 100       295 if ( $j =~ /^chord\s+(\S+)/ ) {
    100          
    100          
    100          
217 40         79 push( @chords, chord2mma($1) );
218             }
219             elsif ( $j =~ /^advance / ) {
220 41         81 push( @chords, "/" );
221             }
222             elsif ( $j =~ /^time\s+(\S+)/ ) {
223 1 50       8 warn("Time signature change: $time -> $1\n")
224             unless $1 eq $time;
225             }
226             elsif ( $j eq "segno" ) {
227 1         11 $mma .= "Label Segno\n";
228 1         5 next;
229             }
230             else {
231 1         4 $res .= " /* $j? */";
232             }
233             }
234             }
235 39 100       83 if ( @chords < $time_d ) {
236 38 50       85 if ( @chords == 1 ) {
    100          
237             # Okay, MMA will fill.
238             }
239             elsif ( @chords == 2 ) {
240 37         101 splice( @chords, 1, 0, ("/") x int($time_d/2) );
241             }
242             }
243 39         77 while ( $chords[-1] eq "/" ) {
244 114         207 pop(@chords);
245             }
246 39         77 $res .= " @chords";
247             $res .= " /* " . $item->{jump}->{text} ." */"
248 39 100       93 if $item->{jump};
249 39         102 $mma .= $res . "\n";
250             }
251             elsif ( $item->{type} eq "repeat start" ) {
252 5         9 push( @mma, $mma );
253 5         12 $mma = "Repeat\n";
254             }
255             elsif ( $item->{type} =~ /repeat end(.*)/ ) {
256 8         26 $mma .= "RepeatEnd$1\n";
257             }
258             else {
259 0         0 warn("OOPS: ", $item->{type}, "\n");
260             }
261             }
262             }
263              
264 2 50       38 push( @mma, $mma ) if $mma;
265 2         16 push( @mma, "\n" . endrepeat('$SongForms') );
266              
267 2         46 join( "", @mma );
268             }
269              
270             sub endrepeat {
271 2     2 0 4 my ( $n ) = @_;
272 2         5 my $res = "RepeatEnd";
273 2 50 0     12 if ( $n =~ /^\$/ ) {
    0          
274 2         9 $res .= " NoWarn $n";
275             }
276             elsif ( $n && $n ne "2" ) {
277 0         0 $res .= " $n";
278             }
279 2         8 $res . "\n";
280             }
281              
282             sub interpret {
283 2     2 0 5 my ( $self, $song ) = @_;
284              
285 2         8 my $res = $self->_interpret1($song);
286 2         6 foreach ( @{ $res->{content} } ) {
  2         8  
287 5         33 $self->_interpret2($_);
288 5         17 $self->_interpret3($_);
289             }
290              
291 2 50       11 if ( $self->{debug} ) {
292 0         0 $Data::Dumper::Deepcopy = 1;
293 0         0 $Data::Dumper::Sortkeys = 1;
294 0         0 warn Dumper($res);
295             }
296              
297 2         6 return $res;
298             }
299              
300             # Filter, and break into sections.
301             sub _interpret1 {
302 2     2   7 my ( $self, $song ) = @_;
303 2         4 my $tokens = [ @{ $song->tokens } ];
  2         9  
304              
305 2         15 while ( $tokens->[0] =~ /^advance|hspace/ ) {
306 0         0 shift(@$tokens);
307             }
308 2         12 while ( $tokens->[-1] =~ /^advance|hspace/ ) {
309 1         4 pop(@$tokens);
310             }
311 2 50       13 unless ( $tokens->[0] =~ /^start (?:repeat|section)/ ) {
312 0         0 warn("Invalid section start: ", $tokens->[0], "\n");
313             }
314              
315 2         19 my $res = { tokens => [ @$tokens ],
316             content => [] };
317 2         5 my $section; # current section
318              
319             my $add_section = sub {
320 5         21 push( @{$res->{content}}, { %$section } )
321 11 100 100 11   27 if $section && @{ $section->{tokens} };
  9         38  
322 2         11 };
323              
324             my $new_section = sub {
325 9     9   20 $add_section->();
326 9         34 $section = { type => "section",
327             tokens => [],
328             };
329 2         8 };
330              
331 2         9 $new_section->();
332              
333 2         9 for ( my $tp = 0; $tp < @$tokens; $tp++ ) {
334 142         188 my $t = $tokens->[$tp];
335              
336             # Treat marked repeat as a section start.
337 142 100 66     268 if ( $t eq "start repeat" && $tokens->[$tp+1] =~ /^mark/
      100        
338 4         22 && @{ $section->{tokens} } ) {
339 1         12 unshift( @$tokens, $t );
340 1         9 $t = "start section";
341             # Fall through...
342             }
343              
344 142 100       236 if ( $t eq "start section" ) {
345 3         9 $new_section->();
346 3         9 next;
347             }
348              
349 139 100       249 if ( $t =~ /^hspace\s+(\d+)$/ ) {
350 1         3 next;
351             }
352 138 50 33     371 if ( $t eq "small" || $t eq "large" ) {
353 0         0 next;
354             }
355              
356 138 100       237 if ( $t =~ /^mark\s+(.+)$/ ) {
357 3         9 $section->{type} = $1;
358 3         7 next;
359             }
360              
361 135 100       216 if ( $t eq "end section" ) {
362 3         10 $new_section->();
363 3         9 next;
364             }
365              
366 132 50       269 if ( $t =~ /^chord (\(.+\))/ ) {
367 0         0 $t = "text $1";
368             }
369              
370 132         165 push( @{ $section->{tokens} }, $t );
  132         215  
371              
372 132 100       303 if ( $t eq "end" ) {
373 1         3 $new_section->();
374 1         3 next;
375             }
376              
377             }
378              
379 2         6 $add_section->();
380              
381 2         27 return $res;
382             }
383              
384             # Process repeat sections.
385             sub _interpret2 {
386 5     5   14 my ( $self, $section ) = @_;
387 5         11 my @tokens = @{ $section->{tokens} };
  5         24  
388              
389 5         10 my $in_repeat;
390             my $repeatpending;
391 5         8 my $alternatives = 0;
392 5         10 my $lastalternative;
393              
394             my $tp;
395              
396             my $peek = sub {
397 6     6   10 my ( $pat ) = @_;
398 6         22 foreach ( @tokens[ $tp+1 .. $#tokens ] ) {
399 7 100       35 return 1 if /$pat/;
400 3 100       13 return if /^chord|advance|end|start/;
401             }
402 1         3 return;
403 5         21 };
404              
405 5         27 for ( $tp = 0; $tp < @tokens; $tp++ ) {
406 132         185 my $t = $tokens[$tp];
407             # printf STDERR ("%2d: %s\n", $tp, $t );
408              
409 132 100       210 if ( $t eq "start repeat" ) {
410 3         5 $tokens[$tp] = "repeat start";
411 3         4 $in_repeat = 1;
412 3         6 $alternatives = 0;
413 3 50       7 $tokens[$lastalternative] = "repeat end"
414             if defined $lastalternative;
415 3         11 $lastalternative = undef;
416 3         3 $repeatpending = 2;
417 3         10 next;
418             }
419              
420             # Implied repeat at start of section.
421 129 100 100     342 if ( $t eq "end repeat" || $t =~ /^alternative / ) {
422 11 100       18 if ( !$in_repeat ) {
423 1         4 unshift( @tokens, "repeat start" );
424 1         2 $in_repeat = 1;
425 1         2 $tp++;
426 1   50     5 $repeatpending //= 2;
427             }
428             }
429              
430             # [ A | B | N1 C } N2 D | will play A B C A B D, but
431             # [ A | B } N1 C } N2 D | will play A B A B C A B A B D
432 129 100       216 if ( $t eq "end repeat" ) {
433 6 100       27 if ( $peek->(qr/^alternative/) ) {
434 4 100       14 if ( $alternatives ) {
435 3         7 splice( @tokens, $tp, 1 );
436 3         18 $tp--;
437 3         10 next;
438             }
439 1         3 unshift( @tokens, "repeat start" );
440 1   50     3 $repeatpending //= 2;
441 1         2 $tp++;
442             }
443 3         11 $tokens[$tp] = "repeat end";
444 3 50       11 $tokens[$tp] .= " " . $repeatpending
445             if $repeatpending != 2;
446 3         5 $lastalternative = undef;
447 3         7 next;
448             }
449              
450 123 50       214 if ( $t =~ /^text\s+\d+\s+(\d+)x$/ ) {
451 0         0 $repeatpending = $1;
452 0         0 next;
453             }
454              
455 123 100       268 if ( $t =~ /^alternative\s+(\d+)$/ ) {
456 5 100 66     25 if ( $1 > $alternatives+1 && defined $lastalternative ) {
457 1         4 $tokens[$lastalternative] = "repeat ending " . ($1-$alternatives);
458             }
459 5         10 $tokens[$tp] = "repeat ending";
460 5         20 $alternatives = $1;
461 5         8 $lastalternative = $tp;
462 5         13 next;
463             }
464             }
465              
466 5 100       17 $tokens[$lastalternative] = "repeat end"
467             if defined $lastalternative;
468              
469 5         30 $section->{tokens} = \@tokens;
470             }
471              
472             sub _interpret3 {
473 5     5   11 my ( $self, $section ) = @_;
474 5         17 my $tokens = [ @{ $section->{tokens} } ];
  5         25  
475 5         15 my @tokens;
476              
477             my $measure; # current measure
478 5         0 my $cell; # current cell
479              
480             my $new_cell = sub {
481 135     135   262 $cell = [];
482 5         18 };
483              
484             my $new_measure = sub {
485 54     54   104 $new_cell->();
486 39         63 push( @{ $section->{content} }, { %{ $measure } } )
  39         182  
487 54 100 100     138 if $measure && @{ $measure->{content} };
  49         135  
488 54         194 $measure = { type => "measure",
489             content => [],
490             };
491 5         18 };
492              
493 5         14 $new_measure->();
494 5         18 $section->{content} = [];
495              
496 5         12 my $i = 0;
497 5         7 my $barskip = 0;
498 5         20 my $in_repeat;
499             my $repeatpending;
500              
501 5         14 while ( @$tokens ) {
502 131         199 my $t = shift(@$tokens);
503 131         170 $i++;
504              
505 131         167 my $done = 0;
506             # warn( $t, $barskip ? "*\n" : "\n" );
507              
508 131 50       233 if ( $t =~ /^chord (\(.+\))/ ) {
509 0         0 $t = "text $1";
510             }
511              
512 131         212 push( @tokens, $t );
513              
514 131 100       256 if ( $t =~ /^coda/ ) {
515 1 50 33     4 if ( @{ $section->{content} } == 0 && @{ $measure->{content} } == 0 ) {
  1         9  
  1         4  
516 1         5 $section->{type} = $t;
517             }
518             else {
519 0         0 $measure->{jump} = { text => "Coda", al => "coda" };
520 0         0 $section->{hasjumps} = 1;
521             }
522 1         2 next;
523             }
524              
525 130 50       209 if ( $barskip ) {
526 0 0       0 if ( $t =~ /^(?:bar|end.*|start.*|repeat*)$/ ) {
527 0         0 $barskip = 0;
528             }
529             else {
530 0         0 next;
531             }
532             }
533              
534 130 100 100     363 if ( $t eq "bar" || $t =~ /^repeat/ ) {
535 44         100 $new_measure->();
536 44 100       118 next if $t eq "bar";
537 13         17 push( @{ $section->{content} }, { type => $t } );
  13         34  
538 13         30 next;
539             }
540              
541 86 100       284 if ( $t =~ /^(chord\s+(.*)|advance\s+\d+)$/ ) {
542 81         146 push( @$cell, $t );
543 81         100 push( @{ $measure->{content} }, [ @$cell ] );
  81         188  
544 81         186 $new_cell->();
545 81         201 next;
546             }
547              
548 5 50       11 if ( $t eq "measure repeat single" ) {
549 0         0 $measure = $section->{content}->[-1];
550 0         0 $barskip = 1;
551 0         0 next;
552             }
553              
554 5 50       12 if ( $t =~ /^measure repeat double$/ ) {
555 0         0 push( @{ $section->{content} }, $section->{content}->[-2] );
  0         0  
556 0         0 $measure = $section->{content}->[-2];
557 0         0 $barskip = 1;
558 0         0 next;
559             }
560              
561 5 100       16 if ( $t =~ /^text \d+ (.*)/ ) {
562 1 50       14 if ( my $jump = isjump($1) ) {
563 1         3 $section->{hasjumps} = 1;
564 1         2 $measure->{jump} = $jump;
565             }
566 1         4 next;
567             }
568              
569 4 50 33     16 if ( $t eq "small" || $t eq "large" ) {
570 0         0 next;
571             }
572              
573 4         10 push( @$cell, $t );
574             }
575 5         13 $new_measure->();
576 5         42 $section->{tokens} = \@tokens;
577             }
578              
579             my %chordqual =
580             ('' => '',
581             '+' => '+',
582             '-#5' => 'm#5',
583             '-' => 'm',
584             '-11' => 'm11',
585             '-6' => 'm6',
586             '-69' => 'm69',
587             '-7' => 'm7',
588             '-7b5' => 'm7b5',
589             '-9' => 'm9',
590             '-^7' => 'mM7',
591             '-^9' => 'mM9',
592             '-b6' => 'mb6', # ???
593             '11' => '11',
594             '13#11' => '13#11',
595             '13#9' => '13#9',
596             '13' => '13',
597             '13b9' => '13b9',
598             '13sus' => '13sus',
599             '13x11' => '13#11',
600             '13x9' => '13#9',
601             '2' => '2',
602             '5' => '5',
603             '6' => '6',
604             '69' => '69',
605             '7#11' => '7#11',
606             '7#5' => '+7',
607             '7#9#11' => '7#9#11',
608             '7#9#5' => '+7#9',
609             '7#9' => '7#9',
610             '7#9b5' => '7b5#9',
611             '7' => '7',
612             '7alt' => '7alt',
613             '7b13' => '7b13',
614             '7b13sus' => '7b13sus', # ???
615             '7b5' => '7b5',
616             '7b9#11' => '7b9#11',
617             '7b9#5' => '7#5b9',
618             '7b9#9' => '7b9#9', # ???
619             '7b9' => '7b9',
620             '7b9b13' => '7b9b13', # ???
621             '7b9b5' => '7b5b9',
622             '7b9sus' => '7b9sus',
623             '7b9x11' => '7b9#11',
624             '7b9x5' => '7#5b9',
625             '7b9x9' => '7b9#9', # ???
626             '7sus' => '7sus4',
627             '7susadd3' => '7sus(add3)', # ???
628             '7x11' => '7#11',
629             '7x5' => '7#5',
630             '7x9' => '7#9',
631             '7x9b5' => '7b5#9',
632             '7x9x11' => '7#9#11',
633             '7x9x5' => '+7#9',
634             '9#11' => '9#11',
635             '9#5' => '+9',
636             '9' => '9',
637             '9b5' => '9b5',
638             '9sus' => '9sus4',
639             '9x11' => '9#11',
640             '9x5' => '+9',
641             '^' => 'M7',
642             '^13' => 'M13',
643             '^7#11' => 'M7#11',
644             '^7#5' => '+M7',
645             '^7' => 'M7',
646             '^9#11' => 'M9#11',
647             '^9' => 'M9',
648             'add9' => 'add9',
649             'alt' => 'alt',
650             'h' => 'ΓΈ', # dim or dim7 ?
651             'h7' => 'dim',
652             'h9' => 'h9', # ???
653             'm#5' => 'm#5', # ???
654             'm' => 'm',
655             'm11' => 'm11',
656             'm6' => 'm6',
657             'm69' => 'm69',
658             'm7' => 'm7',
659             'm7b5' => 'm7b5',
660             'm9' => 'm9',
661             'mb6' => 'mb6', # ???
662             'mv7' => 'mM7',
663             'mv9' => 'mM9',
664             'mx5' => 'm#5', # ???
665             'o' => 'dim',
666             'o7' => 'dim7',
667             'p' => 'aug',
668             'sus' => 'sus4',
669             'v' => '^',
670             'v13' => '^13',
671             'v7' => '^7',
672             'v7x11' => 'M7#11',
673             'v7x5' => '+M7',
674             'v9' => 'M9',
675             'v9x11' => 'M9#11',
676             );
677              
678             my $pc;
679              
680             sub chord2mma {
681 40     40 0 89 my ( $chord ) = @_;
682              
683 40         54 my $bass;
684 40 100       86 if ( $chord =~ m;^(.+)/(.+)$; ) {
685 4         8 $bass = $2;
686 4         6 $chord = $1;
687             }
688 40 50       67 if ( $chord eq "W" ) {
689 0         0 $chord = $pc;
690             }
691             else {
692 40         59 $pc = $chord;
693             }
694              
695 40 50       86 if ( $chord =~ /^N\.?C\.?/ ) {
696 0         0 $chord = "z";
697             }
698              
699 40 50       120 unless ( $chord =~ /^([ABCDEFGWz][b\#]?)(.*)/ ) {
700 0         0 Carp::croak("Invalid chord key: $chord");
701             }
702              
703 40         91 my ( $root, $mod ) = ( $1, $2 );
704              
705 40 50       86 if ( defined($chordqual{$mod}) ) {
706 40         76 $root .= $chordqual{$mod};
707             }
708             else {
709 0         0 Carp::croak("Invalid chord modifier: $chord");
710             }
711              
712 40 100       78 $root .= "/$bass" if $bass;
713 40         114 return $root;
714             }
715              
716             sub key2mma {
717 2     2 0 7 my ( $key ) = @_;
718              
719 2 50       15 unless ( $key =~ /^([ABCDEFGW][b#]?)([-m])?$/ ) {
720 0         0 Carp::croak("Invalid key: $key");
721             }
722              
723 2         11 my ( $root, $min ) = ( $1, $2 );
724 2 50       8 $root .= $min ? " minor" : " major";
725              
726 2         16 return $root;
727              
728             }
729              
730             sub style2mma {
731 5     5 0 13 my ( $style, $section ) = @_;
732 5         25 return "CountrySwing";
733             }
734              
735             sub isjump {
736 1     1 0 5 my ( $code ) = @_;
737              
738             return unless
739 1 50       14 $code =~ m{ ^
740             D\. ([CS]) \. \s+ al \s+
741             ( Coda | Fine | (?:1st|2nd|3rd) \s+ End )
742             $
743             }xoi;
744              
745 1         5 my $al = lc($2);
746 1 50       15 $al = $1 if $al =~ /^(\d)/;
747 1 50       13 { text => $code, da => $1 eq "C" ? "capo" : "segno", al => $al }
748             }
749              
750             1;