App-MusicTools
view release on metacpan or search on metacpan
$output_tmpl .= "\n" unless $output_tmpl =~ m/\s$/;
for my $vov_spec (@ARGV) {
my @vovs = reverse split '/', $vov_spec;
my $base_intervals = get_mode_intervals($mode_name);
my $cur_intervals = $base_intervals;
my $sd_transpose = 0;
my ( $prev_root_pitch, $prev_root_sd, $pset, $invert_by );
for my $vov (@vovs) {
my ( $root_sd, $factor, $alterations, $inv ) = parse_roman_numeral($vov);
if ( defined $prev_root_sd ) {
$sd_transpose = $prev_root_pitch;
# This rotation trick constrains the pitches of the new relative
# pitch to those of the overlying mode, as otherwise III/ii
# assuming major will use pitches not present in the underlying
# major scale.
$cur_intervals = $atu->rotate( -1 * $prev_root_sd, $base_intervals );
}
my $sds = build_triad_degrees( $root_sd, $factor );
$pset = sd2ps( $sds, $alterations, $cur_intervals, $sd_transpose );
$prev_root_pitch = $pset->[0];
$prev_root_sd = $root_sd;
$invert_by = $inv;
}
$pset = $atu->rotate( -1 * $invert_by, $pset ) if $invert_by;
if ($Transpose) {
for my $p (@$pset) {
$p += $Transpose;
$p %= $DEG_IN_SCALE;
}
}
my %out_attr = ( vov => $vov_spec );
if ($raw_output) {
$out_attr{chord} = join " ", @$pset;
} else {
$out_attr{chord} = join " ", $lyu->p2ly(@$pset);
}
my $str = $output_tmpl;
$str =~
s/ \Q%{\E (\w+) \Q}\E / defined $out_attr{$1} ? $out_attr{$1} : q{} /egx;
print $str;
}
exit 0;
########################################################################
#
# SUBROUTINES
# Given root scale degree plus a chord factor, returns an array
# reference of scale degrees of the chord elements up to the
# chord factor.
sub build_triad_degrees {
my ( $root_sd, $factor ) = @_;
if ( defined $factor ) {
$factor =~ tr/0-9//cd; # so can say "5th" or "7th" or the like
if ( $factor < 1 or $factor > $MAX_CHORD_FACTOR or $factor % 2 == 0 ) {
croak "factor must be odd number between 1 and $MAX_CHORD_FACTOR inclusive";
}
} else {
$factor = $DEFAULT_CHORD_FACTOR;
}
if ( !defined $root_sd or $root_sd < 0 or $root_sd >= $MAX_SCALE_DEGREE ) {
croak "root scale degree must be 0 to "
. ( $MAX_SCALE_DEGREE - 1 )
. " inclusive";
}
my @sds;
# I blame too many slope equation videos & exercises on Khan Academy
# for this code.
for my $i ( 1 .. ( $factor * 0.5 + 0.5 ) ) {
push @sds, ( $root_sd + 2 * $i - 2 ) % $MAX_SCALE_DEGREE;
}
return \@sds;
}
# Convert pitches from Music::Scales into a list of intervals
sub get_mode_intervals {
my ($name) = @_;
my @pitches = get_scale_nums($name);
my @intervals;
for my $i ( 1 .. $#pitches ) {
push @intervals, $pitches[$i] - $pitches[ $i - 1 ];
}
# pad out scale with VII to I interval
my $sum = sum @intervals;
if ( $sum < $DEG_IN_SCALE ) {
push @intervals, $DEG_IN_SCALE - $sum;
} elsif ( $sum > $DEG_IN_SCALE ) {
die "error: do not know what to do with scale larger than $DEG_IN_SCALE\n";
}
return \@intervals;
}
# XXX 7ths could use more work? no way to specify MM7 vs. Mm7 (that
# these are notated the same using Roman Numerals does not help).
sub parse_roman_numeral {
my ($numeral) = @_;
my ( $pre, $roman, $suf ) =
$numeral =~ m/^($ROMAN_PREFIX_RE)?($ROMAN_NUMERAL_RE)($ROMAN_SUFFIX_RE)?/;
$pre //= '';
$suf //= '';
my %alterations;
my $factor = $Default_Factor;
my $inversion;
# Inversions by trailing letter format: Ia no inversion, Ib first,
# etc. 'g' is to support the maximum inversion of a 13th chord.
if ( $suf =~ m/([a-g])/ ) {
$inversion = ord($1) - ord('a');
( run in 4.252 seconds using v1.01-cache-2.11-cpan-b9db842bd85 )