App-Music-ChordPro
view release on metacpan or search on metacpan
lib/ChordPro/Song.pm view on Meta::CPAN
}
$nbt++;
}
if ( $_->{class} eq "bar" ) {
$p2 = $p1; $p1 = $p0; undef $p0;
}
}
if ( $nbt > $grid_cells->[0] ) {
do_warn( "Too few cells for grid content" );
}
return ( tokens => \@tokens,
$grid_type == 1 ? ( type => "strumline" ) : (),
$grid_type == 2 ? ( type => "strumline", subtype => "cellbars" ) : (),
%res );
}
################ Parsing directives ################
my %directives = (
chord => \&define_chord,
chorus => \&dir_chorus,
column_break => \&dir_column_break,
columns => \&dir_columns,
comment => \&dir_comment,
comment_box => \&dir_comment,
comment_italic => \&dir_comment,
define => \&define_chord,
diagrams => \&dir_diagrams,
end_of_bridge => undef,
end_of_chorus => undef,
end_of_grid => undef,
end_of_grille => undef,
end_of_tab => undef,
end_of_verse => undef,
grid => \&dir_grid,
highlight => \&dir_comment,
image => \&dir_image,
meta => \&dir_meta,
new_page => \&dir_new_page,
new_physical_page => \&dir_new_page,
new_song => \&dir_new_song,
no_grid => \&dir_no_grid,
pagesize => \&dir_papersize,
pagetype => \&dir_papersize,
start_of_bridge => undef,
start_of_chorus => undef,
start_of_grid => undef,
start_of_grille => undef,
start_of_tab => undef,
start_of_verse => undef,
subtitle => \&dir_subtitle,
title => \&dir_title,
titles => \&dir_titles,
transpose => \&dir_transpose,
);
# NOTE: Flex: start_of_... end_of_... x_...
my %abbrevs = (
c => "comment",
cb => "comment_box",
cf => "chordfont",
ci => "comment_italic",
col => "columns",
colb => "column_break",
cs => "chordsize",
eob => "end_of_bridge",
eoc => "end_of_chorus",
eog => "end_of_grid",
eot => "end_of_tab",
eov => "end_of_verse",
g => "diagrams",
ng => "no_grid",
np => "new_page",
npp => "new_physical_page",
ns => "new_song",
sob => "start_of_bridge",
soc => "start_of_chorus",
sog => "start_of_grid",
sot => "start_of_tab",
sov => "start_of_verse",
st => "subtitle",
t => "title",
tf => "textfont",
ts => "textsize",
);
# Use by: runtimeinfo.
sub _directives { \%directives }
sub _directive_abbrevs { \%abbrevs }
my $dirpat;
sub parse_directive {
my ( $self, $d ) = @_;
# Pattern for all recognized directives.
unless ( $dirpat ) {
$dirpat =
'(?:' .
join( '|', keys(%directives),
@{$config->{metadata}->{keys}},
keys(%abbrevs),
'(?:start|end)_of_\w+',
"(?:$propitems_re".
'(?:font|size|colou?r))',
) . ')';
$dirpat = qr/$dirpat/;
}
# $d is the complete directive line, without leading/trailing { }.
if ( $options->{reference} and $d =~ s/^\s*:[: ]*//) {
do_warn("Incorrect start of directive (':' not allowed at start)");
}
$d =~ s/^[: ]+//;
$d =~ s/\s+$//;
my $dir = lc($d);
my $arg = "";
if ( $d =~ /^(.*?)([: ])\s*(.*)/ ) {
( $dir, $arg ) = ( lc($1), $3 );
if ( $options->{reference} ) {
do_warn("Directive name must be followed by a ':'")
unless $2 eq ":";
}
}
$dir =~ s/[: ]+$//;
# $dir is the lowcase directive name.
# $arg is the rest, if any.
# Check for xxx-yyy selectors.
if ( $dir =~ /^($dirpat)-(.+)$/ ) {
$dir = $abbrevs{$1} // $1;
unless ( $self->selected($2) ) {
if ( $dir =~ /^start_of_/ ) {
return { name => $dir, arg => $arg, omit => 2 };
}
else {
return { name => $dir, arg => $arg, omit => 1 };
}
}
}
else {
$dir = $abbrevs{$dir} // $dir;
}
if ( $dir =~ /^start_of_(.*)/
&& exists $config->{delegates}->{$1}
&& beo( $config->{delegates}->{$1}, 'type' ) eq 'omit' ) {
return { name => $dir, arg => $arg, omit => 2 };
}
return { name => $dir, arg => $arg, omit => 0 }
}
# Process a selector.
sub selected {
my ( $self, $sel ) = @_;
return 1 unless defined $sel;
my $negate = $sel =~ s/\!$//;
$sel = ( $sel eq lc($config->{instrument}->{type}) )
||
( $sel eq lc($config->{user}->{name})
||
( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) )
);
$sel = !$sel if $negate;
lib/ChordPro/Song.pm view on Meta::CPAN
}
else {
$self->{spreadimage} =
{ id => $id, space => $opts->{spread} };
warn("Got spread image $id with space=$opts->{spread}\n")
if $config->{debug}->{images};
}
}
# Move to assets.
$self->{assets}->{$id} =
{ type => "image",
subtype => "delegate",
delegate => "Grille",
handler => "grille2xo",
opts => $opts,
line => $grille[0]{line},
data => \@grille,
context => $in_context,
};
if ( $def ) {
my $label = delete $a->{label};
do_warn("Label \"$label\" ignored on non-displaying $in_context section\n")
if $label;
}
else {
my $label = delete $opts->{label};
$self->add( type => "set",
name => "label",
value => $label )
if $label && $label ne "";
$self->add( type => "image",
opts => $opts,
id => $id );
if ( $opts->{label} ) {
push( @labels, $opts->{label} )
unless $in_context eq "chorus"
&& !$config->{settings}->{choruslabels};
}
}
}
}
else {
$self->add( type => "set",
name => "context",
value => $def_context );
}
$in_context = $def_context;
undef $memchords;
return 1;
}
# Metadata extensions (legacy). Should use meta instead.
# Only accept the list from config.
if ( any { $_ eq $dir } @{ $config->{metadata}->{keys} } ) {
return $self->dir_meta( "meta", "$dir $arg" );
}
# Formatting. {chordsize XX} and such.
if ( $dir =~ m/ ^( $propitems_re )
( font | size | colou?r )
$/x ) {
my $item = $1;
my $prop = $2;
$self->propset( $item, $prop, $arg );
# Derived props.
$self->propset( "chorus", $prop, $arg ) if $item eq "text";
# ::dump( { %propstack, line => $diag->{line} } );
return 1;
}
# More private hacks.
if ( !$options->{reference} && $d =~ /^([-+])([-\w.]+)$/i ) {
if ( $2 eq "dumpmeta" ) {
warn(::dump($self->{meta}));
}
$self->add( type => "set",
name => $2,
value => $1 eq "+" ? 1 : 0,
);
return 1;
}
if ( !$options->{reference} && $dir =~ /^\+([-\w.]+(?:\.[<>])?)$/ ) {
$self->add( type => "set",
name => $1,
value => $arg,
);
$config->unlock;
prpadd2cfg( $config, $1 => $arg );
$config->lock;
upd_config();
return 1;
}
# Warn about unknowns, unless they are x_... form.
do_warn("Unknown directive: $d\n")
if $config->{settings}->{strict} && $d !~ /^x_/;
return;
}
sub dir_chorus {
my ( $self, $dir, $arg ) = @_;
if ( $in_context ) {
do_warn("{chorus} encountered while in $in_context context -- ignored\n");
return 1;
}
# Clone the chorus so we can modify the label, if required.
my $chorus = @chorus ? dclone(\@chorus) : [];
if ( @$chorus && $arg && $arg ne "" ) {
my $kv = parse_kv( $arg, "label" );
my $label = $kv->{label};
if ( $chorus->[0]->{type} eq "set" && $chorus->[0]->{name} eq "label" ) {
lib/ChordPro/Song.pm view on Meta::CPAN
}
}
$self->add( %a, value => $xpose )
if $no_transpose;
return 1;
}
#### End of directive handlers ####
sub transpose_sound {
my ( $only_print ) = @_;
my $xp =
# Use current.
$xpose
# Apply capo.
+ ( $only_print ? 0 : $capo//0 )
# Apply global (cli, config) transpose.
+ $config->{settings}->{transpose};
warn( "XPOSE: ",
"base = ", $xpose->_data_printer, " ",
($only_print || !$capo) ? "" : "capo = $capo ",
"outer = ", $config->{settings}->{transpose}->_data_printer, " ",
$only_print ? "print = " : "sound = ", $xp->_data_printer,
"\n") if $config->{debug}->{xpose};
return $xp;
}
sub transpose_print {
transpose_sound(1);
}
sub transpose_debug {
return unless $config->{debug}->{xpose};
my ( $tag, $m ) = @_;
my $xp = transpose_print();
warn( "XPOSE: $tag, ",
"key = ", $m->{key}->[-1], ", ",
"print = ", $m->{key_print}->[0], ", ",
"sound = ", $m->{key_sound}->[0],
" [ ", join( " ", $config->{settings}->{transpose},
$xpose, $capo//0,
$xp->key ? "\@" . $xp->key->keyname : () ),
" ]\n" );
}
sub propset {
my ( $self, $item, $prop, $value ) = @_;
$prop = "color" if $prop eq "colour";
my $name = "$item-$prop";
$propstack{$name} //= [];
if ( $value eq "" ) {
my @toadd;
# Pop current value from stack.
if ( @{ $propstack{$name} } ) {
my $old = pop( @{ $propstack{$name} } );
# A trailing number after a font directive means there
# was also a size saved. Pop it.
if ( $prop eq "font" && $old =~ /\s(\d+(?:\.\d+)?)$/ ) {
pop( @{ $propstack{"$item-size"} } );
# Resetting the size must follow the font reset.
push( @toadd, type => "control",
name => "$item-size",
value =>
@{ $propstack{"$item-size"} }
? $propstack{"$item-size"}->[-1]
: undef );
}
}
else {
do_warn("No saved value for property $item$prop\n" )
}
# Use new current value, if any.
if ( @{ $propstack{$name} } ) {
$value = $propstack{$name}->[-1]
}
else {
$value = undef;
}
$self->add( type => "control",
name => $name,
value => $value );
$self->add( @toadd ) if @toadd;
return 1;
}
if ( $prop eq "size" ) {
unless ( $value =~ /^\d+(?:\.\d+)?\%?$/ ) {
do_warn("Illegal value \"$value\" for $item$prop\n");
return 1;
}
}
if ( $prop eq "color" ) {
my $v;
unless ( $v = get_color($value) ) {
do_warn("Illegal value \"$value\" for $item$prop\n");
return 1;
}
$value = $v;
}
$value = $prop eq "font" ? $value : lc($value);
$self->add( type => "control",
name => $name,
value => $value );
push( @{ $propstack{$name} }, $value );
# A trailing number after a font directive is an implicit size
# directive.
if ( $prop eq 'font' && $value =~ /\s(\d+(?:\.\d+)?)$/ ) {
$self->add( type => "control",
name => "$item-size",
value => $1 );
push( @{ $propstack{"$item-size"} }, $1 );
}
}
sub add_chord {
my ( $self, $info, $new_id ) = @_;
if ( $new_id ) {
if ( $new_id eq "1" ) {
state $id = "ch0000";
$new_id = " $id";
$id++;
}
}
else {
$new_id = $info->name;
}
$self->{chordsinfo}->{$new_id} = $info->new($info);
return $new_id;
}
sub define_chord {
my ( $self, $dir, $args ) = @_;
# Split the arguments and keep a copy for error messages.
# Note that quotewords returns an empty result if it gets confused,
# so fall back to the ancient split method if so.
$args =~ s/^\s+//;
$args =~ s/\s+$//;
my @a = quotewords( '[: ]+', 0, $args );
@a = split( /[: ]+/, $args ) unless @a;
my @orig = @a;
my $show = $dir eq "chord";
my $fail = 0;
my $name = shift(@a);
my $strings = $config->diagram_strings;
# Process the options.
my %kv = ( name => $name );
while ( @a ) {
my $a = shift(@a);
# Copy existing definition.
if ( $a eq "copy" || $a eq "copyall" ) {
if ( my $i = ChordPro::Chords::known_chord($a[0]) ) {
$kv{$a} = $a[0];
$kv{orig} = $i;
shift(@a);
}
else {
do_warn("Unknown chord to copy: $a[0]\n");
$fail++;
last;
}
}
( run in 0.611 second using v1.01-cache-2.11-cpan-5735350b133 )