Music-NeoRiemannianTonnetz
view release on metacpan or search on metacpan
eg/nrt-study-setclass view on Meta::CPAN
my $atu = Music::AtonalUtil->new;
my $lyu = Music::LilyPondUtil->new(
ignore_register => 1,
keep_state => 0,
mode => 'relative',
);
my $nrt = Music::NeoRiemannianTonnetz->new;
my $FORTE_NUMBER_RE = $atu->forte_number_re;
GetOptions(
'exclusive|E' => \my $Flag_Exclusive,
'invary|i=i' => \my $Flag_Invary_Count,
) or @ARGV = ();
if ( !@ARGV ) {
warn "Usage: $0 --invary=# [--exclusive|-E] forte-number|list of pitches\n";
exit 64;
}
my $pitch_set;
if ( $ARGV[0] =~ m/($FORTE_NUMBER_RE)/ ) {
$pitch_set = $atu->forte2pcs($1);
die "unknown Forte Number '$ARGV[0]'\n" if !defined $pitch_set;
} else {
for my $arg (@ARGV) {
for my $p ( $arg =~ /([-\d\w]+)/g ) {
push @$pitch_set, $lyu->notes2pitches($p);
}
}
}
# figure out disposition of the input set class
my ( %consists, %variations );
for my $base ( 0 .. $atu->scale_degrees - 1 ) {
my ( @new, @newinverse );
for my $i (@$pitch_set) {
push @new, ( $base + $i ) % $atu->scale_degrees;
push @newinverse, ( $base - $i ) % $atu->scale_degrees;
}
$variations{ ps2bits($_) }++ for \@new, \@newinverse;
$consists{ $nrt->normalize( \@new ) }++;
$consists{ $nrt->normalize( \@newinverse ) }++;
}
for my $v ( values %variations ) {
if ( $v > 1 ) {
warn 'notice: set class ', ps2str($pitch_set),
" likely not suitable for N-R operations\n";
last;
}
}
warn 'info: set class ', ps2str($pitch_set), ' consists of { ',
join( ', ', map ps2str($_), keys %consists ), " }\n";
$Flag_Invary_Count //= 2; # as both 3-11 and 4-27 operations use this
if ( $Flag_Invary_Count >= @$pitch_set ) {
# well, you could, but the results would be unedifying
die "error: cannot invary $Flag_Invary_Count elements in a ",
scalar(@$pitch_set), " element set class\n";
}
# In hindsight these could be set with the -G flag to neato or whatnot
say <<"GRAPHVIZ_HEADER";
graph links {
size="32,32";
ratio="fill";
splines=polyline;
GRAPHVIZ_HEADER
my %seen;
for my $pset ( keys %variations ) {
# Exclude any three invariant pitch matches if given for example 4-27
# and --invary=2.
if ($Flag_Exclusive) {
my $higher_invary_count = @$pitch_set - $Flag_Invary_Count;
if ( $higher_invary_count > 1 ) {
for my $i ( $Flag_Invary_Count + 1 .. @$pitch_set - 1 ) {
my $c = Math::Combinatorics->new(
count => $i,
data => bits2ps($pset),
);
while ( my @tomask = $c->next_combination ) {
my $invary_mask = ps2bits( \@tomask );
for my $set ( keys %variations ) {
if ( $invary_mask == ( $set & $invary_mask ) ) {
$seen{ join ':', sort $pset, $set }++;
}
}
}
}
}
}
my $c = Math::Combinatorics->new(
count => $Flag_Invary_Count,
data => bits2ps($pset),
);
while ( my @tomask = $c->next_combination ) {
my $invary_mask = ps2bits( \@tomask );
for my $set ( keys %variations ) {
next if $pset == $set; # exclude link to self
if ( $invary_mask == ( $set & $invary_mask ) ) {
# omit "b to a" if already have "a to b" for cleaner output
next if $seen{ join ':', sort $pset, $set }++;
printf qq{ "%s" -- "%s";\n}, ps2str( bits2ps($pset) ),
ps2str( bits2ps($set) );
}
}
}
}
say <<"GRAPHVIZ_FOOTER";
}
GRAPHVIZ_FOOTER
exit;
########################################################################
#
( run in 1.027 second using v1.01-cache-2.11-cpan-788537b7465 )