line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package main; |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $options; |
6
|
|
|
|
|
|
|
our $config; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
package ChordPro::Song; |
9
|
|
|
|
|
|
|
|
10
|
79
|
|
|
79
|
|
607
|
use strict; |
|
79
|
|
|
|
|
214
|
|
|
79
|
|
|
|
|
2401
|
|
11
|
79
|
|
|
79
|
|
430
|
use warnings; |
|
79
|
|
|
|
|
203
|
|
|
79
|
|
|
|
|
1955
|
|
12
|
|
|
|
|
|
|
|
13
|
79
|
|
|
79
|
|
427
|
use ChordPro; |
|
79
|
|
|
|
|
167
|
|
|
79
|
|
|
|
|
2215
|
|
14
|
79
|
|
|
79
|
|
481
|
use ChordPro::Chords; |
|
79
|
|
|
|
|
182
|
|
|
79
|
|
|
|
|
2276
|
|
15
|
79
|
|
|
79
|
|
31514
|
use ChordPro::Chords::Appearance; |
|
79
|
|
|
|
|
211
|
|
|
79
|
|
|
|
|
2752
|
|
16
|
79
|
|
|
79
|
|
583
|
use ChordPro::Chords::Parser; |
|
79
|
|
|
|
|
183
|
|
|
79
|
|
|
|
|
1807
|
|
17
|
79
|
|
|
79
|
|
419
|
use ChordPro::Output::Common; |
|
79
|
|
|
|
|
214
|
|
|
79
|
|
|
|
|
3704
|
|
18
|
79
|
|
|
79
|
|
513
|
use ChordPro::Utils; |
|
79
|
|
|
|
|
255
|
|
|
79
|
|
|
|
|
7054
|
|
19
|
|
|
|
|
|
|
|
20
|
79
|
|
|
79
|
|
505
|
use Carp; |
|
79
|
|
|
|
|
162
|
|
|
79
|
|
|
|
|
4004
|
|
21
|
79
|
|
|
79
|
|
472
|
use List::Util qw(any); |
|
79
|
|
|
|
|
167
|
|
|
79
|
|
|
|
|
4889
|
|
22
|
79
|
|
|
79
|
|
36971
|
use File::LoadLines; |
|
79
|
|
|
|
|
1025139
|
|
|
79
|
|
|
|
|
5669
|
|
23
|
79
|
|
|
79
|
|
685
|
use Storable qw(dclone); |
|
79
|
|
|
|
|
197
|
|
|
79
|
|
|
|
|
3578
|
|
24
|
79
|
|
|
79
|
|
545
|
use feature 'state'; |
|
79
|
|
|
|
|
210
|
|
|
79
|
|
|
|
|
5971
|
|
25
|
79
|
|
|
79
|
|
602
|
use Text::ParseWords qw(quotewords); |
|
79
|
|
|
|
|
262
|
|
|
79
|
|
|
|
|
4532
|
|
26
|
79
|
|
|
79
|
|
612
|
use File::Basename qw(basename); |
|
79
|
|
|
|
|
228
|
|
|
79
|
|
|
|
|
860736
|
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Parser context. |
29
|
|
|
|
|
|
|
my $def_context = ""; |
30
|
|
|
|
|
|
|
my $in_context = $def_context; |
31
|
|
|
|
|
|
|
my $skip_context = 0; |
32
|
|
|
|
|
|
|
my $grid_arg; |
33
|
|
|
|
|
|
|
my $grid_cells; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# Local transposition. |
36
|
|
|
|
|
|
|
my $xpose = 0; |
37
|
|
|
|
|
|
|
my $xpose_dir; |
38
|
|
|
|
|
|
|
my $capo; |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# Used chords, in order of appearance. |
41
|
|
|
|
|
|
|
my @used_chords; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Chorus lines, if any. |
44
|
|
|
|
|
|
|
my @chorus; |
45
|
|
|
|
|
|
|
my $chorus_xpose = 0; |
46
|
|
|
|
|
|
|
my $chorus_xpose_dir = 0; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# Memorized chords. |
49
|
|
|
|
|
|
|
my %memchords; # all sections |
50
|
|
|
|
|
|
|
my $memchords; # current section |
51
|
|
|
|
|
|
|
my $memcrdinx; # chords tally |
52
|
|
|
|
|
|
|
my $memorizing; # if memorizing (a.o.t. recalling) |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Keep track of unknown chords, to avoid dup warnings. |
55
|
|
|
|
|
|
|
my %warned_chords; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
my $re_chords; # for chords |
58
|
|
|
|
|
|
|
my $intervals; # number of note intervals |
59
|
|
|
|
|
|
|
my @labels; # labels used |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# Normally, transposition and subtitutions are handled by the parser. |
62
|
|
|
|
|
|
|
my $decapo; |
63
|
|
|
|
|
|
|
my $no_transpose; # NYI |
64
|
|
|
|
|
|
|
my $xcmov; # transcode to movable system |
65
|
|
|
|
|
|
|
my $no_substitute; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# Stack for properties like textsize. |
68
|
|
|
|
|
|
|
my %propstack; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my $diag; # for diagnostics |
71
|
|
|
|
|
|
|
my $lineinfo; # keep lineinfo |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# Constructor. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub new { |
76
|
171
|
|
|
171
|
0
|
2614
|
my ( $pkg, $filesource ) = @_; |
77
|
|
|
|
|
|
|
|
78
|
171
|
|
|
|
|
454
|
$xpose = 0; |
79
|
171
|
|
|
|
|
593
|
$grid_arg = [ 4, 4, 1, 1 ]; # 1+4x4+1 |
80
|
171
|
|
|
|
|
450
|
$in_context = $def_context; |
81
|
171
|
|
|
|
|
464
|
@used_chords = (); |
82
|
171
|
|
|
|
|
425
|
%warned_chords = (); |
83
|
171
|
|
|
|
|
386
|
%memchords = (); |
84
|
171
|
|
|
|
|
381
|
%propstack = (); |
85
|
171
|
|
|
|
|
818
|
ChordPro::Chords::reset_song_chords(); |
86
|
171
|
|
|
|
|
353
|
@labels = (); |
87
|
171
|
|
|
|
|
1590
|
@chorus = (); |
88
|
171
|
|
|
|
|
379
|
$capo = undef; |
89
|
171
|
|
|
|
|
379
|
$xcmov = undef; |
90
|
171
|
|
|
|
|
616
|
upd_config(); |
91
|
|
|
|
|
|
|
|
92
|
171
|
|
|
|
|
615
|
$diag->{format} = $config->{diagnostics}->{format}; |
93
|
171
|
|
|
|
|
504
|
$diag->{file} = $filesource; |
94
|
171
|
|
|
|
|
439
|
$diag->{line} = 0; |
95
|
171
|
|
|
|
|
431
|
$diag->{orig} = "(at start of song)"; |
96
|
|
|
|
|
|
|
|
97
|
171
|
|
|
|
|
1845
|
bless { chordsinfo => {}, |
98
|
|
|
|
|
|
|
meta => {}, |
99
|
|
|
|
|
|
|
structure => "linear", |
100
|
|
|
|
|
|
|
} => $pkg; |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub upd_config { |
104
|
351
|
|
|
351
|
0
|
1241
|
$decapo = $config->{settings}->{decapo}; |
105
|
351
|
|
|
|
|
764
|
$lineinfo = $config->{settings}->{lineinfo}; |
106
|
351
|
|
|
|
|
596
|
$intervals = @{ $config->{notes}->{sharp} }; |
|
351
|
|
|
|
|
911
|
|
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
169
|
|
|
sub ::break() {} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub parse_song { |
112
|
169
|
|
|
169
|
0
|
636
|
my ( $self, $lines, $linecnt, $meta, $defs ) = @_; |
113
|
169
|
50
|
|
|
|
720
|
die("OOPS! Wrong meta") unless ref($meta) eq 'HASH'; |
114
|
169
|
|
|
|
|
183240
|
local $config = dclone($config); |
115
|
|
|
|
|
|
|
|
116
|
169
|
50
|
|
|
|
1248
|
warn("Processing song ", $diag->{file}, "...\n") if $options->{verbose}; |
117
|
169
|
|
|
|
|
815
|
::break(); |
118
|
169
|
|
|
|
|
380
|
my @configs; |
119
|
|
|
|
|
|
|
# |
120
|
169
|
50
|
|
|
|
995
|
if ( $lines->[0] =~ /^##config:\s*json/ ) { |
121
|
0
|
|
|
|
|
0
|
my $cf = ""; |
122
|
0
|
|
|
|
|
0
|
shift(@$lines); |
123
|
0
|
|
|
|
|
0
|
$$linecnt++; |
124
|
0
|
|
|
|
|
0
|
while ( @$lines ) { |
125
|
0
|
0
|
|
|
|
0
|
if ( $lines->[0] =~ /^# (.*)/ ) { |
126
|
0
|
|
|
|
|
0
|
$cf .= $1 . "\n"; |
127
|
0
|
|
|
|
|
0
|
shift(@$lines); |
128
|
0
|
|
|
|
|
0
|
$$linecnt++; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
else { |
131
|
0
|
|
|
|
|
0
|
last; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
} |
134
|
0
|
0
|
|
|
|
0
|
if ( $cf ) { |
135
|
0
|
|
|
|
|
0
|
my $pp = JSON::PP->new->relaxed; |
136
|
0
|
|
|
|
|
0
|
my $precfg = $pp->decode($cf); |
137
|
0
|
|
|
|
|
0
|
my $prename = "__PRECFG__"; |
138
|
0
|
|
|
|
|
0
|
ChordPro::Config::precheck( $precfg, $prename ); |
139
|
0
|
|
|
|
|
0
|
push( @configs, ChordPro::Config::prep_configs( $precfg, $prename) ); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
# Load song-specific config, if any. |
143
|
169
|
50
|
66
|
|
|
920
|
if ( !$options->{nosongconfig} && $diag->{file} ) { |
144
|
78
|
50
|
|
|
|
269
|
if ( $options->{verbose} ) { |
145
|
0
|
|
|
|
|
0
|
my $this = ChordPro::Chords::get_parser(); |
146
|
0
|
0
|
|
|
|
0
|
$this = defined($this) ? $this->{system} : ""; |
147
|
0
|
|
|
|
|
0
|
print STDERR ("Parsers at start of ", $diag->{file}, ":"); |
148
|
|
|
|
|
|
|
print STDERR ( $this eq $_ ? " *" : " ", "$_") |
149
|
0
|
0
|
|
|
|
0
|
for keys %{ ChordPro::Chords::Parser->parsers }; |
|
0
|
|
|
|
|
0
|
|
150
|
0
|
|
|
|
|
0
|
print STDERR ("\n"); |
151
|
|
|
|
|
|
|
} |
152
|
78
|
50
|
33
|
|
|
436
|
if ( $meta && $meta->{__config} ) { |
153
|
0
|
|
|
|
|
0
|
my $cf = delete($meta->{__config})->[0]; |
154
|
0
|
0
|
|
|
|
0
|
die("Missing config: $cf\n") unless -s $cf; |
155
|
0
|
0
|
|
|
|
0
|
warn("Config[song]: $cf\n") if $options->{verbose}; |
156
|
0
|
|
|
|
|
0
|
my $have = ChordPro::Config::get_config($cf); |
157
|
0
|
|
|
|
|
0
|
push( @configs, ChordPro::Config::prep_configs( $have, $cf) ); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
else { |
160
|
78
|
|
|
|
|
254
|
for ( "prp", "json" ) { |
161
|
156
|
|
|
|
|
714
|
( my $cf = $diag->{file} ) =~ s/\.\w+$/.$_/; |
162
|
156
|
100
|
|
|
|
609
|
$cf .= ".$_" if $cf eq $diag->{file}; |
163
|
156
|
50
|
|
|
|
2094
|
next unless -s $cf; |
164
|
0
|
0
|
|
|
|
0
|
warn("Config[song]: $cf\n") if $options->{verbose}; |
165
|
0
|
|
|
|
|
0
|
my $have = ChordPro::Config::get_config($cf); |
166
|
0
|
|
|
|
|
0
|
push( @configs, ChordPro::Config::prep_configs( $have, $cf) ); |
167
|
0
|
|
|
|
|
0
|
last; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
} |
171
|
169
|
|
|
|
|
514
|
my $tuncheck = join("|",@{$config->{tuning}}); |
|
169
|
|
|
|
|
834
|
|
172
|
169
|
|
|
|
|
665
|
foreach my $have ( @configs ) { |
173
|
0
|
0
|
|
|
|
0
|
warn("Config[song*]: ", $have->{_src}, "\n") if $options->{verbose}; |
174
|
0
|
|
|
|
|
0
|
my $chords = $have->{chords}; |
175
|
0
|
|
|
|
|
0
|
$config->augment($have); |
176
|
0
|
0
|
|
|
|
0
|
if ( $tuncheck ne join("|",@{$config->{tuning}}) ) { |
|
0
|
|
|
|
|
0
|
|
177
|
0
|
|
|
|
|
0
|
my $res = |
178
|
|
|
|
|
|
|
ChordPro::Chords::set_tuning($config); |
179
|
0
|
0
|
|
|
|
0
|
warn( "Invalid tuning in config: ", $res, "\n" ) if $res; |
180
|
|
|
|
|
|
|
} |
181
|
0
|
|
|
|
|
0
|
ChordPro::Chords::reset_parser(); |
182
|
0
|
|
|
|
|
0
|
ChordPro::Chords::Parser->reset_parsers; |
183
|
0
|
0
|
|
|
|
0
|
if ( $chords ) { |
184
|
0
|
|
|
|
|
0
|
my $c = $chords; |
185
|
0
|
0
|
0
|
|
|
0
|
if ( @$c && $c->[0] eq "append" ) { |
186
|
0
|
|
|
|
|
0
|
shift(@$c); |
187
|
|
|
|
|
|
|
} |
188
|
0
|
|
|
|
|
0
|
foreach ( @$c ) { |
189
|
0
|
|
|
|
|
0
|
my $res = |
190
|
|
|
|
|
|
|
ChordPro::Chords::add_config_chord($_); |
191
|
|
|
|
|
|
|
warn( "Invalid chord in config: ", |
192
|
0
|
0
|
|
|
|
0
|
$_->{name}, ": ", $res, "\n" ) if $res; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
} |
195
|
0
|
0
|
|
|
|
0
|
if ( $options->{verbose} > 1 ) { |
196
|
0
|
0
|
|
|
|
0
|
warn( "Processed ", scalar(@$chords), " chord entries\n") |
197
|
|
|
|
|
|
|
if $chords; |
198
|
0
|
|
|
|
|
0
|
warn( "Totals: ", |
199
|
|
|
|
|
|
|
ChordPro::Chords::chord_stats(), "\n" ); |
200
|
|
|
|
|
|
|
} |
201
|
0
|
|
|
|
|
0
|
if ( 0 && $options->{verbose} ) { |
202
|
|
|
|
|
|
|
my $this = ChordPro::Chords::get_parser()->{system}; |
203
|
|
|
|
|
|
|
print STDERR ("Parsers after local config:"); |
204
|
|
|
|
|
|
|
print STDERR ( $this eq $_ ? " *" : " ", "$_") |
205
|
|
|
|
|
|
|
for keys %{ ChordPro::Chords::Parser->parsers }; |
206
|
|
|
|
|
|
|
print STDERR ("\n"); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
169
|
|
|
|
|
1135
|
$config->unlock; |
211
|
|
|
|
|
|
|
|
212
|
169
|
50
|
|
|
|
276453
|
if ( %$defs ) { |
213
|
0
|
|
|
|
|
0
|
my $c = $config->hmerge( prp2cfg( $defs, $config ) ); |
214
|
0
|
|
|
|
|
0
|
bless $c => ref($config); |
215
|
0
|
|
|
|
|
0
|
$config = $c; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
169
|
|
|
|
|
659
|
for ( qw( transpose transcode decapo lyrics-only ) ) { |
219
|
676
|
100
|
|
|
|
1871
|
next unless defined $options->{$_}; |
220
|
18
|
|
|
|
|
94
|
$config->{settings}->{$_} = $options->{$_}; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
# Catch common error. |
223
|
169
|
50
|
|
|
|
1051
|
unless ( UNIVERSAL::isa( $config->{instrument}, 'HASH' ) ) { |
224
|
0
|
|
0
|
|
|
0
|
$config->{instrument} //= "guitar"; |
225
|
|
|
|
|
|
|
$config->{instrument} = |
226
|
|
|
|
|
|
|
{ type => $config->{instrument}, |
227
|
0
|
|
|
|
|
0
|
description => ucfirst $config->{instrument} }; |
228
|
|
|
|
|
|
|
do_warn( "Missing or invalid instrument - set to ", |
229
|
0
|
|
|
|
|
0
|
$config->{instrument}->{type}, "\n" ); |
230
|
|
|
|
|
|
|
} |
231
|
169
|
|
|
|
|
827
|
$config->lock; |
232
|
169
|
|
|
|
|
299370
|
for ( keys %{ $config->{meta} } ) { |
|
169
|
|
|
|
|
1041
|
|
233
|
0
|
|
0
|
|
|
0
|
$meta->{$_} //= []; |
234
|
0
|
0
|
|
|
|
0
|
if ( UNIVERSAL::isa($config->{meta}->{$_}, 'ARRAY') ) { |
235
|
0
|
|
|
|
|
0
|
push( @{ $meta->{$_} }, @{ $config->{meta}->{$_} } ); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
else { |
238
|
0
|
|
|
|
|
0
|
push( @{ $meta->{$_} }, $config->{meta}->{$_} ); |
|
0
|
|
|
|
|
0
|
|
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
169
|
|
|
|
|
534
|
$no_transpose = $options->{'no-transpose'}; |
243
|
169
|
|
|
|
|
406
|
$no_substitute = $options->{'no-substitute'}; |
244
|
169
|
|
|
|
|
398
|
my $fragment = $options->{fragment}; |
245
|
169
|
|
|
|
|
495
|
my $target = $config->{settings}->{transcode}; |
246
|
169
|
100
|
|
|
|
573
|
if ( $target ) { |
247
|
2
|
50
|
|
|
|
11
|
unless ( ChordPro::Chords::Parser->have_parser($target) ) { |
248
|
2
|
50
|
|
|
|
22
|
if ( my $file = ::rsc_or_file("config/notes/$target.json") ) { |
249
|
2
|
|
|
|
|
19
|
for ( ChordPro::Config::get_config($file) ) { |
250
|
2
|
|
|
|
|
14
|
my $new = $config->hmerge($_); |
251
|
2
|
|
|
|
|
8
|
local $config = $new; |
252
|
2
|
|
|
|
|
29
|
ChordPro::Chords::Parser->new($new); |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
2
|
50
|
|
|
|
22
|
unless ( ChordPro::Chords::Parser->have_parser($target) ) { |
257
|
0
|
|
|
|
|
0
|
die("No transcoder for ", $target, "\n"); |
258
|
|
|
|
|
|
|
} |
259
|
2
|
50
|
|
|
|
27
|
warn("Got transcoder for $target\n") if $::options->{verbose}; |
260
|
2
|
|
|
|
|
13
|
ChordPro::Chords::set_parser($target); |
261
|
2
|
|
|
|
|
11
|
my $p = ChordPro::Chords::get_parser; |
262
|
2
|
|
|
|
|
17
|
$xcmov = $p->movable; |
263
|
2
|
50
|
|
|
|
10
|
if ( $target ne $p->{system} ) { |
264
|
0
|
|
|
|
|
0
|
::dump(ChordPro::Chords::Parser->parsers); |
265
|
|
|
|
|
|
|
warn("OOPS parser mixup, $target <> ", |
266
|
|
|
|
|
|
|
ChordPro::Chords::get_parser->{system}) |
267
|
0
|
|
|
|
|
0
|
} |
268
|
2
|
|
|
|
|
18
|
ChordPro::Chords::set_parser($self->{system}); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
else { |
271
|
167
|
|
|
|
|
646
|
$target = $self->{system}; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
169
|
|
|
|
|
604
|
upd_config(); |
275
|
169
|
|
|
|
|
1175
|
$self->{source} = { file => $diag->{file}, line => 1 + $$linecnt }; |
276
|
169
|
|
|
|
|
615
|
$self->{system} = $config->{notes}->{system}; |
277
|
169
|
|
|
|
|
454
|
$self->{config} = $config; |
278
|
169
|
50
|
|
|
|
718
|
$self->{meta} = $meta if $meta; |
279
|
169
|
|
|
|
|
526
|
$self->{chordsinfo} = {}; |
280
|
169
|
|
66
|
|
|
1045
|
$target //= $self->{system}; |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# Preprocessor. |
283
|
169
|
|
|
|
|
995
|
my $prep = make_preprocessor( $config->{parser}->{preprocess} ); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# Pre-fill meta data, if any. TODO? ALREADY DONE? |
286
|
169
|
50
|
|
|
|
807
|
if ( $options->{meta} ) { |
287
|
0
|
|
|
|
|
0
|
while ( my ($k, $v ) = each( %{ $options->{meta} } ) ) { |
|
0
|
|
|
|
|
0
|
|
288
|
0
|
|
|
|
|
0
|
$self->{meta}->{$k} = [ $v ]; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Build regexp to split out chords. |
293
|
169
|
100
|
|
|
|
1010
|
if ( $config->{settings}->{memorize} ) { |
294
|
1
|
|
|
|
|
9
|
$re_chords = qr/(\[.*?\]|\^)/; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
else { |
297
|
168
|
|
|
|
|
2613
|
$re_chords = qr/(\[.*?\])/; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
169
|
|
|
|
|
465
|
my $skipcnt = 0; |
301
|
169
|
|
|
|
|
596
|
while ( @$lines ) { |
302
|
1925
|
50
|
|
|
|
3719
|
if ( $skipcnt ) { |
303
|
0
|
|
|
|
|
0
|
$skipcnt--; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
else { |
306
|
1925
|
|
|
|
|
3819
|
$diag->{line} = ++$$linecnt; |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
1925
|
|
|
|
|
3720
|
$_ = shift(@$lines); |
310
|
1925
|
|
33
|
|
|
6156
|
while ( /\\\Z/ && @$lines ) { |
311
|
0
|
|
|
|
|
0
|
chop; |
312
|
0
|
|
|
|
|
0
|
my $cont = shift(@$lines); |
313
|
0
|
|
|
|
|
0
|
$$linecnt++; |
314
|
0
|
|
|
|
|
0
|
$cont =~ s/^\s+//; |
315
|
0
|
|
|
|
|
0
|
$_ .= $cont; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
# Uncomment this to allow \uXXXX escapes. |
319
|
1925
|
|
|
|
|
6382
|
s/\\u([0-9a-f]{4})/chr(hex("0x$1"))/ige; |
|
0
|
|
|
|
|
0
|
|
320
|
|
|
|
|
|
|
# Uncomment this to allow \u{XX...} escapes. |
321
|
|
|
|
|
|
|
# s/\\u\{([0-9a-f]+)\}/chr(hex("0x$1"))/ige; |
322
|
|
|
|
|
|
|
|
323
|
1925
|
|
|
|
|
163041
|
$diag->{orig} = $_; |
324
|
|
|
|
|
|
|
# Get rid of TABs. |
325
|
1925
|
|
|
|
|
3532
|
s/\t/ /g; |
326
|
|
|
|
|
|
|
|
327
|
1925
|
50
|
|
|
|
4655
|
if ( $config->{debug}->{echo} ) { |
328
|
0
|
|
|
|
|
0
|
warn(sprintf("==[%3d]=> %s\n", $diag->{line}, $diag->{orig} ) ); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
1925
|
50
|
|
|
|
4005
|
if ( $prep->{all} ) { |
332
|
|
|
|
|
|
|
# warn("PRE: ", $_, "\n"); |
333
|
0
|
|
|
|
|
0
|
$prep->{all}->($_); |
334
|
|
|
|
|
|
|
# warn("POST: ", $_, "\n"); |
335
|
0
|
0
|
|
|
|
0
|
if ( /\n/ ) { |
336
|
0
|
|
|
|
|
0
|
my @a = split( /\n/, $_ ); |
337
|
0
|
|
|
|
|
0
|
$_ = shift(@a); |
338
|
0
|
|
|
|
|
0
|
unshift( @$lines, @a ); |
339
|
0
|
|
|
|
|
0
|
$skipcnt += @a; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
1925
|
100
|
|
|
|
3669
|
if ( $skip_context ) { |
344
|
4
|
100
|
|
|
|
31
|
if ( /^\s*\{(\w+)\}\s*$/ ) { |
345
|
2
|
|
|
|
|
7
|
my $dir = $self->parse_directive($1); |
346
|
2
|
50
|
|
|
|
13
|
if ( $dir->{name} eq "end_of_$in_context" ) { |
347
|
2
|
|
|
|
|
3
|
$in_context = $def_context; |
348
|
2
|
|
|
|
|
5
|
$skip_context = 0; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
} |
351
|
4
|
|
|
|
|
10
|
next; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
1921
|
100
|
|
|
|
4730
|
if ( /^\s*\{(new_song|ns)\}\s*$/ ) { |
355
|
32
|
100
|
|
|
|
126
|
last if $self->{body}; |
356
|
3
|
|
|
|
|
8
|
next; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
1889
|
100
|
|
|
|
3980
|
if ( /^#/ ) { |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Handle assets. |
362
|
55
|
|
|
|
|
139
|
my $kw = ""; |
363
|
55
|
|
|
|
|
114
|
my $kv = {}; |
364
|
55
|
100
|
|
|
|
190
|
if ( /^##(image|asset):\s+(.*)/i ) { |
365
|
1
|
|
|
|
|
4
|
$kw = lc($1); |
366
|
1
|
|
|
|
|
6
|
$kv = parse_kv($2); |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
55
|
100
|
|
|
|
145
|
if ( $kw eq "image" ) { |
370
|
1
|
|
|
|
|
8
|
my $id = $kv->{id}; |
371
|
1
|
50
|
|
|
|
3
|
unless ( $id ) { |
372
|
0
|
|
|
|
|
0
|
do_warn("Missing id for image asset\n"); |
373
|
0
|
|
|
|
|
0
|
next; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# In-line image asset. |
377
|
1
|
|
|
|
|
612
|
require MIME::Base64; |
378
|
1
|
|
|
|
|
687
|
require Image::Info; |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# Read the image. |
381
|
1
|
|
|
|
|
3
|
my $data = ''; |
382
|
1
|
|
66
|
|
|
12
|
while ( @$lines && $lines->[0] =~ /^# (.+)/ ) { |
383
|
3
|
|
|
|
|
17
|
$data .= MIME::Base64::decode($1); |
384
|
3
|
|
|
|
|
15
|
shift(@$lines); |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# Get info. |
388
|
1
|
|
|
|
|
9
|
my $info = Image::Info::image_info(\$data); |
389
|
1
|
50
|
|
|
|
4007
|
if ( $info->{error} ) { |
390
|
0
|
|
|
|
|
0
|
do_warn($info->{error}); |
391
|
0
|
|
|
|
|
0
|
next; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# Store in assets. |
395
|
1
|
|
50
|
|
|
8
|
$self->{assets} //= {}; |
396
|
|
|
|
|
|
|
$self->{assets}->{$id} = |
397
|
|
|
|
|
|
|
{ data => $data, type => $info->{file_ext}, |
398
|
|
|
|
|
|
|
width => $info->{width}, height => $info->{height}, |
399
|
1
|
|
|
|
|
12
|
}; |
400
|
|
|
|
|
|
|
|
401
|
1
|
50
|
|
|
|
5
|
if ( $config->{debug}->{images} ) { |
402
|
0
|
|
|
|
|
0
|
warn("asset[$id] ", length($data), " bytes, ", |
403
|
|
|
|
|
|
|
"width=$info->{width}, height=$info->{height}", |
404
|
|
|
|
|
|
|
"\n"); |
405
|
|
|
|
|
|
|
} |
406
|
1
|
|
|
|
|
8
|
next; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
54
|
50
|
|
|
|
143
|
if ( $kw eq "asset" ) { |
410
|
0
|
|
|
|
|
0
|
my $id = $kv->{id}; |
411
|
0
|
|
|
|
|
0
|
my $type = $kv->{type}; |
412
|
0
|
0
|
|
|
|
0
|
unless ( $id ) { |
413
|
0
|
|
|
|
|
0
|
do_warn("Missing id for asset\n"); |
414
|
0
|
|
|
|
|
0
|
next; |
415
|
|
|
|
|
|
|
} |
416
|
0
|
0
|
|
|
|
0
|
unless ( $type ) { |
417
|
0
|
|
|
|
|
0
|
do_warn("Missing type for asset\n"); |
418
|
0
|
|
|
|
|
0
|
next; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
# Read the data. |
422
|
0
|
|
|
|
|
0
|
my @data; |
423
|
0
|
|
0
|
|
|
0
|
while ( @$lines && $lines->[0] =~ /^# (.+)/ ) { |
424
|
0
|
|
|
|
|
0
|
push( @data, $1 ); |
425
|
0
|
|
|
|
|
0
|
shift(@$lines); |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Store in assets. |
429
|
0
|
|
0
|
|
|
0
|
$self->{assets} //= {}; |
430
|
|
|
|
|
|
|
$self->{assets}->{$id} = |
431
|
|
|
|
|
|
|
{ data => \@data, type => $type, |
432
|
|
|
|
|
|
|
subtype => $config->{delegates}->{$type}->{type}, |
433
|
|
|
|
|
|
|
handler => $config->{delegates}->{$type}->{handler}, |
434
|
0
|
|
|
|
|
0
|
}; |
435
|
0
|
0
|
|
|
|
0
|
if ( $config->{debug}->{images} ) { |
436
|
0
|
|
|
|
|
0
|
warn("asset[$id] ", ::dump($self->{assets}->{$id})); |
437
|
|
|
|
|
|
|
} |
438
|
0
|
|
|
|
|
0
|
next; |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
# Collect pre-title stuff separately. |
442
|
54
|
50
|
33
|
|
|
166
|
if ( exists $self->{title} || $fragment ) { |
443
|
54
|
|
|
|
|
159
|
$self->add( type => "ignore", text => $_ ); |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
else { |
446
|
0
|
|
|
|
|
0
|
push( @{ $self->{preamble} }, $_ ); |
|
0
|
|
|
|
|
0
|
|
447
|
|
|
|
|
|
|
} |
448
|
54
|
|
|
|
|
139
|
next; |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
1834
|
100
|
|
|
|
3863
|
if ( $in_context eq "tab" ) { |
452
|
62
|
100
|
|
|
|
265
|
unless ( /^\s*\{(?:end_of_tab|eot)\}\s*$/ ) { |
453
|
49
|
|
|
|
|
131
|
$self->add( type => "tabline", text => $_ ); |
454
|
49
|
|
|
|
|
98
|
next; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
|
458
|
1785
|
50
|
|
|
|
4140
|
if ( exists $config->{delegates}->{$in_context} ) { |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# 'open' indicates open. |
461
|
0
|
0
|
|
|
|
0
|
if ( /^\s*\{(?:end_of_\Q$in_context\E)\}\s*$/ ) { |
462
|
0
|
|
|
|
|
0
|
delete $self->{body}->[-1]->{open}; |
463
|
|
|
|
|
|
|
# A subsequent {start_of_XXX} will reopen a new item |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
else { |
466
|
|
|
|
|
|
|
# Add to an open item. |
467
|
0
|
0
|
0
|
|
|
0
|
if ( $self->{body} && @{ $self->{body} } |
|
0
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
468
|
|
|
|
|
|
|
&& $self->{body}->[-1]->{context} eq $in_context |
469
|
|
|
|
|
|
|
&& $self->{body}->[-1]->{open} ) { |
470
|
0
|
|
|
|
|
0
|
push( @{$self->{body}->[-1]->{data}}, $_ ); |
|
0
|
|
|
|
|
0
|
|
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
# Else start new item. |
474
|
|
|
|
|
|
|
else { |
475
|
0
|
|
|
|
|
0
|
my %opts; |
476
|
|
|
|
|
|
|
####TODO |
477
|
0
|
0
|
0
|
|
|
0
|
if ( $xpose || $config->{settings}->{transpose} ) { |
478
|
|
|
|
|
|
|
$opts{transpose} = |
479
|
0
|
|
0
|
|
|
0
|
$xpose + ($config->{settings}->{transpose}//0 ); |
480
|
|
|
|
|
|
|
} |
481
|
0
|
|
|
|
|
0
|
my $d = $config->{delegates}->{$in_context}; |
482
|
|
|
|
|
|
|
$self->add( type => "delegate", |
483
|
|
|
|
|
|
|
delegate => $d->{module}, |
484
|
|
|
|
|
|
|
subtype => $d->{type}, |
485
|
|
|
|
|
|
|
handler => $d->{handler}, |
486
|
0
|
|
|
|
|
0
|
data => [ $_ ], |
487
|
|
|
|
|
|
|
opts => \%opts, |
488
|
|
|
|
|
|
|
open => 1 ); |
489
|
|
|
|
|
|
|
} |
490
|
0
|
|
|
|
|
0
|
next; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# For now, directives should go on their own lines. |
495
|
1785
|
100
|
|
|
|
7493
|
if ( /^\s*\{(.*)\}\s*$/ ) { |
496
|
997
|
|
|
|
|
3082
|
my $dir = $1; |
497
|
997
|
50
|
|
|
|
2455
|
if ( $prep->{directive} ) { |
498
|
|
|
|
|
|
|
# warn("PRE: ", $_, "\n"); |
499
|
0
|
|
|
|
|
0
|
$prep->{directive}->($dir); |
500
|
|
|
|
|
|
|
# warn("POST: ", $_, "\n"); |
501
|
|
|
|
|
|
|
} |
502
|
997
|
100
|
|
|
|
2862
|
$self->add( type => "ignore", |
503
|
|
|
|
|
|
|
text => $_ ) |
504
|
|
|
|
|
|
|
unless $self->directive($dir); |
505
|
997
|
|
|
|
|
3691
|
next; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
788
|
50
|
66
|
|
|
4664
|
if ( /\S/ && !$fragment && !exists $self->{title} ) { |
|
|
|
66
|
|
|
|
|
509
|
0
|
|
|
|
|
0
|
do_warn("Missing {title} -- prepare for surprising results"); |
510
|
0
|
|
|
|
|
0
|
unshift( @$lines, "{title:$_}"); |
511
|
0
|
|
|
|
|
0
|
$skipcnt++; |
512
|
0
|
|
|
|
|
0
|
next; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
788
|
50
|
|
|
|
1867
|
if ( $in_context eq "tab" ) { |
516
|
0
|
|
|
|
|
0
|
$self->add( type => "tabline", text => $_ ); |
517
|
0
|
|
|
|
|
0
|
warn("OOPS"); |
518
|
0
|
|
|
|
|
0
|
next; |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
788
|
100
|
|
|
|
1666
|
if ( $in_context eq "grid" ) { |
522
|
39
|
|
|
|
|
122
|
$self->add( type => "gridline", $self->decompose_grid($_) ); |
523
|
39
|
|
|
|
|
130
|
next; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
749
|
100
|
33
|
|
|
2391
|
if ( /\S/ ) { |
|
|
50
|
|
|
|
|
|
527
|
452
|
50
|
|
|
|
1081
|
if ( $prep->{songline} ) { |
528
|
|
|
|
|
|
|
# warn("PRE: ", $_, "\n"); |
529
|
0
|
|
|
|
|
0
|
$prep->{songline}->($_); |
530
|
|
|
|
|
|
|
# warn("POST: ", $_, "\n"); |
531
|
|
|
|
|
|
|
} |
532
|
452
|
|
|
|
|
1276
|
$self->add( type => "songline", $self->decompose($_) ); |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
elsif ( exists $self->{title} || $fragment ) { |
535
|
297
|
|
|
|
|
776
|
$self->add( type => "empty" ); |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
else { |
538
|
|
|
|
|
|
|
# Collect pre-title stuff separately. |
539
|
0
|
|
|
|
|
0
|
push( @{ $self->{preamble} }, $_ ); |
|
0
|
|
|
|
|
0
|
|
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
} |
542
|
169
|
50
|
|
|
|
653
|
do_warn("Unterminated context in song: $in_context") |
543
|
|
|
|
|
|
|
if $in_context; |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# These don't make sense after processing. Or do they? |
546
|
|
|
|
|
|
|
# delete $self->{meta}->{$_} for qw( key_actual key_from ); |
547
|
|
|
|
|
|
|
|
548
|
169
|
50
|
|
|
|
740
|
warn("Processed song...\n") if $options->{verbose}; |
549
|
169
|
|
|
|
|
501
|
$diag->{format} = "\"%f\": %m"; |
550
|
|
|
|
|
|
|
|
551
|
169
|
50
|
|
|
|
608
|
$self->dump(0) if $config->{debug}->{song} > 1; |
552
|
|
|
|
|
|
|
|
553
|
169
|
100
|
|
|
|
537
|
if ( @labels ) { |
554
|
1
|
|
|
|
|
5
|
$self->{labels} = [ @labels ]; |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# Suppress chords that the user considers 'easy'. |
558
|
169
|
|
|
|
|
371
|
my %suppress; |
559
|
169
|
|
|
|
|
581
|
my $xc = $config->{settings}->{transcode}; |
560
|
169
|
|
|
|
|
334
|
for ( @{ $config->{diagrams}->{suppress} } ) { |
|
169
|
|
|
|
|
747
|
|
561
|
0
|
|
|
|
|
0
|
my $info = ChordPro::Chords::known_chord($_); |
562
|
0
|
0
|
|
|
|
0
|
warn("Unknown chord \"$_\" in suppress list\n"), next |
563
|
|
|
|
|
|
|
unless $info; |
564
|
|
|
|
|
|
|
# Note we do transcode, but we do not transpose. |
565
|
0
|
0
|
|
|
|
0
|
if ( $xc ) { |
566
|
0
|
|
|
|
|
0
|
$info = $info->transcode($xc); |
567
|
|
|
|
|
|
|
} |
568
|
0
|
|
|
|
|
0
|
$suppress{$info->name} = 1; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
# Suppress chords that the user don't want. |
571
|
169
|
|
|
|
|
404
|
while ( my ($k,$v) = each %{ $self->{chordsinfo} } ) { |
|
599
|
|
|
|
|
2369
|
|
572
|
430
|
100
|
100
|
|
|
2127
|
$suppress{$k} = 1 if !is_true($v->{diagram}//1); |
573
|
|
|
|
|
|
|
} |
574
|
169
|
100
|
|
|
|
794
|
@used_chords = map { $suppress{$_} ? () : $_ } @used_chords; |
|
902
|
|
|
|
|
2289
|
|
575
|
|
|
|
|
|
|
|
576
|
169
|
|
|
|
|
384
|
my $diagrams; |
577
|
169
|
100
|
|
|
|
720
|
if ( exists($self->{settings}->{diagrams} ) ) { |
578
|
5
|
|
|
|
|
14
|
$diagrams = $self->{settings}->{diagrams}; |
579
|
5
|
|
100
|
|
|
28
|
$diagrams &&= $config->{diagrams}->{show} || "all"; |
|
|
|
66
|
|
|
|
|
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
else { |
582
|
164
|
|
|
|
|
455
|
$diagrams = $config->{diagrams}->{show}; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
|
585
|
169
|
50
|
66
|
|
|
1767
|
if ( $diagrams =~ /^(user|all)$/ |
586
|
|
|
|
|
|
|
&& !ChordPro::Chords::Parser->get_parser($target,1)->has_diagrams ) { |
587
|
|
|
|
|
|
|
do_warn( "Chord diagrams suppressed for " . |
588
|
0
|
0
|
|
|
|
0
|
ucfirst($target) . " chords" ) unless $options->{silent}; |
589
|
0
|
|
|
|
|
0
|
$diagrams = "none"; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
169
|
50
|
|
|
|
1470
|
if ( $diagrams eq "user" ) { |
593
|
|
|
|
|
|
|
|
594
|
0
|
0
|
0
|
|
|
0
|
if ( $self->{define} && @{$self->{define}} ) { |
|
0
|
|
|
|
|
0
|
|
595
|
0
|
|
|
|
|
0
|
my %h = map { demarkup($_) => 1 } @used_chords; |
|
0
|
|
|
|
|
0
|
|
596
|
|
|
|
|
|
|
@used_chords = |
597
|
0
|
0
|
|
|
|
0
|
map { $h{$_->{name}} ? $_->{name} : () } @{$self->{define}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
else { |
600
|
0
|
|
|
|
|
0
|
@used_chords = (); |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
} |
603
|
|
|
|
|
|
|
else { |
604
|
169
|
|
|
|
|
412
|
my %h; |
605
|
900
|
100
|
|
|
|
3040
|
@used_chords = map { $h{$_}++ ? () : $_ } |
606
|
169
|
|
|
|
|
505
|
map { demarkup($_) } @used_chords; |
|
900
|
|
|
|
|
1849
|
|
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
169
|
50
|
|
|
|
838
|
if ( $config->{diagrams}->{sorted} ) { |
610
|
0
|
|
|
|
|
0
|
@used_chords = |
611
|
|
|
|
|
|
|
sort ChordPro::Chords::chordcompare @used_chords; |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
# For headings, footers, table of contents, ... |
615
|
169
|
|
50
|
|
|
2688
|
$self->{meta}->{chords} //= [ @used_chords ]; |
616
|
169
|
|
|
|
|
385
|
$self->{meta}->{numchords} = [ scalar(@{$self->{meta}->{chords}}) ]; |
|
169
|
|
|
|
|
761
|
|
617
|
|
|
|
|
|
|
|
618
|
169
|
100
|
|
|
|
1000
|
if ( $diagrams =~ /^(user|all)$/ ) { |
619
|
|
|
|
|
|
|
$self->{chords} = |
620
|
114
|
|
|
|
|
743
|
{ type => "diagrams", |
621
|
|
|
|
|
|
|
origin => "song", |
622
|
|
|
|
|
|
|
show => $diagrams, |
623
|
|
|
|
|
|
|
chords => [ @used_chords ], |
624
|
|
|
|
|
|
|
}; |
625
|
|
|
|
|
|
|
|
626
|
114
|
50
|
|
|
|
406
|
if ( %warned_chords ) { |
627
|
0
|
|
|
|
|
0
|
my @a = sort ChordPro::Chords::chordcompare |
628
|
|
|
|
|
|
|
keys(%warned_chords); |
629
|
0
|
|
|
|
|
0
|
my $l; |
630
|
0
|
0
|
|
|
|
0
|
if ( @a > 1 ) { |
631
|
0
|
|
|
|
|
0
|
my $a = pop(@a); |
632
|
0
|
|
|
|
|
0
|
$l = '"' . join('", "', @a) . '" and "' . $a . '"'; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
else { |
635
|
0
|
|
|
|
|
0
|
$l = '"' . $a[0] . '"'; |
636
|
|
|
|
|
|
|
} |
637
|
0
|
|
|
|
|
0
|
do_warn( "No chord diagram defined for $l (skipped)\n" ); |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
169
|
50
|
|
|
|
563
|
$self->dump(0) if $config->{debug}->{song}; |
642
|
169
|
50
|
|
|
|
502
|
$self->dump(1) if $config->{debug}->{songfull}; |
643
|
|
|
|
|
|
|
|
644
|
169
|
|
|
|
|
874
|
return $self; |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
sub add { |
648
|
1220
|
|
|
1220
|
0
|
2119
|
my $self = shift; |
649
|
1220
|
50
|
|
|
|
2491
|
return if $skip_context; |
650
|
1220
|
|
|
|
|
5247
|
push( @{$self->{body}}, |
651
|
|
|
|
|
|
|
{ context => $in_context, |
652
|
1220
|
100
|
|
|
|
1831
|
$lineinfo ? ( line => $diag->{line} ) : (), |
653
|
|
|
|
|
|
|
@_ } ); |
654
|
1220
|
100
|
|
|
|
13757
|
if ( $in_context eq "chorus" ) { |
655
|
106
|
|
|
|
|
344
|
push( @chorus, { context => $in_context, @_ } ); |
656
|
106
|
|
|
|
|
194
|
$chorus_xpose = $xpose; |
657
|
106
|
|
|
|
|
298
|
$chorus_xpose_dir = $xpose_dir; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
# Parses a chord and adds it to the song. |
662
|
|
|
|
|
|
|
# It understands markup, parenthesized chords and annotations. |
663
|
|
|
|
|
|
|
# Returns the chord Appearance. |
664
|
|
|
|
|
|
|
sub chord { |
665
|
937
|
|
|
937
|
0
|
2400
|
my ( $self, $orig ) = @_; |
666
|
937
|
50
|
|
|
|
2442
|
Carp::confess unless length($orig); |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# Intercept annotations. |
669
|
937
|
100
|
|
|
|
2339
|
if ( $orig =~ /^\*(.+)/ ) { |
670
|
3
|
|
|
|
|
45
|
my $i = ChordPro::Chord::Annotation->new |
671
|
|
|
|
|
|
|
( { name => $orig, text => $1 } ); |
672
|
|
|
|
|
|
|
return |
673
|
3
|
|
|
|
|
15
|
ChordPro::Chords::Appearance->new |
674
|
|
|
|
|
|
|
( key => $self->add_chord($i), info => $i, orig => $orig ); |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
# Check for markup. |
678
|
934
|
|
|
|
|
1683
|
my $markup = $orig; |
679
|
934
|
|
|
|
|
2904
|
my $c = demarkup($orig); |
680
|
934
|
100
|
|
|
|
2515
|
if ( $markup eq $c ) { # no markup |
681
|
927
|
|
|
|
|
1620
|
undef $markup; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
# Special treatment for parenthesized chords. |
685
|
934
|
|
|
|
|
1835
|
$c =~ s/^\((.*)\)$/$1/; |
686
|
934
|
50
|
|
|
|
2420
|
do_warn("Double parens in chord: \"$orig\"") |
687
|
|
|
|
|
|
|
if $c =~ s/^\((.*)\)$/$1/; |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
# We have a 'bare' chord now. Parse it. |
690
|
934
|
|
|
|
|
2473
|
my $info = $self->parse_chord($c); |
691
|
934
|
50
|
|
|
|
2360
|
unless ( defined $info ) { |
692
|
|
|
|
|
|
|
# Warning was given. |
693
|
|
|
|
|
|
|
# Make annotation. |
694
|
0
|
|
|
|
|
0
|
my $i = ChordPro::Chord::Annotation->new |
695
|
|
|
|
|
|
|
( { name => $orig, text => $orig } ); |
696
|
|
|
|
|
|
|
return |
697
|
0
|
|
|
|
|
0
|
ChordPro::Chords::Appearance->new |
698
|
|
|
|
|
|
|
( key => $self->add_chord($i), info => $i, orig => $orig ); |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
934
|
|
|
|
|
7140
|
my $ap = ChordPro::Chords::Appearance->new( orig => $orig ); |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
# Handle markup, if any. |
704
|
934
|
100
|
|
|
|
15267
|
if ( $markup ) { |
|
|
50
|
|
|
|
|
|
705
|
7
|
100
|
100
|
|
|
164
|
if ( $markup =~ s/\>\Q$c\E\>%{formatted} |
706
|
|
|
|
|
|
|
|| |
707
|
|
|
|
|
|
|
$markup =~ s/\>\(\Q$c\E\)\>(%{formatted}) ) { |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
else { |
710
|
1
|
|
|
|
|
12
|
do_warn("Invalid markup in chord: \"$markup\"\n"); |
711
|
|
|
|
|
|
|
} |
712
|
7
|
|
|
|
|
35
|
$ap->format = $markup; |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
elsif ( (my $m = $orig) =~ s/\Q$c\E/%{formatted}/ ) { |
715
|
927
|
100
|
|
|
|
2909
|
$ap->format = $m unless $m eq "%{formatted}"; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
# After parsing, the chord can be changed by transpose/code. |
719
|
|
|
|
|
|
|
# info->name is the new key. |
720
|
934
|
|
|
|
|
3039
|
$ap->key = $self->add_chord( $info, $c = $info->name ); |
721
|
934
|
|
|
|
|
2640
|
$ap->info = $info; |
722
|
|
|
|
|
|
|
|
723
|
934
|
100
|
100
|
|
|
2491
|
unless ( $info->is_nc || $info->is_note ) { |
724
|
|
|
|
|
|
|
# if ( $info->is_keyboard ) { |
725
|
922
|
50
|
0
|
|
|
3404
|
if ( $::config->{instrument}->{type} eq "keyboard" ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
726
|
0
|
|
|
|
|
0
|
push( @used_chords, $c ); |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
elsif ( $info->{origin} ) { |
729
|
|
|
|
|
|
|
# Include if we have diagram info. |
730
|
766
|
50
|
|
|
|
1884
|
push( @used_chords, $c ) if $info->has_diagram; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
elsif ( $::running_under_test ) { |
733
|
|
|
|
|
|
|
# Tests run without config and chords, so pretend. |
734
|
156
|
|
|
|
|
381
|
push( @used_chords, $c ); |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
elsif ( ! ( $info->is_rootless |
737
|
|
|
|
|
|
|
|| $info->has_diagram |
738
|
|
|
|
|
|
|
|| !$info->parser->has_diagrams |
739
|
|
|
|
|
|
|
) ) { |
740
|
|
|
|
|
|
|
do_warn("Unknown chord: $c") |
741
|
0
|
0
|
|
|
|
0
|
unless $warned_chords{$c}++; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
934
|
|
|
|
|
2711
|
return $ap; |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
sub decompose { |
749
|
576
|
|
|
576
|
0
|
1372
|
my ($self, $orig) = @_; |
750
|
576
|
|
|
|
|
1949
|
my $line = fmt_subst( $self, $orig ); |
751
|
576
|
100
|
|
|
|
48735
|
undef $orig if $orig eq $line; |
752
|
576
|
|
|
|
|
3581
|
$line =~ s/\s+$//; |
753
|
576
|
|
|
|
|
6308
|
my @a = split( $re_chords, $line, -1); |
754
|
|
|
|
|
|
|
|
755
|
576
|
100
|
|
|
|
1948
|
if ( @a <= 1 ) { |
756
|
233
|
50
|
|
|
|
1329
|
return ( phrases => [ $line ], |
757
|
|
|
|
|
|
|
$orig ? ( orig => $orig ) : (), |
758
|
|
|
|
|
|
|
); |
759
|
|
|
|
|
|
|
} |
760
|
|
|
|
|
|
|
|
761
|
343
|
|
|
|
|
594
|
my $dummy; |
762
|
343
|
100
|
|
|
|
1009
|
shift(@a) if $a[0] eq ""; |
763
|
343
|
100
|
|
|
|
2416
|
unshift(@a, '[]'), $dummy++ if $a[0] !~ $re_chords; |
764
|
|
|
|
|
|
|
|
765
|
343
|
|
|
|
|
832
|
my @phrases; |
766
|
|
|
|
|
|
|
my @chords; |
767
|
343
|
|
|
|
|
920
|
while ( @a ) { |
768
|
1035
|
|
|
|
|
1956
|
my $chord = shift(@a); |
769
|
1035
|
|
|
|
|
2035
|
push(@phrases, shift(@a)); |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
# Normal chords. |
772
|
1035
|
100
|
100
|
|
|
8846
|
if ( $chord =~ s/^\[(.*)\]$/$1/ && $chord ne "^" ) { |
|
|
100
|
66
|
|
|
|
|
773
|
1008
|
100
|
|
|
|
3794
|
push(@chords, $chord eq "" ? "" : $self->chord($chord)); |
774
|
1008
|
100
|
100
|
|
|
2611
|
if ( $memchords && !$dummy ) { |
775
|
21
|
100
|
|
|
|
46
|
if ( $memcrdinx == 0 ) { |
776
|
3
|
|
|
|
|
5
|
$memorizing++; |
777
|
|
|
|
|
|
|
} |
778
|
21
|
100
|
|
|
|
45
|
if ( $memorizing ) { |
779
|
20
|
|
|
|
|
38
|
push( @$memchords, $chords[-1] ); |
780
|
|
|
|
|
|
|
warn("Chord memorized for $in_context\[$memcrdinx]: ", |
781
|
|
|
|
|
|
|
$chords[-1], "\n") |
782
|
20
|
50
|
|
|
|
53
|
if $config->{debug}->{chords}; |
783
|
|
|
|
|
|
|
} |
784
|
21
|
|
|
|
|
36
|
$memcrdinx++; |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
} |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
# Recall memorized chords. |
789
|
|
|
|
|
|
|
elsif ( $memchords && $in_context ) { |
790
|
20
|
100
|
100
|
|
|
77
|
if ( $memcrdinx == 0 && @$memchords == 0 ) { |
|
|
50
|
|
|
|
|
|
791
|
1
|
|
|
|
|
14
|
do_warn("No chords memorized for $in_context"); |
792
|
1
|
|
|
|
|
21
|
push( @chords, $chord ); |
793
|
1
|
|
|
|
|
3
|
undef $memchords; |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
elsif ( $memcrdinx >= @$memchords ) { |
796
|
0
|
|
|
|
|
0
|
do_warn("Not enough chords memorized for $in_context"); |
797
|
0
|
|
|
|
|
0
|
push( @chords, $chord ); |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
else { |
800
|
19
|
|
|
|
|
71
|
push( @chords, $self->chord($memchords->[$memcrdinx]->chord_display)); |
801
|
|
|
|
|
|
|
warn("Chord recall $in_context\[$memcrdinx]: ", $chords[-1], "\n") |
802
|
19
|
50
|
|
|
|
63
|
if $config->{debug}->{chords}; |
803
|
|
|
|
|
|
|
} |
804
|
20
|
|
|
|
|
32
|
$memcrdinx++; |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
# Not memorizing. |
808
|
|
|
|
|
|
|
else { |
809
|
|
|
|
|
|
|
# do_warn("No chords memorized for $in_context"); |
810
|
7
|
|
|
|
|
13
|
push( @chords, $chord ); |
811
|
|
|
|
|
|
|
} |
812
|
1035
|
|
|
|
|
2970
|
$dummy = 0; |
813
|
|
|
|
|
|
|
} |
814
|
|
|
|
|
|
|
|
815
|
343
|
100
|
|
|
|
1985
|
return ( phrases => \@phrases, |
816
|
|
|
|
|
|
|
chords => \@chords, |
817
|
|
|
|
|
|
|
$orig ? ( orig => $orig ) : (), |
818
|
|
|
|
|
|
|
); |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
sub cdecompose { |
822
|
124
|
|
|
124
|
0
|
336
|
my ( $self, $line ) = @_; |
823
|
124
|
50
|
|
|
|
571
|
$line = fmt_subst( $self, $line ) unless $no_substitute; |
824
|
124
|
|
|
|
|
10283
|
my %res = $self->decompose($line); |
825
|
124
|
100
|
|
|
|
702
|
return ( text => $line ) unless $res{chords}; |
826
|
14
|
|
|
|
|
76
|
return %res; |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub decompose_grid { |
830
|
39
|
|
|
39
|
0
|
88
|
my ($self, $line) = @_; |
831
|
39
|
|
|
|
|
116
|
$line =~ s/^\s+//; |
832
|
39
|
|
|
|
|
188
|
$line =~ s/\s+$//; |
833
|
39
|
50
|
|
|
|
103
|
return ( tokens => [] ) if $line eq ""; |
834
|
|
|
|
|
|
|
|
835
|
39
|
|
|
|
|
73
|
my $orig; |
836
|
|
|
|
|
|
|
my %res; |
837
|
39
|
50
|
|
|
|
125
|
if ( $line !~ /\|/ ) { |
838
|
0
|
|
|
|
|
0
|
$res{margin} = { $self->cdecompose($line), orig => $line }; |
839
|
0
|
|
|
|
|
0
|
$line = ""; |
840
|
|
|
|
|
|
|
} |
841
|
|
|
|
|
|
|
else { |
842
|
39
|
50
|
|
|
|
277
|
if ( $line =~ /(.*\|\S*)\s([^\|]*)$/ ) { |
843
|
0
|
|
|
|
|
0
|
$line = $1; |
844
|
0
|
|
|
|
|
0
|
$res{comment} = { $self->cdecompose($2), orig => $2 }; |
845
|
0
|
0
|
|
|
|
0
|
do_warn( "No margin cell for trailing comment" ) |
846
|
|
|
|
|
|
|
unless $grid_cells->[2]; |
847
|
|
|
|
|
|
|
} |
848
|
39
|
50
|
|
|
|
140
|
if ( $line =~ /^([^|]+?)\s*(\|.*)/ ) { |
849
|
0
|
|
|
|
|
0
|
$line = $2; |
850
|
0
|
|
|
|
|
0
|
$res{margin} = { $self->cdecompose($1), orig => $1 }; |
851
|
0
|
0
|
|
|
|
0
|
do_warn( "No cell for margin text" ) |
852
|
|
|
|
|
|
|
unless $grid_cells->[1]; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
} |
855
|
|
|
|
|
|
|
|
856
|
39
|
|
|
|
|
65
|
my @tokens; |
857
|
39
|
|
|
|
|
218
|
my @t = split( ' ', $line ); |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
# Unfortunately, gets split too. |
860
|
39
|
|
|
|
|
105
|
while ( @t ) { |
861
|
663
|
|
|
|
|
953
|
$_ = shift(@t); |
862
|
663
|
|
|
|
|
1103
|
push( @tokens, $_ ); |
863
|
663
|
50
|
|
|
|
1328
|
if ( /\
|
864
|
0
|
|
|
|
|
0
|
while ( @t ) { |
865
|
0
|
|
|
|
|
0
|
$_ = shift(@t); |
866
|
0
|
|
|
|
|
0
|
$tokens[-1] .= " " . $_; |
867
|
0
|
0
|
|
|
|
0
|
last if /\<\/span>/; |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
} |
871
|
|
|
|
|
|
|
|
872
|
39
|
|
|
|
|
79
|
my $nbt = 0; # non-bar tokens |
873
|
39
|
|
|
|
|
82
|
foreach ( @tokens ) { |
874
|
663
|
50
|
33
|
|
|
5290
|
if ( $_ eq "|:" || $_ eq "{" ) { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
875
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "bar" }; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
elsif ( /^\|(\d+)(>?)$/ ) { |
878
|
0
|
|
|
|
|
0
|
$_ = { symbol => '|', volta => $1, class => "bar" }; |
879
|
0
|
0
|
|
|
|
0
|
$_->{align} = 1 if $2; |
880
|
|
|
|
|
|
|
} |
881
|
|
|
|
|
|
|
elsif ( $_ eq ":|" || $_ eq "}" ) { |
882
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "bar" }; |
883
|
|
|
|
|
|
|
} |
884
|
|
|
|
|
|
|
elsif ( $_ eq ":|:" || $_ eq "}{" ) { |
885
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "bar" }; |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
elsif ( $_ eq "|" ) { |
888
|
149
|
|
|
|
|
476
|
$_ = { symbol => $_, class => "bar" }; |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
elsif ( $_ eq "||" ) { |
891
|
2
|
|
|
|
|
10
|
$_ = { symbol => $_, class => "bar" }; |
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
elsif ( $_ eq "|." ) { |
894
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "bar" }; |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
elsif ( $_ eq "%" ) { |
897
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "repeat1" }; |
898
|
|
|
|
|
|
|
} |
899
|
|
|
|
|
|
|
elsif ( $_ eq '%%' ) { |
900
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "repeat2" }; |
901
|
|
|
|
|
|
|
} |
902
|
|
|
|
|
|
|
elsif ( $_ eq "/" ) { |
903
|
0
|
|
|
|
|
0
|
$_ = { symbol => $_, class => "slash" }; |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
elsif ( $_ eq "." ) { |
906
|
395
|
|
|
|
|
1050
|
$_ = { symbol => $_, class => "space" }; |
907
|
395
|
|
|
|
|
595
|
$nbt++; |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
else { |
910
|
|
|
|
|
|
|
# Multiple chords in a cell? |
911
|
117
|
|
|
|
|
344
|
my @a = split( /~/, $_, -1 ); |
912
|
117
|
100
|
|
|
|
245
|
if ( @a == 1) { |
913
|
|
|
|
|
|
|
# Normal case, single chord. |
914
|
116
|
|
|
|
|
277
|
$_ = { chord => $self->chord($_), class => "chord" }; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
else { |
917
|
|
|
|
|
|
|
# Multiple chords. |
918
|
|
|
|
|
|
|
$_ = { chords => |
919
|
1
|
50
|
33
|
|
|
4
|
[ map { ( $_ eq '.' || $_ eq '' ) |
|
2
|
50
|
|
|
|
13
|
|
920
|
|
|
|
|
|
|
? '' |
921
|
|
|
|
|
|
|
: $_ eq "/" |
922
|
|
|
|
|
|
|
? "/" |
923
|
|
|
|
|
|
|
: $self->chord($_) } @a ], |
924
|
|
|
|
|
|
|
class => "chords" }; |
925
|
|
|
|
|
|
|
} |
926
|
117
|
|
|
|
|
294
|
$nbt++; |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
} |
929
|
39
|
50
|
|
|
|
116
|
if ( $nbt > $grid_cells->[0] ) { |
930
|
0
|
|
|
|
|
0
|
do_warn( "Too few cells for grid content" ); |
931
|
|
|
|
|
|
|
} |
932
|
39
|
|
|
|
|
208
|
return ( tokens => \@tokens, %res ); |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
################ Parsing directives ################ |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
my @directives = qw( |
938
|
|
|
|
|
|
|
chord |
939
|
|
|
|
|
|
|
chordcolour |
940
|
|
|
|
|
|
|
chordfont |
941
|
|
|
|
|
|
|
chordsize |
942
|
|
|
|
|
|
|
chorus |
943
|
|
|
|
|
|
|
column_break |
944
|
|
|
|
|
|
|
columns |
945
|
|
|
|
|
|
|
comment |
946
|
|
|
|
|
|
|
comment_box |
947
|
|
|
|
|
|
|
comment_italic |
948
|
|
|
|
|
|
|
define |
949
|
|
|
|
|
|
|
end_of_bridge |
950
|
|
|
|
|
|
|
end_of_chorus |
951
|
|
|
|
|
|
|
end_of_grid |
952
|
|
|
|
|
|
|
end_of_tab |
953
|
|
|
|
|
|
|
end_of_verse |
954
|
|
|
|
|
|
|
footersize |
955
|
|
|
|
|
|
|
footercolour |
956
|
|
|
|
|
|
|
footerfont |
957
|
|
|
|
|
|
|
grid |
958
|
|
|
|
|
|
|
highlight |
959
|
|
|
|
|
|
|
image |
960
|
|
|
|
|
|
|
meta |
961
|
|
|
|
|
|
|
new_page |
962
|
|
|
|
|
|
|
new_physical_page |
963
|
|
|
|
|
|
|
new_song |
964
|
|
|
|
|
|
|
no_grid |
965
|
|
|
|
|
|
|
pagetype |
966
|
|
|
|
|
|
|
start_of_bridge |
967
|
|
|
|
|
|
|
start_of_chorus |
968
|
|
|
|
|
|
|
start_of_grid |
969
|
|
|
|
|
|
|
start_of_tab |
970
|
|
|
|
|
|
|
start_of_verse |
971
|
|
|
|
|
|
|
subtitle |
972
|
|
|
|
|
|
|
tabcolour |
973
|
|
|
|
|
|
|
tabfont |
974
|
|
|
|
|
|
|
tabsize |
975
|
|
|
|
|
|
|
textcolour |
976
|
|
|
|
|
|
|
textfont |
977
|
|
|
|
|
|
|
textsize |
978
|
|
|
|
|
|
|
title |
979
|
|
|
|
|
|
|
titlesize |
980
|
|
|
|
|
|
|
titlecolour |
981
|
|
|
|
|
|
|
titlefont |
982
|
|
|
|
|
|
|
titles |
983
|
|
|
|
|
|
|
tocsize |
984
|
|
|
|
|
|
|
toccolour |
985
|
|
|
|
|
|
|
tocfont |
986
|
|
|
|
|
|
|
transpose |
987
|
|
|
|
|
|
|
); |
988
|
|
|
|
|
|
|
# NOTE: Flex: start_of_... end_of_... x_... |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
my %abbrevs = ( |
991
|
|
|
|
|
|
|
c => "comment", |
992
|
|
|
|
|
|
|
cb => "comment_box", |
993
|
|
|
|
|
|
|
cf => "chordfont", |
994
|
|
|
|
|
|
|
ci => "comment_italic", |
995
|
|
|
|
|
|
|
colb => "column_break", |
996
|
|
|
|
|
|
|
cs => "chordsize", |
997
|
|
|
|
|
|
|
grid => "diagrams", # not really an abbrev |
998
|
|
|
|
|
|
|
eob => "end_of_bridge", |
999
|
|
|
|
|
|
|
eoc => "end_of_chorus", |
1000
|
|
|
|
|
|
|
eot => "end_of_tab", |
1001
|
|
|
|
|
|
|
eov => "end_of_verse", |
1002
|
|
|
|
|
|
|
g => "diagrams", |
1003
|
|
|
|
|
|
|
highlight => "comment", # not really an abbrev |
1004
|
|
|
|
|
|
|
ng => "no_grid", |
1005
|
|
|
|
|
|
|
np => "new_page", |
1006
|
|
|
|
|
|
|
npp => "new_physical_page", |
1007
|
|
|
|
|
|
|
ns => "new_song", |
1008
|
|
|
|
|
|
|
sob => "start_of_bridge", |
1009
|
|
|
|
|
|
|
soc => "start_of_chorus", |
1010
|
|
|
|
|
|
|
sot => "start_of_tab", |
1011
|
|
|
|
|
|
|
sov => "start_of_verse", |
1012
|
|
|
|
|
|
|
st => "subtitle", |
1013
|
|
|
|
|
|
|
t => "title", |
1014
|
|
|
|
|
|
|
tf => "textfont", |
1015
|
|
|
|
|
|
|
ts => "textsize", |
1016
|
|
|
|
|
|
|
); |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
my $dirpat; |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
sub parse_directive { |
1021
|
999
|
|
|
999
|
0
|
2048
|
my ( $self, $d ) = @_; |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
# Pattern for all recognized directives. |
1024
|
999
|
100
|
|
|
|
2343
|
unless ( $dirpat ) { |
1025
|
|
|
|
|
|
|
$dirpat = |
1026
|
|
|
|
|
|
|
'(?:' . |
1027
|
|
|
|
|
|
|
join( '|', @directives, |
1028
|
57
|
|
|
|
|
207
|
@{$config->{metadata}->{keys}}, |
|
57
|
|
|
|
|
1563
|
|
1029
|
|
|
|
|
|
|
keys(%abbrevs), |
1030
|
|
|
|
|
|
|
'(?:start|end)_of_\w+' ) . |
1031
|
|
|
|
|
|
|
')'; |
1032
|
57
|
|
|
|
|
19377
|
$dirpat = qr/$dirpat/; |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
# $d is the complete directive line, without leading/trailing { }. |
1036
|
999
|
|
|
|
|
3409
|
$d =~ s/^[: ]+//; |
1037
|
999
|
|
|
|
|
3178
|
$d =~ s/\s+$//; |
1038
|
999
|
|
|
|
|
2698
|
my $dir = lc($d); |
1039
|
999
|
|
|
|
|
7251
|
my $arg = ""; |
1040
|
999
|
100
|
|
|
|
4043
|
if ( $d =~ /^(.*?)[: ]\s*(.*)/ ) { |
1041
|
787
|
|
|
|
|
2945
|
( $dir, $arg ) = ( lc($1), $2 ); |
1042
|
|
|
|
|
|
|
} |
1043
|
999
|
|
|
|
|
2226
|
$dir =~ s/[: ]+$//; |
1044
|
|
|
|
|
|
|
# $dir is the lowcase directive name. |
1045
|
|
|
|
|
|
|
# $arg is the rest, if any. |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
# Check for xxx-yyy selectors. |
1048
|
999
|
100
|
|
|
|
24405
|
if ( $dir =~ /^($dirpat)-(.+)$/ ) { |
1049
|
11
|
|
66
|
|
|
69
|
$dir = $abbrevs{$1} // $1; |
1050
|
11
|
|
|
|
|
24
|
my $sel = $2; |
1051
|
11
|
|
|
|
|
44
|
my $negate = $sel =~ s/\!$//; |
1052
|
|
|
|
|
|
|
$sel = ( $sel eq lc($config->{instrument}->{type}) ) |
1053
|
|
|
|
|
|
|
|| |
1054
|
|
|
|
|
|
|
( $sel eq lc($config->{user}->{name}) |
1055
|
|
|
|
|
|
|
|| |
1056
|
11
|
|
66
|
|
|
99
|
( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) ) |
1057
|
|
|
|
|
|
|
); |
1058
|
11
|
100
|
|
|
|
28
|
$sel = !$sel if $negate; |
1059
|
11
|
100
|
|
|
|
28
|
unless ( $sel ) { |
1060
|
4
|
100
|
|
|
|
16
|
if ( $dir =~ /^start_of_/ ) { |
1061
|
2
|
|
|
|
|
17
|
return { name => $dir, arg => $arg, omit => 2 }; |
1062
|
|
|
|
|
|
|
} |
1063
|
|
|
|
|
|
|
else { |
1064
|
2
|
|
|
|
|
10
|
return { name => $dir, arg => $arg, omit => 1 }; |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
} |
1067
|
|
|
|
|
|
|
} |
1068
|
|
|
|
|
|
|
else { |
1069
|
988
|
|
66
|
|
|
4709
|
$dir = $abbrevs{$dir} // $dir; |
1070
|
|
|
|
|
|
|
} |
1071
|
|
|
|
|
|
|
|
1072
|
995
|
|
|
|
|
5267
|
return { name => $dir, arg => $arg, omit => 0 } |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
sub directive { |
1076
|
997
|
|
|
997
|
0
|
2882
|
my ( $self, $d ) = @_; |
1077
|
|
|
|
|
|
|
|
1078
|
997
|
|
|
|
|
4985
|
my $dd = $self->parse_directive($d); |
1079
|
997
|
100
|
|
|
|
2862
|
return 1 if $dd->{omit} == 1; |
1080
|
|
|
|
|
|
|
|
1081
|
995
|
|
|
|
|
1847
|
my $arg = $dd->{arg}; |
1082
|
995
|
100
|
|
|
|
2304
|
if ( $arg ne "" ) { |
1083
|
784
|
|
|
|
|
2885
|
$arg = fmt_subst( $self, $arg ); |
1084
|
784
|
50
|
|
|
|
93427
|
return 1 if $arg !~ /\S/; |
1085
|
|
|
|
|
|
|
} |
1086
|
995
|
|
|
|
|
2503
|
my $dir = $dd->{name}; |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
# Context flags. |
1089
|
|
|
|
|
|
|
|
1090
|
995
|
100
|
|
|
|
2753
|
if ( $dir =~ /^start_of_(\w+)$/ ) { |
1091
|
76
|
50
|
|
|
|
262
|
do_warn("Already in " . ucfirst($in_context) . " context\n") |
1092
|
|
|
|
|
|
|
if $in_context; |
1093
|
76
|
|
|
|
|
224
|
$in_context = $1; |
1094
|
76
|
100
|
|
|
|
252
|
if ( $dd->{omit} ) { |
1095
|
2
|
|
|
|
|
161
|
$skip_context = 1; |
1096
|
|
|
|
|
|
|
# warn("Skipping context: $in_context\n"); |
1097
|
2
|
|
|
|
|
9
|
return 1; |
1098
|
|
|
|
|
|
|
} |
1099
|
74
|
100
|
|
|
|
282
|
@chorus = (), $chorus_xpose = $chorus_xpose_dir = 0 |
1100
|
|
|
|
|
|
|
if $in_context eq "chorus"; |
1101
|
74
|
100
|
66
|
|
|
304
|
if ( $in_context eq "grid" ) { |
|
|
100
|
|
|
|
|
|
1102
|
25
|
100
|
|
|
|
172
|
if ( $arg eq "" ) { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1103
|
3
|
|
|
|
|
10
|
$self->add( type => "set", |
1104
|
|
|
|
|
|
|
name => "gridparams", |
1105
|
|
|
|
|
|
|
value => $grid_arg ); |
1106
|
|
|
|
|
|
|
} |
1107
|
|
|
|
|
|
|
elsif ( $arg =~ m/^ |
1108
|
|
|
|
|
|
|
(?: (\d+) \+)? |
1109
|
|
|
|
|
|
|
(\d+) (?: x (\d+) )? |
1110
|
|
|
|
|
|
|
(?:\+ (\d+) )? |
1111
|
|
|
|
|
|
|
(?:[:\s+] (.*)? )? $/x ) { |
1112
|
22
|
50
|
|
|
|
74
|
do_warn("Invalid grid params: $arg (must be non-zero)"), return |
1113
|
|
|
|
|
|
|
unless $2; |
1114
|
22
|
|
50
|
|
|
220
|
$grid_arg = [ $2, $3//1, $1//0, $4//0 ]; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
1115
|
22
|
|
50
|
|
|
192
|
$self->add( type => "set", |
1116
|
|
|
|
|
|
|
name => "gridparams", |
1117
|
|
|
|
|
|
|
value => [ @$grid_arg, $5||"" ] ); |
1118
|
22
|
50
|
50
|
|
|
116
|
push( @labels, $5 ) if length($5||""); |
1119
|
|
|
|
|
|
|
} |
1120
|
|
|
|
|
|
|
elsif ( $arg ne "" ) { |
1121
|
0
|
|
|
|
|
0
|
$self->add( type => "set", |
1122
|
|
|
|
|
|
|
name => "gridparams", |
1123
|
|
|
|
|
|
|
value => [ @$grid_arg, $arg ] ); |
1124
|
0
|
|
|
|
|
0
|
push( @labels, $arg ); |
1125
|
|
|
|
|
|
|
} |
1126
|
25
|
|
|
|
|
123
|
$grid_cells = [ $grid_arg->[0] * $grid_arg->[1], |
1127
|
|
|
|
|
|
|
$grid_arg->[2], $grid_arg->[3] ]; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
elsif ( $arg && $arg ne "" ) { |
1130
|
2
|
|
|
|
|
10
|
$self->add( type => "set", |
1131
|
|
|
|
|
|
|
name => "label", |
1132
|
|
|
|
|
|
|
value => $arg ); |
1133
|
|
|
|
|
|
|
push( @labels, $arg ) |
1134
|
2
|
50
|
33
|
|
|
10
|
unless $in_context eq "chorus" && !$config->{settings}->{choruslabels}; |
1135
|
|
|
|
|
|
|
} |
1136
|
|
|
|
|
|
|
else { |
1137
|
47
|
50
|
|
|
|
118
|
do_warn("Garbage in start_of_$1: $arg (ignored)\n") |
1138
|
|
|
|
|
|
|
if $arg; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
# Enabling this always would allow [^] to recall anyway. |
1142
|
|
|
|
|
|
|
# Feature? |
1143
|
74
|
100
|
|
|
|
285
|
if ( $config->{settings}->{memorize} ) { |
1144
|
7
|
|
100
|
|
|
29
|
$memchords = $memchords{$in_context} //= []; |
1145
|
7
|
|
|
|
|
15
|
$memcrdinx = 0; |
1146
|
7
|
|
|
|
|
10
|
$memorizing = 0; |
1147
|
|
|
|
|
|
|
} |
1148
|
74
|
|
|
|
|
768
|
return 1; |
1149
|
|
|
|
|
|
|
} |
1150
|
919
|
100
|
|
|
|
2488
|
if ( $dir =~ /^end_of_(\w+)$/ ) { |
1151
|
74
|
50
|
|
|
|
324
|
do_warn("Not in " . ucfirst($1) . " context\n") |
1152
|
|
|
|
|
|
|
unless $in_context eq $1; |
1153
|
74
|
|
|
|
|
308
|
$self->add( type => "set", |
1154
|
|
|
|
|
|
|
name => "context", |
1155
|
|
|
|
|
|
|
value => $def_context ); |
1156
|
74
|
|
|
|
|
159
|
$in_context = $def_context; |
1157
|
74
|
|
|
|
|
173
|
undef $memchords; |
1158
|
74
|
|
|
|
|
295
|
return 1; |
1159
|
|
|
|
|
|
|
} |
1160
|
845
|
100
|
|
|
|
2453
|
if ( $dir =~ /^chorus$/i ) { |
1161
|
30
|
50
|
|
|
|
94
|
if ( $in_context ) { |
1162
|
0
|
|
|
|
|
0
|
do_warn("{chorus} encountered while in $in_context context -- ignored\n"); |
1163
|
0
|
|
|
|
|
0
|
return 1; |
1164
|
|
|
|
|
|
|
} |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
# Clone the chorus so we can modify the label, if required. |
1167
|
30
|
100
|
|
|
|
4843
|
my $chorus = @chorus ? dclone(\@chorus) : []; |
1168
|
|
|
|
|
|
|
|
1169
|
30
|
50
|
66
|
|
|
30057
|
if ( @$chorus && $arg && $arg ne "" ) { |
|
|
|
33
|
|
|
|
|
1170
|
0
|
0
|
0
|
|
|
0
|
if ( $chorus->[0]->{type} eq "set" && $chorus->[0]->{name} eq "label" ) { |
1171
|
0
|
|
|
|
|
0
|
$chorus->[0]->{value} = $arg; |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
else { |
1174
|
0
|
|
|
|
|
0
|
unshift( @$chorus, |
1175
|
|
|
|
|
|
|
{ type => "set", |
1176
|
|
|
|
|
|
|
name => "label", |
1177
|
|
|
|
|
|
|
value => $arg, |
1178
|
|
|
|
|
|
|
context => "chorus", |
1179
|
|
|
|
|
|
|
} ); |
1180
|
|
|
|
|
|
|
} |
1181
|
|
|
|
|
|
|
push( @labels, $arg ) |
1182
|
0
|
0
|
|
|
|
0
|
if $config->{settings}->{choruslabels}; |
1183
|
|
|
|
|
|
|
} |
1184
|
|
|
|
|
|
|
|
1185
|
30
|
100
|
|
|
|
111
|
if ( $chorus_xpose != ( my $xp = $xpose ) ) { |
1186
|
17
|
|
|
|
|
40
|
$xp -= $chorus_xpose; |
1187
|
17
|
|
|
|
|
58
|
for ( @$chorus ) { |
1188
|
32
|
100
|
|
|
|
119
|
if ( $_->{type} eq "songline" ) { |
1189
|
16
|
|
|
|
|
28
|
for ( @{ $_->{chords} } ) { |
|
16
|
|
|
|
|
53
|
|
1190
|
61
|
100
|
|
|
|
184
|
next if $_ eq ''; |
1191
|
46
|
|
|
|
|
164
|
my $info = $self->{chordsinfo}->{$_->key}; |
1192
|
46
|
50
|
|
|
|
161
|
next if $info->is_annotation; |
1193
|
46
|
50
|
|
|
|
188
|
$info = $info->transpose($xp, $xpose <=> 0) if $xp; |
1194
|
46
|
|
|
|
|
149
|
$info = $info->new($info); |
1195
|
46
|
|
|
|
|
189
|
$_ = ChordPro::Chords::Appearance->new |
1196
|
|
|
|
|
|
|
( key => $self->add_chord($info), |
1197
|
|
|
|
|
|
|
info => $info, |
1198
|
|
|
|
|
|
|
maybe format => $_->format |
1199
|
|
|
|
|
|
|
); |
1200
|
|
|
|
|
|
|
} |
1201
|
|
|
|
|
|
|
} |
1202
|
|
|
|
|
|
|
} |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
|
1205
|
30
|
100
|
|
|
|
171
|
$self->add( type => "rechorus", |
1206
|
|
|
|
|
|
|
@$chorus |
1207
|
|
|
|
|
|
|
? ( "chorus" => $chorus ) |
1208
|
|
|
|
|
|
|
: (), |
1209
|
|
|
|
|
|
|
); |
1210
|
30
|
|
|
|
|
183
|
return 1; |
1211
|
|
|
|
|
|
|
} |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
# Song settings. |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
# Breaks. |
1216
|
|
|
|
|
|
|
|
1217
|
815
|
100
|
|
|
|
2011
|
if ( $dir eq "column_break" ) { |
1218
|
13
|
|
|
|
|
58
|
$self->add( type => "colb" ); |
1219
|
13
|
|
|
|
|
56
|
return 1; |
1220
|
|
|
|
|
|
|
} |
1221
|
|
|
|
|
|
|
|
1222
|
802
|
100
|
100
|
|
|
3381
|
if ( $dir eq "new_page" || $dir eq "new_physical_page" ) { |
1223
|
16
|
|
|
|
|
69
|
$self->add( type => "newpage" ); |
1224
|
16
|
|
|
|
|
74
|
return 1; |
1225
|
|
|
|
|
|
|
} |
1226
|
|
|
|
|
|
|
|
1227
|
786
|
50
|
|
|
|
1794
|
if ( $dir eq "new_song" ) { |
1228
|
0
|
|
|
|
|
0
|
die("FATAL - cannot start a new song now\n"); |
1229
|
|
|
|
|
|
|
} |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
# Comments. Strictly speaking they do not belong here. |
1232
|
|
|
|
|
|
|
|
1233
|
786
|
100
|
|
|
|
2341
|
if ( $dir =~ /^comment(_italic|_box)?$/ ) { |
1234
|
124
|
|
|
|
|
557
|
my %res = $self->cdecompose($arg); |
1235
|
124
|
|
|
|
|
424
|
$res{orig} = $dd->{arg}; |
1236
|
|
|
|
|
|
|
$self->add( type => $dir, %res ) |
1237
|
124
|
50
|
66
|
|
|
1284
|
unless exists($res{text}) && $res{text} =~ /^[ \t]*$/; |
1238
|
124
|
|
|
|
|
601
|
return 1; |
1239
|
|
|
|
|
|
|
} |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
# Images. |
1242
|
662
|
100
|
|
|
|
1503
|
if ( $dir eq "image" ) { |
1243
|
3
|
|
|
|
|
19
|
my $res = parse_kv($arg); |
1244
|
3
|
|
|
|
|
10
|
my $uri; |
1245
|
|
|
|
|
|
|
my $id; |
1246
|
3
|
|
|
|
|
0
|
my %opts; |
1247
|
3
|
|
|
|
|
21
|
while ( my($k,$v) = each(%$res) ) { |
1248
|
9
|
100
|
66
|
|
|
136
|
if ( $k =~ /^(title)$/i && $v ne "" ) { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
50
|
66
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1249
|
1
|
|
|
|
|
5
|
$opts{lc($k)} = $v; |
1250
|
|
|
|
|
|
|
} |
1251
|
|
|
|
|
|
|
elsif ( $k =~ /^(border|spread|center)$/i && $v =~ /^(\d+)$/ ) { |
1252
|
2
|
|
|
|
|
7
|
$opts{lc($k)} = $v; |
1253
|
|
|
|
|
|
|
} |
1254
|
|
|
|
|
|
|
elsif ( $k =~ /^(width|height)$/i && $v =~ /^(\d+(?:\.\d+)?\%?)$/ ) { |
1255
|
2
|
|
|
|
|
11
|
$opts{lc($k)} = $v; |
1256
|
|
|
|
|
|
|
} |
1257
|
|
|
|
|
|
|
elsif ( $k =~ /^(x|y)$/i && $v =~ /^([-+]?\d+(?:\.\d+)?\%?)$/ ) { |
1258
|
0
|
|
|
|
|
0
|
$opts{lc($k)} = $v; |
1259
|
|
|
|
|
|
|
} |
1260
|
|
|
|
|
|
|
elsif ( $k =~ /^(scale)$/ && $v =~ /^(\d+(?:\.\d+)?)(%)?$/ ) { |
1261
|
1
|
50
|
|
|
|
8
|
$opts{lc($k)} = $2 ? $1/100 : $1; |
1262
|
|
|
|
|
|
|
} |
1263
|
|
|
|
|
|
|
elsif ( $k =~ /^(center|border|spread)$/i ) { |
1264
|
0
|
|
|
|
|
0
|
$opts{lc($k)} = $v; |
1265
|
|
|
|
|
|
|
} |
1266
|
|
|
|
|
|
|
elsif ( $k =~ /^(src|uri)$/i && $v ne "" ) { |
1267
|
0
|
|
|
|
|
0
|
$uri = $v; |
1268
|
|
|
|
|
|
|
} |
1269
|
|
|
|
|
|
|
elsif ( $k =~ /^(id)$/i && $v ne "" ) { |
1270
|
1
|
|
|
|
|
6
|
$id = $v; |
1271
|
|
|
|
|
|
|
} |
1272
|
|
|
|
|
|
|
elsif ( $k =~ /^(anchor)$/i |
1273
|
|
|
|
|
|
|
&& $v =~ /^(paper|page|column|line)$/ ) { |
1274
|
0
|
|
|
|
|
0
|
$opts{lc($k)} = lc($v); |
1275
|
|
|
|
|
|
|
} |
1276
|
|
|
|
|
|
|
elsif ( $uri ) { |
1277
|
0
|
|
|
|
|
0
|
do_warn( "Unknown image attribute: $k\n" ); |
1278
|
0
|
|
|
|
|
0
|
next; |
1279
|
|
|
|
|
|
|
} |
1280
|
|
|
|
|
|
|
# Assume just an image file uri. |
1281
|
|
|
|
|
|
|
else { |
1282
|
2
|
|
|
|
|
8
|
$uri = $k; |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
} |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
# If the image name does not have a directory, look it up |
1287
|
|
|
|
|
|
|
# next to the song, and then in the images folder of the |
1288
|
|
|
|
|
|
|
# CHORDPRO_LIB. |
1289
|
3
|
100
|
66
|
|
|
19
|
if ( $uri && $uri !~ m;/\\; ) { # basename |
1290
|
79
|
|
|
79
|
|
896
|
use File::Basename qw(dirname); |
|
79
|
|
|
|
|
199
|
|
|
79
|
|
|
|
|
20821
|
|
1291
|
2
|
|
|
|
|
125
|
L: for ( dirname($diag->{file}) ) { |
1292
|
2
|
50
|
|
|
|
53
|
$uri = "$_/$uri", last if -s "$_/$uri"; |
1293
|
0
|
|
|
|
|
0
|
for ( ::rsc_or_file("images/$uri") ) { |
1294
|
0
|
0
|
|
|
|
0
|
last unless $_; |
1295
|
0
|
0
|
|
|
|
0
|
$uri = $_, last L if -s $_; |
1296
|
|
|
|
|
|
|
} |
1297
|
0
|
|
|
|
|
0
|
do_warn("Missing image for \"$uri\""); |
1298
|
|
|
|
|
|
|
} |
1299
|
|
|
|
|
|
|
} |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
# uri + id -> define asset |
1302
|
3
|
50
|
66
|
|
|
28
|
if ( $uri && $id ) { |
1303
|
|
|
|
|
|
|
# Define a new asset. |
1304
|
0
|
0
|
|
|
|
0
|
if ( %opts ) { |
1305
|
0
|
|
|
|
|
0
|
do_warn("Asset definition \"$id\" does not take attributes"); |
1306
|
0
|
|
|
|
|
0
|
return; |
1307
|
|
|
|
|
|
|
} |
1308
|
79
|
|
|
79
|
|
42555
|
use Image::Info; |
|
79
|
|
|
|
|
146779
|
|
|
79
|
|
|
|
|
666767
|
|
1309
|
0
|
|
|
|
|
0
|
open( my $fd, '<:raw', $uri ); |
1310
|
0
|
0
|
|
|
|
0
|
unless ( $fd ) { |
1311
|
0
|
|
|
|
|
0
|
do_warn("$uri: $!"); |
1312
|
0
|
|
|
|
|
0
|
return; |
1313
|
|
|
|
|
|
|
} |
1314
|
0
|
|
|
|
|
0
|
my $data = do { local $/; <$fd> }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1315
|
|
|
|
|
|
|
# Get info. |
1316
|
0
|
|
|
|
|
0
|
my $info = Image::Info::image_info(\$data); |
1317
|
0
|
0
|
|
|
|
0
|
if ( $info->{error} ) { |
1318
|
0
|
|
|
|
|
0
|
do_warn($info->{error}); |
1319
|
0
|
|
|
|
|
0
|
return; |
1320
|
|
|
|
|
|
|
} |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
# Store in assets. |
1323
|
0
|
|
0
|
|
|
0
|
$self->{assets} //= {}; |
1324
|
|
|
|
|
|
|
$self->{assets}->{$id} = |
1325
|
|
|
|
|
|
|
{ data => $data, type => $info->{file_ext}, |
1326
|
|
|
|
|
|
|
width => $info->{width}, height => $info->{height}, |
1327
|
0
|
|
|
|
|
0
|
}; |
1328
|
|
|
|
|
|
|
|
1329
|
0
|
0
|
|
|
|
0
|
if ( $config->{debug}->{images} ) { |
1330
|
0
|
|
|
|
|
0
|
warn("asset[$id] ", length($data), " bytes, ", |
1331
|
|
|
|
|
|
|
"width=$info->{width}, height=$info->{height}", |
1332
|
|
|
|
|
|
|
"\n"); |
1333
|
|
|
|
|
|
|
} |
1334
|
0
|
|
|
|
|
0
|
return 1; |
1335
|
|
|
|
|
|
|
} |
1336
|
|
|
|
|
|
|
|
1337
|
3
|
100
|
|
|
|
12
|
$uri = "id=$id" if $id; |
1338
|
3
|
50
|
|
|
|
9
|
unless ( $uri ) { |
1339
|
0
|
|
|
|
|
0
|
do_warn( "Missing image source\n" ); |
1340
|
0
|
|
|
|
|
0
|
return; |
1341
|
|
|
|
|
|
|
} |
1342
|
3
|
50
|
|
|
|
27
|
$self->add( type => $uri =~ /\.svg$/ ? "svg" : "image", |
1343
|
|
|
|
|
|
|
uri => $uri, |
1344
|
|
|
|
|
|
|
opts => \%opts ); |
1345
|
3
|
|
|
|
|
35
|
return 1; |
1346
|
|
|
|
|
|
|
} |
1347
|
|
|
|
|
|
|
|
1348
|
659
|
100
|
|
|
|
1633
|
if ( $dir eq "title" ) { |
1349
|
173
|
|
|
|
|
796
|
$self->{title} = $arg; |
1350
|
173
|
|
|
|
|
2010
|
push( @{ $self->{meta}->{title} }, $arg ); |
|
173
|
|
|
|
|
849
|
|
1351
|
173
|
|
|
|
|
885
|
return 1; |
1352
|
|
|
|
|
|
|
} |
1353
|
|
|
|
|
|
|
|
1354
|
486
|
100
|
|
|
|
1363
|
if ( $dir eq "subtitle" ) { |
1355
|
28
|
|
|
|
|
78
|
push( @{ $self->{subtitle} }, $arg ); |
|
28
|
|
|
|
|
137
|
|
1356
|
28
|
|
|
|
|
67
|
push( @{ $self->{meta}->{subtitle} }, $arg ); |
|
28
|
|
|
|
|
85
|
|
1357
|
28
|
|
|
|
|
141
|
return 1; |
1358
|
|
|
|
|
|
|
} |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
# Metadata extensions (legacy). Should use meta instead. |
1361
|
|
|
|
|
|
|
# Only accept the list from config. |
1362
|
458
|
100
|
|
5453
|
|
2436
|
if ( any { $_ eq $dir } @{ $config->{metadata}->{keys} } ) { |
|
5453
|
|
|
|
|
7588
|
|
|
458
|
|
|
|
|
2549
|
|
1363
|
225
|
|
|
|
|
693
|
$arg = "$dir $arg"; |
1364
|
225
|
|
|
|
|
495
|
$dir = "meta"; |
1365
|
|
|
|
|
|
|
} |
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
# Metadata. |
1368
|
458
|
100
|
|
|
|
2063
|
if ( $dir eq "meta" ) { |
1369
|
263
|
50
|
|
|
|
1422
|
if ( $arg =~ /([^ :]+)[ :]+(.*)/ ) { |
1370
|
263
|
|
|
|
|
920
|
my $key = lc $1; |
1371
|
263
|
|
|
|
|
787
|
my @vals = ( $2 ); |
1372
|
263
|
100
|
|
|
|
1155
|
if ( $config->{metadata}->{autosplit} ) { |
1373
|
256
|
|
|
|
|
767
|
@vals = map { s/s\+$//; $_ } |
|
256
|
|
|
|
|
1001
|
|
1374
|
256
|
|
|
|
|
5826
|
split( quotemeta($config->{metadata}->{separator}), $vals[0] ); |
1375
|
|
|
|
|
|
|
} |
1376
|
263
|
|
|
|
|
655
|
my $m = $self->{meta}; |
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
# User and instrument cannot be set here. |
1379
|
263
|
50
|
33
|
|
|
1205
|
if ( $key eq "user" || $key eq "instrument" ) { |
1380
|
0
|
|
|
|
|
0
|
do_warn("\"$key\" can be set from config only.\n"); |
1381
|
0
|
|
|
|
|
0
|
return 1; |
1382
|
|
|
|
|
|
|
} |
1383
|
|
|
|
|
|
|
|
1384
|
263
|
|
|
|
|
649
|
for my $val ( @vals ) { |
1385
|
|
|
|
|
|
|
|
1386
|
263
|
100
|
|
|
|
696
|
if ( $key eq "key" ) { |
1387
|
92
|
|
|
|
|
384
|
$val =~ s/[\[\]]//g; |
1388
|
92
|
|
|
|
|
407
|
my $info = $self->parse_chord($val); |
1389
|
92
|
|
|
|
|
323
|
my $name = $info->name; |
1390
|
92
|
|
|
|
|
287
|
my $act = $name; |
1391
|
|
|
|
|
|
|
|
1392
|
92
|
50
|
|
|
|
307
|
if ( $capo ) { |
1393
|
0
|
|
|
|
|
0
|
$act = $self->add_chord( $info->transpose($capo) ); |
1394
|
0
|
0
|
|
|
|
0
|
$name = $act if $decapo; |
1395
|
|
|
|
|
|
|
} |
1396
|
|
|
|
|
|
|
|
1397
|
92
|
|
|
|
|
182
|
push( @{ $m->{key} }, $name ); |
|
92
|
|
|
|
|
373
|
|
1398
|
92
|
|
|
|
|
313
|
$m->{key_actual} = [ $act ]; |
1399
|
|
|
|
|
|
|
# warn("XX key=$name act=$act capo=", |
1400
|
|
|
|
|
|
|
# $capo//""," decapo=$decapo\n"); |
1401
|
92
|
|
|
|
|
468
|
return 1; |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
|
1405
|
171
|
100
|
66
|
|
|
735
|
if ( $key eq "capo" ) { |
|
|
100
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
do_warn("Multiple capo settings may yield surprising results.") |
1407
|
16
|
100
|
|
|
|
64
|
if exists $m->{capo}; |
1408
|
|
|
|
|
|
|
|
1409
|
16
|
|
50
|
|
|
52
|
$capo = $val || undef; |
1410
|
16
|
50
|
33
|
|
|
140
|
if ( $capo && $m->{key} ) { |
1411
|
16
|
100
|
|
|
|
52
|
if ( $decapo ) { |
1412
|
|
|
|
|
|
|
my $key = $self->store_chord |
1413
|
4
|
|
|
|
|
30
|
($self->{chordsinfo}->{$m->{key}->[-1]} |
1414
|
|
|
|
|
|
|
->transpose($val)); |
1415
|
4
|
|
|
|
|
16
|
$m->{key}->[-1] = $key; |
1416
|
|
|
|
|
|
|
$key = $self->store_chord |
1417
|
4
|
|
|
|
|
33
|
($self->{chordsinfo}->{$m->{key}->[-1]} |
1418
|
|
|
|
|
|
|
->transpose($xpose)); |
1419
|
4
|
|
|
|
|
29
|
$m->{key_actual} = [ $key ]; |
1420
|
|
|
|
|
|
|
} |
1421
|
|
|
|
|
|
|
else { |
1422
|
12
|
|
|
|
|
120
|
my $act = $m->{key_actual}->[-1]; |
1423
|
12
|
|
|
|
|
37
|
$m->{key_from} = [ $act ]; |
1424
|
|
|
|
|
|
|
my $key = $self->store_chord |
1425
|
12
|
|
|
|
|
71
|
($self->{chordsinfo}->{$act}->transpose($val)); |
1426
|
12
|
|
|
|
|
73
|
$m->{key_actual} = [ $key ]; |
1427
|
|
|
|
|
|
|
} |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
} |
1430
|
|
|
|
|
|
|
|
1431
|
|
|
|
|
|
|
elsif ( $key eq "duration" && $val ) { |
1432
|
9
|
|
|
|
|
46
|
$val = duration($val); |
1433
|
|
|
|
|
|
|
} |
1434
|
|
|
|
|
|
|
|
1435
|
171
|
50
|
33
|
|
|
596
|
if ( $config->{metadata}->{strict} |
1436
|
1296
|
|
|
1296
|
|
2056
|
&& ! any { $_ eq $key } @{ $config->{metadata}->{keys} } ) { |
|
171
|
|
|
|
|
2237
|
|
1437
|
|
|
|
|
|
|
# Unknown, and strict. |
1438
|
|
|
|
|
|
|
do_warn("Unknown metadata item: $key") |
1439
|
0
|
0
|
|
|
|
0
|
if $config->{settings}->{strict}; |
1440
|
0
|
|
|
|
|
0
|
return; |
1441
|
|
|
|
|
|
|
} |
1442
|
|
|
|
|
|
|
|
1443
|
171
|
50
|
|
|
|
739
|
push( @{ $self->{meta}->{$key} }, $val ) if defined $val; |
|
171
|
|
|
|
|
864
|
|
1444
|
|
|
|
|
|
|
} |
1445
|
|
|
|
|
|
|
} |
1446
|
|
|
|
|
|
|
else { |
1447
|
|
|
|
|
|
|
do_warn("Incomplete meta directive: $d\n") |
1448
|
0
|
0
|
|
|
|
0
|
if $config->{settings}->{strict}; |
1449
|
0
|
|
|
|
|
0
|
return; |
1450
|
|
|
|
|
|
|
} |
1451
|
171
|
|
|
|
|
737
|
return 1; |
1452
|
|
|
|
|
|
|
} |
1453
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
# Song / Global settings. |
1455
|
|
|
|
|
|
|
|
1456
|
195
|
100
|
66
|
|
|
911
|
if ( $dir eq "titles" |
1457
|
|
|
|
|
|
|
&& $arg =~ /^(left|right|center|centre)$/i ) { |
1458
|
|
|
|
|
|
|
$self->{settings}->{titles} = |
1459
|
22
|
100
|
|
|
|
193
|
lc($1) eq "centre" ? "center" : lc($1); |
1460
|
22
|
|
|
|
|
118
|
return 1; |
1461
|
|
|
|
|
|
|
} |
1462
|
|
|
|
|
|
|
|
1463
|
173
|
100
|
66
|
|
|
727
|
if ( $dir eq "columns" |
1464
|
|
|
|
|
|
|
&& $arg =~ /^(\d+)$/ ) { |
1465
|
|
|
|
|
|
|
# If there a column specifications in the config, retain them |
1466
|
|
|
|
|
|
|
# if the number of columns match. |
1467
|
19
|
50
|
33
|
|
|
113
|
unless( ref($config->{settings}->{columns}) eq 'ARRAY' |
1468
|
0
|
|
|
|
|
0
|
&& $arg == @{$config->{settings}->{columns}} |
1469
|
|
|
|
|
|
|
) { |
1470
|
19
|
|
|
|
|
67
|
$self->{settings}->{columns} = $arg; |
1471
|
|
|
|
|
|
|
} |
1472
|
19
|
|
|
|
|
89
|
return 1; |
1473
|
|
|
|
|
|
|
} |
1474
|
|
|
|
|
|
|
|
1475
|
154
|
100
|
100
|
|
|
698
|
if ( $dir eq "pagetype" || $dir eq "pagesize" ) { |
1476
|
2
|
|
|
|
|
9
|
$self->{settings}->{papersize} = $arg; |
1477
|
2
|
|
|
|
|
8
|
return 1; |
1478
|
|
|
|
|
|
|
} |
1479
|
|
|
|
|
|
|
|
1480
|
152
|
100
|
|
|
|
369
|
if ( $dir eq "diagrams" ) { # AKA grid |
1481
|
2
|
100
|
|
|
|
9
|
if ( $arg ne "" ) { |
1482
|
1
|
|
|
|
|
6
|
$self->{settings}->{diagrams} = !!is_true($arg); |
1483
|
1
|
50
|
|
|
|
12
|
$self->{settings}->{diagrampos} = lc($arg) |
1484
|
|
|
|
|
|
|
if $arg =~ /^(right|bottom|top|below)$/i; |
1485
|
|
|
|
|
|
|
} |
1486
|
|
|
|
|
|
|
else { |
1487
|
1
|
|
|
|
|
7
|
$self->{settings}->{diagrams} = 1; |
1488
|
|
|
|
|
|
|
} |
1489
|
2
|
|
|
|
|
12
|
return 1; |
1490
|
|
|
|
|
|
|
} |
1491
|
150
|
100
|
|
|
|
358
|
if ( $dir eq "no_grid" ) { |
1492
|
3
|
|
|
|
|
18
|
$self->{settings}->{diagrams} = 0; |
1493
|
3
|
|
|
|
|
12
|
return 1; |
1494
|
|
|
|
|
|
|
} |
1495
|
|
|
|
|
|
|
|
1496
|
147
|
100
|
|
|
|
398
|
if ( $dir eq "transpose" ) { |
1497
|
53
|
|
100
|
|
|
317
|
$propstack{transpose} //= []; |
1498
|
|
|
|
|
|
|
|
1499
|
53
|
100
|
|
|
|
274
|
if ( $arg =~ /^([-+]?\d+)\s*$/ ) { |
1500
|
32
|
|
|
|
|
120
|
my $new = $1; |
1501
|
32
|
|
|
|
|
68
|
push( @{ $propstack{transpose} }, [ $xpose, $xpose_dir ] ); |
|
32
|
|
|
|
|
120
|
|
1502
|
32
|
|
|
|
|
197
|
my %a = ( type => "control", |
1503
|
|
|
|
|
|
|
name => "transpose", |
1504
|
|
|
|
|
|
|
previous => [ $xpose, $xpose_dir ] |
1505
|
|
|
|
|
|
|
); |
1506
|
32
|
|
|
|
|
110
|
$xpose += $new; |
1507
|
32
|
|
|
|
|
87
|
$xpose_dir = $new <=> 0; |
1508
|
32
|
|
|
|
|
93
|
my $m = $self->{meta}; |
1509
|
32
|
100
|
|
|
|
103
|
if ( $m->{key} ) { |
1510
|
22
|
|
|
|
|
66
|
my $key = $m->{key}->[-1]; |
1511
|
22
|
|
|
|
|
45
|
my $xp = $xpose; |
1512
|
22
|
100
|
|
|
|
65
|
$xp += $capo if $capo; |
1513
|
22
|
|
|
|
|
123
|
my $xpk = $self->{chordsinfo}->{$key}->transpose($xp); |
1514
|
22
|
|
|
|
|
110
|
$self->{chordsinfo}->{$xpk->name} = $xpk; |
1515
|
22
|
|
|
|
|
131
|
$m->{key_from} = [ $m->{key_actual}->[0] ]; |
1516
|
22
|
|
|
|
|
80
|
$m->{key_actual} = [ $xpk->name ]; |
1517
|
|
|
|
|
|
|
} |
1518
|
32
|
50
|
|
|
|
164
|
$self->add( %a, value => $xpose, dir => $xpose_dir ) |
1519
|
|
|
|
|
|
|
if $no_transpose; |
1520
|
|
|
|
|
|
|
} |
1521
|
|
|
|
|
|
|
else { |
1522
|
21
|
|
|
|
|
151
|
my %a = ( type => "control", |
1523
|
|
|
|
|
|
|
name => "transpose", |
1524
|
|
|
|
|
|
|
previous => [ $xpose, $xpose_dir ] |
1525
|
|
|
|
|
|
|
); |
1526
|
21
|
|
|
|
|
60
|
my $m = $self->{meta}; |
1527
|
21
|
|
|
|
|
52
|
my ( $new, $dir ); |
1528
|
21
|
50
|
|
|
|
41
|
if ( @{ $propstack{transpose} } ) { |
|
21
|
|
|
|
|
65
|
|
1529
|
21
|
|
|
|
|
42
|
( $new, $dir ) = @{ pop( @{ $propstack{transpose} } ) }; |
|
21
|
|
|
|
|
39
|
|
|
21
|
|
|
|
|
86
|
|
1530
|
|
|
|
|
|
|
} |
1531
|
|
|
|
|
|
|
else { |
1532
|
0
|
|
|
|
|
0
|
$new = 0; |
1533
|
0
|
|
|
|
|
0
|
$dir = $config->{settings}->{transpose} <=> 0; |
1534
|
|
|
|
|
|
|
} |
1535
|
21
|
|
|
|
|
65
|
$xpose = $new; |
1536
|
21
|
|
|
|
|
44
|
$xpose_dir = $dir; |
1537
|
21
|
100
|
|
|
|
78
|
if ( $m->{key} ) { |
1538
|
15
|
|
|
|
|
57
|
$m->{key_from} = [ $m->{key_actual}->[0] ]; |
1539
|
15
|
|
|
|
|
29
|
my $xp = $xpose; |
1540
|
15
|
50
|
66
|
|
|
78
|
$xp += $capo if $capo && $decapo; |
1541
|
|
|
|
|
|
|
$m->{key_actual} = |
1542
|
15
|
|
|
|
|
118
|
[ $self->{chordsinfo}->{$m->{key}->[-1]}->transpose($xp)->name ]; |
1543
|
|
|
|
|
|
|
} |
1544
|
21
|
100
|
|
|
|
180
|
if ( !@{ $propstack{transpose} } ) { |
|
21
|
|
|
|
|
85
|
|
1545
|
12
|
|
|
|
|
73
|
delete $m->{$_} for qw( key_from ); |
1546
|
|
|
|
|
|
|
} |
1547
|
21
|
50
|
|
|
|
101
|
$self->add( %a, value => $xpose, dir => $dir ) |
1548
|
|
|
|
|
|
|
if $no_transpose; |
1549
|
|
|
|
|
|
|
} |
1550
|
53
|
|
|
|
|
257
|
return 1; |
1551
|
|
|
|
|
|
|
} |
1552
|
|
|
|
|
|
|
|
1553
|
|
|
|
|
|
|
# More private hacks. |
1554
|
94
|
50
|
33
|
|
|
575
|
if ( !$options->{reference} && $d =~ /^([-+])([-\w.]+)$/i ) { |
1555
|
0
|
0
|
|
|
|
0
|
if ( $2 eq "dumpmeta" ) { |
1556
|
0
|
|
|
|
|
0
|
warn(::dump($self->{meta})); |
1557
|
|
|
|
|
|
|
} |
1558
|
0
|
0
|
|
|
|
0
|
$self->add( type => "set", |
1559
|
|
|
|
|
|
|
name => $2, |
1560
|
|
|
|
|
|
|
value => $1 eq "+" ? 1 : 0, |
1561
|
|
|
|
|
|
|
); |
1562
|
0
|
|
|
|
|
0
|
return 1; |
1563
|
|
|
|
|
|
|
} |
1564
|
|
|
|
|
|
|
|
1565
|
94
|
100
|
66
|
|
|
514
|
if ( !$options->{reference} && $dir =~ /^\+([-\w.]+(?:\.[<>])?)$/ ) { |
1566
|
11
|
|
|
|
|
67
|
$self->add( type => "set", |
1567
|
|
|
|
|
|
|
name => $1, |
1568
|
|
|
|
|
|
|
value => $arg, |
1569
|
|
|
|
|
|
|
); |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
# THIS IS BASICALLY A COPY OF THE CODE IN Config.pm. |
1572
|
|
|
|
|
|
|
# TODO: GENERALIZE. |
1573
|
11
|
|
|
|
|
27
|
my $ccfg = {}; |
1574
|
11
|
|
|
|
|
85
|
my @k = split( /[:.]/, $1 ); |
1575
|
11
|
|
|
|
|
31
|
my $c = \$ccfg; # new |
1576
|
11
|
|
|
|
|
25
|
my $o = $config; # current |
1577
|
11
|
|
|
|
|
28
|
my $lk = pop(@k); # last key |
1578
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
# Step through the keys. |
1580
|
11
|
|
|
|
|
31
|
foreach ( @k ) { |
1581
|
17
|
|
|
|
|
58
|
$c = \($$c->{$_}); |
1582
|
17
|
|
|
|
|
48
|
$o = $o->{$_}; |
1583
|
|
|
|
|
|
|
} |
1584
|
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
# Turn hash.array into hash.array.> (append). |
1586
|
11
|
50
|
33
|
|
|
103
|
if ( ref($o) eq 'HASH' && ref($o->{$lk}) eq 'ARRAY' ) { |
1587
|
0
|
|
|
|
|
0
|
$c = \($$c->{$lk}); |
1588
|
0
|
|
|
|
|
0
|
$o = $o->{$lk}; |
1589
|
0
|
|
|
|
|
0
|
$lk = '>'; |
1590
|
|
|
|
|
|
|
} |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
# Final key. Merge array if so. |
1593
|
11
|
50
|
33
|
|
|
117
|
if ( ( $lk =~ /^\d+$/ || $lk eq '>' || $lk eq '<' ) |
|
|
|
33
|
|
|
|
|
1594
|
|
|
|
|
|
|
&& ref($o) eq 'ARRAY' ) { |
1595
|
0
|
0
|
|
|
|
0
|
unless ( ref($$c) eq 'ARRAY' ) { |
1596
|
|
|
|
|
|
|
# Only copy orig values the first time. |
1597
|
0
|
|
|
|
|
0
|
$$c->[$_] = $o->[$_] for 0..scalar(@{$o})-1; |
|
0
|
|
|
|
|
0
|
|
1598
|
|
|
|
|
|
|
} |
1599
|
0
|
0
|
|
|
|
0
|
if ( $lk eq '>' ) { |
|
|
0
|
|
|
|
|
|
1600
|
0
|
|
|
|
|
0
|
push( @{$$c}, $arg ); |
|
0
|
|
|
|
|
0
|
|
1601
|
|
|
|
|
|
|
} |
1602
|
|
|
|
|
|
|
elsif ( $lk eq '<' ) { |
1603
|
0
|
|
|
|
|
0
|
unshift( @{$$c}, $arg ); |
|
0
|
|
|
|
|
0
|
|
1604
|
|
|
|
|
|
|
} |
1605
|
|
|
|
|
|
|
else { |
1606
|
0
|
|
|
|
|
0
|
$$c->[$lk] = $arg; |
1607
|
|
|
|
|
|
|
} |
1608
|
|
|
|
|
|
|
} |
1609
|
|
|
|
|
|
|
else { |
1610
|
11
|
|
|
|
|
40
|
$$c->{$lk} = $arg; |
1611
|
|
|
|
|
|
|
} |
1612
|
|
|
|
|
|
|
|
1613
|
11
|
|
|
|
|
73
|
$config->augment($ccfg); |
1614
|
11
|
|
|
|
|
69
|
upd_config(); |
1615
|
|
|
|
|
|
|
|
1616
|
11
|
|
|
|
|
93
|
return 1; |
1617
|
|
|
|
|
|
|
} |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
# Formatting. |
1620
|
83
|
100
|
|
|
|
302
|
if ( $dir =~ /^(text|chord|tab|grid|diagrams|title|footer|toc)(font|size|colou?r)$/ ) { |
1621
|
24
|
|
|
|
|
71
|
my $item = $1; |
1622
|
24
|
|
|
|
|
46
|
my $prop = $2; |
1623
|
24
|
|
|
|
|
33
|
my $value = $arg; |
1624
|
|
|
|
|
|
|
|
1625
|
24
|
100
|
|
|
|
66
|
$prop = "color" if $prop eq "colour"; |
1626
|
24
|
|
|
|
|
58
|
my $name = "$item-$prop"; |
1627
|
24
|
|
50
|
|
|
136
|
$propstack{$name} //= []; |
1628
|
|
|
|
|
|
|
|
1629
|
24
|
50
|
|
|
|
56
|
if ( $value eq "" ) { |
1630
|
|
|
|
|
|
|
# Pop current value from stack. |
1631
|
0
|
0
|
|
|
|
0
|
if ( @{ $propstack{$name} } ) { |
|
0
|
|
|
|
|
0
|
|
1632
|
0
|
|
|
|
|
0
|
pop( @{ $propstack{$name} } ); |
|
0
|
|
|
|
|
0
|
|
1633
|
|
|
|
|
|
|
} |
1634
|
|
|
|
|
|
|
# Use new current value, if any. |
1635
|
0
|
0
|
|
|
|
0
|
if ( @{ $propstack{$name} } ) { |
|
0
|
|
|
|
|
0
|
|
1636
|
0
|
|
|
|
|
0
|
$value = $propstack{$name}->[-1] |
1637
|
|
|
|
|
|
|
} |
1638
|
|
|
|
|
|
|
else { |
1639
|
|
|
|
|
|
|
# do_warn("No saved value for property $item$prop\n" ); |
1640
|
0
|
|
|
|
|
0
|
$value = undef; |
1641
|
|
|
|
|
|
|
} |
1642
|
0
|
|
|
|
|
0
|
$self->add( type => "control", |
1643
|
|
|
|
|
|
|
name => $name, |
1644
|
|
|
|
|
|
|
value => $value ); |
1645
|
0
|
|
|
|
|
0
|
return 1; |
1646
|
|
|
|
|
|
|
} |
1647
|
|
|
|
|
|
|
|
1648
|
24
|
100
|
|
|
|
47
|
if ( $prop eq "size" ) { |
1649
|
8
|
50
|
|
|
|
44
|
unless ( $value =~ /^\d+(?:\.\d+)?\%?$/ ) { |
1650
|
0
|
|
|
|
|
0
|
do_warn("Illegal value \"$value\" for $item$prop\n"); |
1651
|
0
|
|
|
|
|
0
|
return 1; |
1652
|
|
|
|
|
|
|
} |
1653
|
|
|
|
|
|
|
} |
1654
|
24
|
100
|
|
|
|
63
|
if ( $prop =~ /^colou?r$/ ) { |
1655
|
8
|
|
|
|
|
17
|
my $v; |
1656
|
8
|
50
|
|
|
|
25
|
unless ( $v = get_color($value) ) { |
1657
|
0
|
|
|
|
|
0
|
do_warn("Illegal value \"$value\" for $item$prop\n"); |
1658
|
0
|
|
|
|
|
0
|
return 1; |
1659
|
|
|
|
|
|
|
} |
1660
|
8
|
|
|
|
|
16
|
$value = $v; |
1661
|
|
|
|
|
|
|
} |
1662
|
24
|
100
|
|
|
|
60
|
$value = $prop eq 'font' ? $value : lc($value); |
1663
|
24
|
|
|
|
|
78
|
$self->add( type => "control", |
1664
|
|
|
|
|
|
|
name => $name, |
1665
|
|
|
|
|
|
|
value => $value ); |
1666
|
24
|
|
|
|
|
36
|
push( @{ $propstack{$name} }, $value ); |
|
24
|
|
|
|
|
73
|
|
1667
|
|
|
|
|
|
|
|
1668
|
|
|
|
|
|
|
# A trailing number after a font directive is an implisit size |
1669
|
|
|
|
|
|
|
# directive. |
1670
|
24
|
50
|
66
|
|
|
81
|
if ( $prop eq 'font' && $value =~ /\s(\d+(?:\.\d+)?)$/ ) { |
1671
|
0
|
|
|
|
|
0
|
$self->add( type => "control", |
1672
|
|
|
|
|
|
|
name => "$item-size", |
1673
|
|
|
|
|
|
|
value => $1 ); |
1674
|
0
|
|
|
|
|
0
|
push( @{ $propstack{"$item-size"} }, $1 ); |
|
0
|
|
|
|
|
0
|
|
1675
|
|
|
|
|
|
|
} |
1676
|
|
|
|
|
|
|
|
1677
|
24
|
|
|
|
|
91
|
return 1; |
1678
|
|
|
|
|
|
|
} |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
# define A: base-fret N frets N N N N N N fingers N N N N N N |
1681
|
|
|
|
|
|
|
# define: A base-fret N frets N N N N N N fingers N N N N N N |
1682
|
|
|
|
|
|
|
# optional: base-fret N (defaults to 1) |
1683
|
|
|
|
|
|
|
# optional: N N N N N N (for unknown chords) |
1684
|
|
|
|
|
|
|
# optional: fingers N N N N N N |
1685
|
|
|
|
|
|
|
|
1686
|
59
|
100
|
100
|
|
|
199
|
if ( $dir eq "define" or $dir eq "chord" ) { |
1687
|
|
|
|
|
|
|
|
1688
|
58
|
|
|
|
|
223
|
return $self->define_chord( $dir, $arg ); |
1689
|
|
|
|
|
|
|
} |
1690
|
|
|
|
|
|
|
|
1691
|
|
|
|
|
|
|
# Warn about unknowns, unless they are x_... form. |
1692
|
|
|
|
|
|
|
do_warn("Unknown directive: $d\n") |
1693
|
1
|
50
|
33
|
|
|
14
|
if $config->{settings}->{strict} && $d !~ /^x_/; |
1694
|
1
|
|
|
|
|
27
|
return; |
1695
|
|
|
|
|
|
|
} |
1696
|
|
|
|
|
|
|
|
1697
|
|
|
|
|
|
|
sub add_chord { |
1698
|
991
|
|
|
991
|
0
|
2529
|
my ( $self, $info, $new_id ) = @_; |
1699
|
|
|
|
|
|
|
|
1700
|
991
|
100
|
|
|
|
2022
|
if ( $new_id ) { |
1701
|
942
|
100
|
|
|
|
2241
|
if ( $new_id eq "1" ) { |
1702
|
10
|
|
|
|
|
23
|
state $id = "ch0000"; |
1703
|
10
|
|
|
|
|
25
|
$new_id = " $id"; |
1704
|
10
|
|
|
|
|
23
|
$id++; |
1705
|
|
|
|
|
|
|
} |
1706
|
|
|
|
|
|
|
} |
1707
|
|
|
|
|
|
|
else { |
1708
|
49
|
|
|
|
|
137
|
$new_id = $info->name; |
1709
|
|
|
|
|
|
|
} |
1710
|
991
|
|
|
|
|
2639
|
$self->{chordsinfo}->{$new_id} = $info->new($info); |
1711
|
|
|
|
|
|
|
|
1712
|
991
|
|
|
|
|
4001
|
return $new_id; |
1713
|
|
|
|
|
|
|
} |
1714
|
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
sub define_chord { |
1716
|
70
|
|
|
70
|
0
|
217
|
my ( $self, $dir, $args ) = @_; |
1717
|
|
|
|
|
|
|
|
1718
|
|
|
|
|
|
|
# Split the arguments and keep a copy for error messages. |
1719
|
|
|
|
|
|
|
# Note that quotewords returns an empty result if it gets confused, |
1720
|
|
|
|
|
|
|
# so fall back to the ancient split method if so. |
1721
|
70
|
|
|
|
|
226
|
$args =~ s/^\s+//; |
1722
|
70
|
|
|
|
|
320
|
$args =~ s/\s+$//; |
1723
|
70
|
|
|
|
|
287
|
my @a = quotewords( '[: ]+', 0, $args ); |
1724
|
70
|
100
|
|
|
|
20889
|
@a = split( /[: ]+/, $args ) unless @a; |
1725
|
|
|
|
|
|
|
|
1726
|
70
|
|
|
|
|
246
|
my @orig = @a; |
1727
|
70
|
|
|
|
|
154
|
my $show = $dir eq "chord"; |
1728
|
70
|
|
|
|
|
116
|
my $fail = 0; |
1729
|
70
|
|
|
|
|
130
|
my $name = shift(@a); |
1730
|
70
|
|
|
|
|
309
|
my $strings = $config->diagram_strings; |
1731
|
|
|
|
|
|
|
|
1732
|
|
|
|
|
|
|
# Process the options. |
1733
|
70
|
|
|
|
|
241
|
my %kv = ( name => $name ); |
1734
|
70
|
|
|
|
|
194
|
while ( @a ) { |
1735
|
162
|
|
|
|
|
285
|
my $a = shift(@a); |
1736
|
|
|
|
|
|
|
|
1737
|
|
|
|
|
|
|
# Copy existing definition. |
1738
|
162
|
100
|
66
|
|
|
1204
|
if ( $a eq "copy" || $a eq "copyall" ) { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1739
|
11
|
50
|
|
|
|
43
|
if ( my $i = ChordPro::Chords::known_chord($a[0]) ) { |
1740
|
11
|
|
|
|
|
40
|
$kv{$a} = $a[0]; |
1741
|
11
|
|
|
|
|
29
|
$kv{orig} = $i; |
1742
|
11
|
|
|
|
|
36
|
shift(@a); |
1743
|
|
|
|
|
|
|
} |
1744
|
|
|
|
|
|
|
else { |
1745
|
0
|
|
|
|
|
0
|
do_warn("Unknown chord to copy: $a[0]\n"); |
1746
|
0
|
|
|
|
|
0
|
$fail++; |
1747
|
0
|
|
|
|
|
0
|
last; |
1748
|
|
|
|
|
|
|
} |
1749
|
|
|
|
|
|
|
} |
1750
|
|
|
|
|
|
|
|
1751
|
|
|
|
|
|
|
# display |
1752
|
|
|
|
|
|
|
elsif ( $a eq "display" && @a ) { |
1753
|
2
|
|
|
|
|
11
|
$kv{display} = demarkup($a[0]); |
1754
|
|
|
|
|
|
|
do_warn( "\"display\" should not contain markup, use \"format\"" ) |
1755
|
2
|
50
|
|
|
|
10
|
unless $kv{display} eq shift(@a); |
1756
|
2
|
|
|
|
|
11
|
$kv{display} = $self->parse_chord($kv{display},1); |
1757
|
2
|
50
|
|
|
|
14
|
delete $kv{display} unless defined $kv{display}; |
1758
|
|
|
|
|
|
|
} |
1759
|
|
|
|
|
|
|
|
1760
|
|
|
|
|
|
|
# format |
1761
|
|
|
|
|
|
|
elsif ( $a eq "format" && @a ) { |
1762
|
9
|
|
|
|
|
32
|
$kv{format} = shift(@a); |
1763
|
|
|
|
|
|
|
} |
1764
|
|
|
|
|
|
|
|
1765
|
|
|
|
|
|
|
# base-fret N |
1766
|
|
|
|
|
|
|
elsif ( $a eq "base-fret" ) { |
1767
|
46
|
50
|
|
|
|
228
|
if ( $a[0] =~ /^\d+$/ ) { |
1768
|
46
|
|
|
|
|
172
|
$kv{base} = shift(@a); |
1769
|
|
|
|
|
|
|
} |
1770
|
|
|
|
|
|
|
else { |
1771
|
0
|
|
|
|
|
0
|
do_warn("Invalid base-fret value: $a[0]\n"); |
1772
|
0
|
|
|
|
|
0
|
$fail++; |
1773
|
0
|
|
|
|
|
0
|
last; |
1774
|
|
|
|
|
|
|
} |
1775
|
|
|
|
|
|
|
} |
1776
|
|
|
|
|
|
|
# frets N N ... N |
1777
|
|
|
|
|
|
|
elsif ( $a eq "frets" ) { |
1778
|
57
|
|
|
|
|
102
|
my @f; |
1779
|
57
|
|
100
|
|
|
393
|
while ( @a && $a[0] =~ /^(?:[0-9]+|[-xXN])$/ && @f < $strings ) { |
|
|
|
66
|
|
|
|
|
1780
|
342
|
|
|
|
|
1632
|
push( @f, shift(@a) ); |
1781
|
|
|
|
|
|
|
} |
1782
|
57
|
50
|
|
|
|
178
|
if ( @f == $strings ) { |
1783
|
57
|
100
|
|
|
|
129
|
$kv{frets} = [ map { $_ =~ /^\d+/ ? $_ : -1 } @f ]; |
|
342
|
|
|
|
|
1070
|
|
1784
|
|
|
|
|
|
|
} |
1785
|
|
|
|
|
|
|
else { |
1786
|
0
|
|
|
|
|
0
|
do_warn("Incorrect number of fret positions (" . |
1787
|
|
|
|
|
|
|
scalar(@f) . ", should be $strings)\n"); |
1788
|
0
|
|
|
|
|
0
|
$fail++; |
1789
|
0
|
|
|
|
|
0
|
last; |
1790
|
|
|
|
|
|
|
} |
1791
|
|
|
|
|
|
|
} |
1792
|
|
|
|
|
|
|
|
1793
|
|
|
|
|
|
|
# fingers N N ... N |
1794
|
|
|
|
|
|
|
elsif ( $a eq "fingers" ) { |
1795
|
16
|
|
|
|
|
31
|
my @f; |
1796
|
|
|
|
|
|
|
# It is tempting to limit the fingers to 1..5 ... |
1797
|
16
|
|
100
|
|
|
88
|
while ( @a && @f < $strings ) { |
1798
|
96
|
|
|
|
|
150
|
local $_ = shift(@a); |
1799
|
96
|
100
|
|
|
|
277
|
if ( /^[0-9]+$/ ) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1800
|
90
|
|
|
|
|
318
|
push( @f, 0 + $_ ); |
1801
|
|
|
|
|
|
|
} |
1802
|
|
|
|
|
|
|
elsif ( /^[A-MO-WYZ]$/ ) { |
1803
|
0
|
|
|
|
|
0
|
push( @f, $_ ); |
1804
|
|
|
|
|
|
|
} |
1805
|
|
|
|
|
|
|
elsif ( /^[-xNX]$/ ) { |
1806
|
6
|
|
|
|
|
17
|
push( @f, -1 ); |
1807
|
|
|
|
|
|
|
} |
1808
|
|
|
|
|
|
|
else { |
1809
|
0
|
|
|
|
|
0
|
unshift( @a, $_ ); |
1810
|
0
|
|
|
|
|
0
|
last; |
1811
|
|
|
|
|
|
|
} |
1812
|
|
|
|
|
|
|
} |
1813
|
16
|
50
|
|
|
|
57
|
if ( @f == $strings ) { |
1814
|
16
|
|
|
|
|
58
|
$kv{fingers} = \@f; |
1815
|
|
|
|
|
|
|
} |
1816
|
|
|
|
|
|
|
else { |
1817
|
0
|
|
|
|
|
0
|
do_warn("Incorrect number of finger settings (" . |
1818
|
|
|
|
|
|
|
scalar(@f) . ", should be $strings)\n"); |
1819
|
0
|
|
|
|
|
0
|
$fail++; |
1820
|
0
|
|
|
|
|
0
|
last; |
1821
|
|
|
|
|
|
|
} |
1822
|
|
|
|
|
|
|
} |
1823
|
|
|
|
|
|
|
|
1824
|
|
|
|
|
|
|
# keys N N ... N |
1825
|
|
|
|
|
|
|
elsif ( $a eq "keys" ) { |
1826
|
8
|
|
|
|
|
13
|
my @f; |
1827
|
8
|
|
100
|
|
|
36
|
while ( @a && $a[0] =~ /^[0-9]+$/ ) { |
1828
|
24
|
|
|
|
|
94
|
push( @f, shift(@a) ); |
1829
|
|
|
|
|
|
|
} |
1830
|
8
|
50
|
|
|
|
22
|
if ( @f ) { |
1831
|
8
|
|
|
|
|
23
|
$kv{keys} = \@f; |
1832
|
|
|
|
|
|
|
} |
1833
|
|
|
|
|
|
|
else { |
1834
|
0
|
|
|
|
|
0
|
do_warn("Invalid or missing keys\n"); |
1835
|
0
|
|
|
|
|
0
|
$fail++; |
1836
|
0
|
|
|
|
|
0
|
last; |
1837
|
|
|
|
|
|
|
} |
1838
|
|
|
|
|
|
|
} |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
elsif ( $a eq "diagram" && @a > 0 ) { |
1841
|
13
|
50
|
33
|
|
|
59
|
if ( $show && !is_true($a[0]) ) { |
1842
|
0
|
|
|
|
|
0
|
do_warn("Useless diagram suppression"); |
1843
|
0
|
|
|
|
|
0
|
next; |
1844
|
|
|
|
|
|
|
} |
1845
|
13
|
|
|
|
|
50
|
$kv{diagram} = shift(@a); |
1846
|
|
|
|
|
|
|
} |
1847
|
|
|
|
|
|
|
|
1848
|
|
|
|
|
|
|
# Wrong... |
1849
|
|
|
|
|
|
|
else { |
1850
|
|
|
|
|
|
|
# Insert a marker to show how far we got. |
1851
|
0
|
|
|
|
|
0
|
splice( @orig, @orig-@a, 0, "<<<" ); |
1852
|
0
|
|
|
|
|
0
|
splice( @orig, @orig-@a-2, 0, ">>>" ); |
1853
|
0
|
|
|
|
|
0
|
do_warn("Invalid chord definition: @orig\n"); |
1854
|
0
|
|
|
|
|
0
|
$fail++; |
1855
|
0
|
|
|
|
|
0
|
last; |
1856
|
|
|
|
|
|
|
} |
1857
|
|
|
|
|
|
|
} |
1858
|
|
|
|
|
|
|
|
1859
|
70
|
50
|
|
|
|
185
|
return 1 if $fail; |
1860
|
|
|
|
|
|
|
# All options are verified and stored in %kv; |
1861
|
|
|
|
|
|
|
|
1862
|
|
|
|
|
|
|
# Result structure. |
1863
|
70
|
|
|
|
|
196
|
my $res = { name => $name }; |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
# Try to find info. |
1866
|
70
|
|
|
|
|
308
|
my $info = $self->parse_chord( $name, "def" ); |
1867
|
70
|
50
|
|
|
|
191
|
if ( $info ) { |
1868
|
|
|
|
|
|
|
# Copy the chord info. |
1869
|
|
|
|
|
|
|
$res->{$_} //= $info->{$_} // '' |
1870
|
70
|
|
100
|
|
|
2100
|
for qw( root qual ext bass |
|
|
|
66
|
|
|
|
|
1871
|
|
|
|
|
|
|
root_canon qual_canon ext_canon bass_canon |
1872
|
|
|
|
|
|
|
root_ord root_mod bass_ord bass_mod |
1873
|
|
|
|
|
|
|
); |
1874
|
70
|
100
|
|
|
|
200
|
if ( $show ) { |
1875
|
|
|
|
|
|
|
$res->{$_} //= $info->{$_} |
1876
|
8
|
|
66
|
|
|
92
|
for qw( base frets fingers keys ); |
1877
|
|
|
|
|
|
|
} |
1878
|
|
|
|
|
|
|
} |
1879
|
|
|
|
|
|
|
else { |
1880
|
0
|
|
|
|
|
0
|
$res->{parser} = ChordPro::Chords::get_parser(); |
1881
|
|
|
|
|
|
|
} |
1882
|
|
|
|
|
|
|
|
1883
|
|
|
|
|
|
|
# Copy existing definition. |
1884
|
70
|
|
66
|
|
|
326
|
for ( $kv{copyall} // $kv{copy} ) { |
1885
|
70
|
100
|
|
|
|
188
|
next unless defined; |
1886
|
11
|
|
|
|
|
33
|
$res->{copy} = $_; |
1887
|
11
|
|
|
|
|
25
|
my $orig = $res->{orig} = $kv{orig}; |
1888
|
|
|
|
|
|
|
$res->{$_} //= $orig->{$_} |
1889
|
11
|
|
33
|
|
|
117
|
for qw( base frets fingers keys ); |
1890
|
11
|
50
|
|
|
|
36
|
if ( $kv{copyall} ) { |
1891
|
|
|
|
|
|
|
$res->{$_} //= $orig->{$_} |
1892
|
0
|
|
0
|
|
|
0
|
for qw( display format ); |
1893
|
|
|
|
|
|
|
} |
1894
|
|
|
|
|
|
|
} |
1895
|
70
|
|
|
|
|
132
|
for ( qw( display format ) ) { |
1896
|
140
|
100
|
|
|
|
371
|
$res->{$_} = $kv{$_} if defined $kv{$_}; |
1897
|
|
|
|
|
|
|
} |
1898
|
|
|
|
|
|
|
|
1899
|
|
|
|
|
|
|
# If we've got diagram visibility, remove it if true. |
1900
|
70
|
100
|
|
|
|
173
|
if ( defined $kv{diagram} ) { |
1901
|
13
|
|
|
|
|
42
|
for ( my $v = $kv{diagram} ) { |
1902
|
13
|
100
|
|
|
|
49
|
if ( is_true($v) ) { |
1903
|
7
|
100
|
|
|
|
31
|
if ( is_ttrue($v) ) { |
1904
|
6
|
|
|
|
|
17
|
next; |
1905
|
|
|
|
|
|
|
} |
1906
|
|
|
|
|
|
|
} |
1907
|
|
|
|
|
|
|
else { |
1908
|
6
|
|
|
|
|
13
|
$v = 0; |
1909
|
|
|
|
|
|
|
} |
1910
|
7
|
|
|
|
|
23
|
$res->{diagram} = $v; |
1911
|
|
|
|
|
|
|
} |
1912
|
|
|
|
|
|
|
} |
1913
|
|
|
|
|
|
|
|
1914
|
|
|
|
|
|
|
# Copy rest of options. |
1915
|
70
|
|
|
|
|
157
|
for ( qw( base frets fingers keys display format ) ) { |
1916
|
420
|
100
|
|
|
|
844
|
next unless defined $kv{$_}; |
1917
|
138
|
|
|
|
|
289
|
$res->{$_} = $kv{$_}; |
1918
|
|
|
|
|
|
|
} |
1919
|
|
|
|
|
|
|
|
1920
|
|
|
|
|
|
|
# At this time, $res is still just a hash. Time to make a chord. |
1921
|
70
|
|
100
|
|
|
248
|
$res->{base} ||= 1; |
1922
|
70
|
100
|
|
|
|
999
|
$res = ChordPro::Chord::Common->new |
1923
|
|
|
|
|
|
|
( { %$res, origin => $show ? "inline" : "song" } ); |
1924
|
70
|
|
33
|
|
|
602
|
$res->{parser} //= ChordPro::Chords::get_parser(); |
1925
|
|
|
|
|
|
|
|
1926
|
70
|
100
|
|
|
|
169
|
if ( $show) { |
1927
|
8
|
|
|
|
|
42
|
my $ci = $res->clone; |
1928
|
8
|
|
|
|
|
6224
|
my $chidx = $self->add_chord( $ci, 1 ); |
1929
|
|
|
|
|
|
|
# Combine consecutive entries. |
1930
|
8
|
100
|
66
|
|
|
63
|
if ( defined($self->{body}) |
1931
|
|
|
|
|
|
|
&& $self->{body}->[-1]->{type} eq "diagrams" ) { |
1932
|
2
|
|
|
|
|
11
|
push( @{ $self->{body}->[-1]->{chords} }, $chidx ); |
|
2
|
|
|
|
|
9
|
|
1933
|
|
|
|
|
|
|
} |
1934
|
|
|
|
|
|
|
else { |
1935
|
6
|
|
|
|
|
29
|
$self->add( type => "diagrams", |
1936
|
|
|
|
|
|
|
show => "user", |
1937
|
|
|
|
|
|
|
origin => "chord", |
1938
|
|
|
|
|
|
|
chords => [ $chidx ] ); |
1939
|
|
|
|
|
|
|
} |
1940
|
8
|
|
|
|
|
96
|
return 1; |
1941
|
|
|
|
|
|
|
} |
1942
|
|
|
|
|
|
|
|
1943
|
62
|
|
|
|
|
126
|
my $def = {}; |
1944
|
62
|
|
|
|
|
147
|
for ( qw( name base frets fingers keys display format diagram ) ) { |
1945
|
496
|
100
|
|
|
|
1048
|
next unless defined $res->{$_}; |
1946
|
246
|
|
|
|
|
514
|
$def->{$_} = $res->{$_}; |
1947
|
|
|
|
|
|
|
} |
1948
|
62
|
|
|
|
|
125
|
push( @{$self->{define}}, $def ); |
|
62
|
|
|
|
|
200
|
|
1949
|
62
|
|
|
|
|
215
|
my $ret = ChordPro::Chords::add_song_chord($res); |
1950
|
62
|
50
|
|
|
|
161
|
if ( $ret ) { |
1951
|
0
|
|
|
|
|
0
|
do_warn("Invalid chord: ", $res->{name}, ": ", $ret, "\n"); |
1952
|
0
|
|
|
|
|
0
|
return 1; |
1953
|
|
|
|
|
|
|
} |
1954
|
62
|
|
|
|
|
181
|
$info = ChordPro::Chords::known_chord($res->{name}); |
1955
|
62
|
50
|
|
|
|
177
|
croak("We just entered it?? ", $res->{name}) unless $info; |
1956
|
|
|
|
|
|
|
|
1957
|
62
|
50
|
|
|
|
189
|
$info->dump if $config->{debug}->{x1}; |
1958
|
|
|
|
|
|
|
|
1959
|
62
|
|
|
|
|
557
|
return 1; |
1960
|
|
|
|
|
|
|
} |
1961
|
|
|
|
|
|
|
|
1962
|
|
|
|
|
|
|
sub duration { |
1963
|
9
|
|
|
9
|
0
|
27
|
my ( $dur ) = @_; |
1964
|
|
|
|
|
|
|
|
1965
|
9
|
50
|
|
|
|
83
|
if ( $dur =~ /(?:(?:(\d+):)?(\d+):)?(\d+)/ ) { |
1966
|
9
|
50
|
|
|
|
100
|
$dur = $3 + ( $2 ? 60 * $2 :0 ) + ( $1 ? 3600 * $1 : 0 ); |
|
|
50
|
|
|
|
|
|
1967
|
|
|
|
|
|
|
} |
1968
|
9
|
|
|
|
|
130
|
my $res = sprintf( "%d:%02d:%02d", |
1969
|
|
|
|
|
|
|
int( $dur / 3600 ), |
1970
|
|
|
|
|
|
|
int( ( $dur % 3600 ) / 60 ), |
1971
|
|
|
|
|
|
|
$dur % 60 ); |
1972
|
9
|
|
|
|
|
43
|
$res =~ s/^[0:]+//; |
1973
|
9
|
|
|
|
|
29
|
return $res; |
1974
|
|
|
|
|
|
|
} |
1975
|
|
|
|
|
|
|
|
1976
|
|
|
|
|
|
|
sub get_color { |
1977
|
8
|
|
|
8
|
0
|
27
|
$_[0]; |
1978
|
|
|
|
|
|
|
} |
1979
|
|
|
|
|
|
|
|
1980
|
|
|
|
|
|
|
sub _diag { |
1981
|
23
|
|
|
23
|
|
19525
|
my ( $self, %d ) = @_; |
1982
|
23
|
|
|
|
|
118
|
$diag->{$_} = $d{$_} for keys(%d); |
1983
|
|
|
|
|
|
|
} |
1984
|
|
|
|
|
|
|
|
1985
|
|
|
|
|
|
|
sub msg { |
1986
|
3
|
|
|
3
|
0
|
12
|
my $m = join("", @_); |
1987
|
3
|
|
|
|
|
19
|
$m =~ s/\n+$//; |
1988
|
3
|
|
|
|
|
13
|
my $t = $diag->{format}; |
1989
|
3
|
|
|
|
|
13
|
$t =~ s/\\n/\n/g; |
1990
|
3
|
|
|
|
|
10
|
$t =~ s/\\t/\t/g; |
1991
|
3
|
|
|
|
|
21
|
$t =~ s/\%f/$diag->{file}/g; |
1992
|
3
|
|
|
|
|
16
|
$t =~ s/\%n/$diag->{line}/g; |
1993
|
3
|
|
|
|
|
13
|
$t =~ s/\%l/$diag->{orig}/g; |
1994
|
3
|
|
|
|
|
13
|
$t =~ s/\%m/$m/g; |
1995
|
3
|
|
|
|
|
139
|
$t; |
1996
|
|
|
|
|
|
|
} |
1997
|
|
|
|
|
|
|
|
1998
|
|
|
|
|
|
|
sub do_warn { |
1999
|
3
|
|
|
3
|
0
|
19
|
warn(msg(@_)."\n"); |
2000
|
|
|
|
|
|
|
} |
2001
|
|
|
|
|
|
|
|
2002
|
|
|
|
|
|
|
# Parse a chord. |
2003
|
|
|
|
|
|
|
# Handles transpose/transcode. |
2004
|
|
|
|
|
|
|
# Returns the chord object. |
2005
|
|
|
|
|
|
|
# No parens or annotations, please. |
2006
|
|
|
|
|
|
|
sub parse_chord { |
2007
|
1112
|
|
|
1112
|
0
|
3548
|
my ( $self, $chord, $def ) = @_; |
2008
|
|
|
|
|
|
|
|
2009
|
1112
|
|
|
|
|
2542
|
my $debug = $config->{debug}->{chords}; |
2010
|
|
|
|
|
|
|
|
2011
|
1112
|
50
|
|
|
|
2446
|
warn("Parsing chord: \"$chord\"\n") if $debug; |
2012
|
1112
|
|
|
|
|
1681
|
my $info; |
2013
|
1112
|
|
|
|
|
2354
|
my $xp = $xpose + $config->{settings}->{transpose}; |
2014
|
1112
|
100
|
100
|
|
|
2745
|
$xp += $capo if $capo && $decapo; |
2015
|
1112
|
|
|
|
|
2224
|
my $xc = $config->{settings}->{transcode}; |
2016
|
1112
|
|
|
|
|
2115
|
my $global_dir = $config->{settings}->{transpose} <=> 0; |
2017
|
1112
|
|
|
|
|
1588
|
my $unk; |
2018
|
|
|
|
|
|
|
|
2019
|
|
|
|
|
|
|
# When called from {define} ignore xc/xp. |
2020
|
1112
|
100
|
|
|
|
2254
|
$xc = $xp = '' if $def; |
2021
|
|
|
|
|
|
|
|
2022
|
1112
|
|
|
|
|
3366
|
$info = ChordPro::Chords::known_chord($chord); |
2023
|
1112
|
100
|
|
|
|
2628
|
if ( $info ) { |
2024
|
|
|
|
|
|
|
warn( "Parsing chord: \"$chord\" found \"", |
2025
|
880
|
50
|
|
|
|
2043
|
$info->name, "\" in ", $info->{_via}, "\n" ) if $debug > 1; |
2026
|
880
|
50
|
|
|
|
1787
|
$info->dump if $debug > 1; |
2027
|
|
|
|
|
|
|
} |
2028
|
|
|
|
|
|
|
else { |
2029
|
232
|
|
|
|
|
786
|
$info = ChordPro::Chords::parse_chord($chord); |
2030
|
|
|
|
|
|
|
warn( "Parsing chord: \"$chord\" parsed ok [", |
2031
|
|
|
|
|
|
|
$info->{system}, |
2032
|
232
|
50
|
66
|
|
|
1140
|
"]\n" ) if $info && $debug > 1; |
2033
|
|
|
|
|
|
|
} |
2034
|
1112
|
|
|
|
|
2035
|
$unk = !defined $info; |
2035
|
|
|
|
|
|
|
|
2036
|
1112
|
100
|
100
|
|
|
6237
|
if ( ( $def || $xp || $xc ) |
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
2037
|
|
|
|
|
|
|
&& |
2038
|
|
|
|
|
|
|
! ($info && $info->is_xpxc ) ) { |
2039
|
21
|
|
|
|
|
66
|
local $::config->{settings}->{chordnames} = "relaxed"; |
2040
|
21
|
|
|
|
|
53
|
$info = ChordPro::Chords::parse_chord($chord); |
2041
|
|
|
|
|
|
|
} |
2042
|
|
|
|
|
|
|
|
2043
|
1112
|
0
|
33
|
|
|
4756
|
unless ( ( $info && $info->is_xpxc ) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
33
|
|
|
|
|
2044
|
|
|
|
|
|
|
|| |
2045
|
|
|
|
|
|
|
( $def && !( $xc || $xp ) ) ) { |
2046
|
|
|
|
|
|
|
do_warn( "Cannot parse", |
2047
|
|
|
|
|
|
|
$xp ? "/transpose" : "", |
2048
|
|
|
|
|
|
|
$xc ? "/transcode" : "", |
2049
|
|
|
|
|
|
|
" chord \"$chord\"\n" ) |
2050
|
0
|
0
|
0
|
|
|
0
|
if $xp || $xc || $config->{debug}->{chords}; |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
} |
2052
|
|
|
|
|
|
|
|
2053
|
1112
|
100
|
66
|
|
|
3291
|
if ( $xp && $info ) { |
2054
|
|
|
|
|
|
|
# For transpose/transcode, chord must be wellformed. |
2055
|
156
|
|
100
|
|
|
727
|
$info = $info->transpose( $xp, |
2056
|
|
|
|
|
|
|
$xpose_dir // $global_dir); |
2057
|
156
|
50
|
|
|
|
486
|
warn( "Parsing chord: \"$chord\" transposed ", |
2058
|
|
|
|
|
|
|
sprintf("%+d", $xp), " to \"", |
2059
|
|
|
|
|
|
|
$info->name, "\"\n" ) if $debug > 1; |
2060
|
|
|
|
|
|
|
} |
2061
|
|
|
|
|
|
|
# else: warning has been given. |
2062
|
|
|
|
|
|
|
|
2063
|
1112
|
50
|
|
|
|
2391
|
if ( $info ) { # TODO roman? |
2064
|
|
|
|
|
|
|
# Look it up now, the name may change by transcode. |
2065
|
1112
|
100
|
33
|
|
|
2741
|
if ( my $i = ChordPro::Chords::known_chord($info) ) { |
|
|
50
|
|
|
|
|
|
2066
|
|
|
|
|
|
|
warn( "Parsing chord: \"$chord\" found ", |
2067
|
|
|
|
|
|
|
$i->name, " for ", $info->name, |
2068
|
885
|
50
|
|
|
|
2182
|
" in ", $i->{_via}, "\n" ) if $debug > 1; |
2069
|
885
|
|
|
|
|
5233
|
$info = $i->new({ %$i, name => $info->name }) ; |
2070
|
885
|
|
|
|
|
4606
|
$unk = 0; |
2071
|
|
|
|
|
|
|
} |
2072
|
|
|
|
|
|
|
elsif ( $config->{instrument}->{type} eq 'keyboard' |
2073
|
|
|
|
|
|
|
&& ( my $k = ChordPro::Chords::get_keys($info) ) ) { |
2074
|
0
|
0
|
|
|
|
0
|
warn( "Parsing chord: \"$chord\" \"", $info->name, "\" not found ", |
2075
|
|
|
|
|
|
|
"but we know what to do\n" ) if $debug > 1; |
2076
|
0
|
|
|
|
|
0
|
$info = $info->new({ %$info, keys => $k }) ; |
2077
|
0
|
|
|
|
|
0
|
$unk = 0; |
2078
|
|
|
|
|
|
|
} |
2079
|
|
|
|
|
|
|
else { |
2080
|
227
|
50
|
|
|
|
544
|
warn( "Parsing chord: \"$chord\" \"", $info->name, |
2081
|
|
|
|
|
|
|
"\" not found in song/config chords\n" ) if $debug; |
2082
|
|
|
|
|
|
|
# warn("XX \'", $info->agnostic, "\'\n"); |
2083
|
227
|
|
|
|
|
411
|
$unk = 1; |
2084
|
|
|
|
|
|
|
} |
2085
|
|
|
|
|
|
|
} |
2086
|
|
|
|
|
|
|
|
2087
|
1112
|
100
|
66
|
|
|
2950
|
if ( $xc && $info ) { |
2088
|
20
|
|
|
|
|
36
|
my $key_ord; |
2089
|
|
|
|
|
|
|
$key_ord = $self->{chordsinfo}->{$self->{meta}->{key}->[-1]}->{root_ord} |
2090
|
20
|
100
|
|
|
|
114
|
if $self->{meta}->{key}; |
2091
|
20
|
50
|
33
|
|
|
64
|
if ( $xcmov && !defined $key_ord ) { |
2092
|
0
|
|
|
|
|
0
|
do_warn("Warning: Transcoding to $xc without key may yield unexpected results\n"); |
2093
|
0
|
|
|
|
|
0
|
undef $xcmov; |
2094
|
|
|
|
|
|
|
} |
2095
|
20
|
|
|
|
|
158
|
$info = $info->transcode( $xc, $key_ord ); |
2096
|
|
|
|
|
|
|
warn( "Parsing chord: \"$chord\" transcoded to ", |
2097
|
|
|
|
|
|
|
$info->name, |
2098
|
20
|
50
|
|
|
|
64
|
" (", $info->{system}, ")", |
2099
|
|
|
|
|
|
|
"\n" ) if $debug > 1; |
2100
|
20
|
100
|
|
|
|
62
|
if ( my $i = ChordPro::Chords::known_chord($info) ) { |
2101
|
8
|
50
|
|
|
|
22
|
warn( "Parsing chord: \"$chord\" found \"", |
2102
|
|
|
|
|
|
|
$info->name, "\" in song/config chords\n" ) if $debug > 1; |
2103
|
8
|
|
|
|
|
18
|
$unk = 0; |
2104
|
|
|
|
|
|
|
} |
2105
|
|
|
|
|
|
|
} |
2106
|
|
|
|
|
|
|
# else: warning has been given. |
2107
|
|
|
|
|
|
|
|
2108
|
1112
|
50
|
|
|
|
2464
|
if ( ! $info ) { |
2109
|
0
|
0
|
|
|
|
0
|
if ( my $i = ChordPro::Chords::known_chord($chord) ) { |
2110
|
0
|
|
|
|
|
0
|
$info = $i; |
2111
|
|
|
|
|
|
|
warn( "Parsing chord: \"$chord\" found \"", |
2112
|
|
|
|
|
|
|
$chord, "\" in ", |
2113
|
0
|
0
|
|
|
|
0
|
$i->{_via}, "\n" ) if $debug > 1; |
2114
|
0
|
|
|
|
|
0
|
$unk = 0; |
2115
|
|
|
|
|
|
|
} |
2116
|
|
|
|
|
|
|
} |
2117
|
|
|
|
|
|
|
|
2118
|
1112
|
50
|
33
|
|
|
2619
|
unless ( $info || $def ) { |
2119
|
0
|
0
|
0
|
|
|
0
|
if ( $config->{debug}->{chords} || ! $warned_chords{$chord}++ ) { |
2120
|
0
|
0
|
|
|
|
0
|
warn("Parsing chord: \"$chord\" unknown\n") if $debug; |
2121
|
0
|
0
|
|
|
|
0
|
do_warn( "Unknown chord: \"$chord\"\n" ) |
2122
|
|
|
|
|
|
|
unless $chord =~ /^n\.?c\.?$/i; |
2123
|
|
|
|
|
|
|
} |
2124
|
|
|
|
|
|
|
} |
2125
|
|
|
|
|
|
|
|
2126
|
1112
|
50
|
|
|
|
2229
|
if ( $info ) { |
2127
|
1112
|
0
|
|
|
|
2201
|
warn( "Parsing chord: \"$chord\" okay: \"", |
|
|
50
|
|
|
|
|
|
2128
|
|
|
|
|
|
|
$info->name, "\" \"", |
2129
|
|
|
|
|
|
|
$info->chord_display, "\"", |
2130
|
|
|
|
|
|
|
$unk ? " but unknown" : "", |
2131
|
|
|
|
|
|
|
"\n" ) if $debug > 1; |
2132
|
1112
|
|
|
|
|
3344
|
$self->store_chord($info); |
2133
|
1112
|
|
|
|
|
3045
|
return $info; |
2134
|
|
|
|
|
|
|
} |
2135
|
|
|
|
|
|
|
|
2136
|
0
|
0
|
|
|
|
0
|
warn( "Parsing chord: \"$chord\" not found\n" ) if $debug; |
2137
|
0
|
|
|
|
|
0
|
return; |
2138
|
|
|
|
|
|
|
} |
2139
|
|
|
|
|
|
|
|
2140
|
|
|
|
|
|
|
sub store_chord { |
2141
|
1132
|
|
|
1132
|
0
|
2208
|
my ( $self, $info ) = @_; |
2142
|
1132
|
|
|
|
|
3076
|
$self->{chordsinfo}->{$info->name} = $info; |
2143
|
1132
|
|
|
|
|
3136
|
$info->name; |
2144
|
|
|
|
|
|
|
} |
2145
|
|
|
|
|
|
|
|
2146
|
|
|
|
|
|
|
sub structurize { |
2147
|
13
|
|
|
13
|
0
|
39
|
my ( $self ) = @_; |
2148
|
|
|
|
|
|
|
|
2149
|
13
|
50
|
|
|
|
50
|
return if $self->{structure} eq "structured"; |
2150
|
|
|
|
|
|
|
|
2151
|
13
|
|
|
|
|
24
|
my @body; |
2152
|
13
|
|
|
|
|
30
|
my $context = $def_context; |
2153
|
|
|
|
|
|
|
|
2154
|
13
|
|
|
|
|
26
|
foreach my $item ( @{ $self->{body} } ) { |
|
13
|
|
|
|
|
45
|
|
2155
|
251
|
100
|
66
|
|
|
607
|
if ( $item->{type} eq "empty" && $item->{context} eq $def_context ) { |
2156
|
56
|
|
|
|
|
86
|
$context = $def_context; |
2157
|
56
|
|
|
|
|
87
|
next; |
2158
|
|
|
|
|
|
|
} |
2159
|
195
|
100
|
100
|
|
|
485
|
if ( $item->{type} eq "songline" && $item->{context} eq '' ){ # A songline should have a context - non means verse |
2160
|
36
|
|
|
|
|
61
|
$item->{context} = 'verse'; |
2161
|
|
|
|
|
|
|
} |
2162
|
195
|
100
|
|
|
|
356
|
if ( $context ne $item->{context} ) { |
2163
|
49
|
|
|
|
|
158
|
push( @body, { type => $context = $item->{context}, body => [] } ); |
2164
|
|
|
|
|
|
|
} |
2165
|
195
|
100
|
|
|
|
304
|
if ( $context ) { |
2166
|
135
|
|
|
|
|
167
|
push( @{ $body[-1]->{body} }, $item ); |
|
135
|
|
|
|
|
264
|
|
2167
|
|
|
|
|
|
|
} |
2168
|
|
|
|
|
|
|
else { |
2169
|
60
|
|
|
|
|
110
|
push( @body, $item ); |
2170
|
|
|
|
|
|
|
} |
2171
|
|
|
|
|
|
|
} |
2172
|
13
|
|
|
|
|
78
|
$self->{body} = [ @body ]; |
2173
|
13
|
|
|
|
|
51
|
$self->{structure} = "structured"; |
2174
|
|
|
|
|
|
|
} |
2175
|
|
|
|
|
|
|
|
2176
|
|
|
|
|
|
|
sub dump { |
2177
|
0
|
|
|
0
|
0
|
|
my ( $self, $full ) = @_; |
2178
|
0
|
|
|
|
|
|
my $a = dclone($self); |
2179
|
0
|
|
|
|
|
|
$a->{config} = ref(delete($a->{config})); |
2180
|
0
|
0
|
|
|
|
|
unless ( $full ) { |
2181
|
0
|
|
|
|
|
|
for my $ci ( keys %{$a->{chordsinfo}} ) { |
|
0
|
|
|
|
|
|
|
2182
|
0
|
|
|
|
|
|
$a->{chordsinfo}{$ci} = $a->{chordsinfo}{$ci}->simplify; |
2183
|
|
|
|
|
|
|
} |
2184
|
|
|
|
|
|
|
} |
2185
|
|
|
|
|
|
|
# require Data::Dump::Filtered; |
2186
|
|
|
|
|
|
|
# warn Data::Dump::Filtered::dump_filtered($a, sub { |
2187
|
|
|
|
|
|
|
# my ( $ctx, $o ) = @_; |
2188
|
|
|
|
|
|
|
# my $h = { hide_keys => [ 'parser' ] }; |
2189
|
|
|
|
|
|
|
# $h->{bless} = "" |
2190
|
|
|
|
|
|
|
# if $ctx->class; |
2191
|
|
|
|
|
|
|
# $h; |
2192
|
|
|
|
|
|
|
# }); |
2193
|
0
|
|
|
|
|
|
::dump($a); |
2194
|
|
|
|
|
|
|
} |
2195
|
|
|
|
|
|
|
|
2196
|
|
|
|
|
|
|
unless ( caller ) { |
2197
|
|
|
|
|
|
|
require DDumper; |
2198
|
|
|
|
|
|
|
binmode STDERR => ':utf8'; |
2199
|
|
|
|
|
|
|
ChordPro::Config::configurator(); |
2200
|
|
|
|
|
|
|
my $s = ChordPro::Song->new; |
2201
|
|
|
|
|
|
|
$options->{settings}->{transpose} = 0; |
2202
|
|
|
|
|
|
|
for ( @ARGV ) { |
2203
|
|
|
|
|
|
|
if ( /^[a-z]/ ) { |
2204
|
|
|
|
|
|
|
$options->{settings}->{transcode} = $_; |
2205
|
|
|
|
|
|
|
next; |
2206
|
|
|
|
|
|
|
} |
2207
|
|
|
|
|
|
|
# DDumper::DDumper( $s->parse_chord($_) ); |
2208
|
|
|
|
|
|
|
my ( undef, $i ) = $s->parse_chord($_); |
2209
|
|
|
|
|
|
|
warn("$_ => ", $i->name, " => ", $s->add_chord($i, $i->name eq 'D'), "\n" ); |
2210
|
|
|
|
|
|
|
$xpose++; |
2211
|
|
|
|
|
|
|
} |
2212
|
|
|
|
|
|
|
DDumper::DDumper($s->{chordsinfo}); |
2213
|
|
|
|
|
|
|
} |
2214
|
|
|
|
|
|
|
|
2215
|
|
|
|
|
|
|
1; |