App-MusicTools
view release on metacpan or search on metacpan
bin/atonal-util view on Meta::CPAN
#!perl
#
# atonal-util - command line interface to the atonal routines in the
# Music::AtonalUtil module (and otherwise a dumping ground for music
# related wrangling).
#
# Run perldoc(1) on this file for additional documentation.
#
# A ZSH completion script is available in the zsh-compdef/ directory of
# the App::MusicTools distribution.
# XXX improve emit_pitch_set (returns a string, so caller can then do
# what it will -- or have LilyPondUtil sub that knows how to format a
# pitch set? (among other possible code cleanups/simplifications)
#
# (Or more likely scrap and rewrite from scratch, eesh.)
use 5.14.0;
use warnings;
use Getopt::Long qw/GetOptionsFromArray/;
use List::Util qw/sum/;
use Music::AtonalUtil ();
use Music::LilyPondUtil ();
use Music::Scala ();
use Music::Scales qw/get_scale_nums/;
use Music::Tempo qw/bpm_to_ms ms_to_bpm/;
use Music::Tension::Cope ();
# untested with --tension
#use Music::Tension::PlompLevelt ();
use Parse::Range qw/parse_range/;
use Scalar::Util qw/looks_like_number/;
my %modes = (
adjacent_interval_content => \&adjacent_interval_content,
bark => \&bark,
basic => \&basic,
beats2set => \&beats2set,
circular_permute => \&circular_permute,
combos => \&combos,
complement => \&complement,
equivs => \&equivs,
findall => \&findall,
findin => \&findin,
fnums => \&fnums,
forte2pcs => \&forte2pcs,
freq2pitch => \&freq2pitch,
gen_melody => \&gen_melody,
half_prime_form => \&half_prime_form,
interval_class_content => \&interval_class_content,
intervals2pcs => \&intervals2pcs,
invariance_matrix => \&invariance_matrix,
invariants => \&invariants,
invert => \&invert,
ly2pitch => \&ly2pitch,
ly2struct => \&ly2struct,
multiply => \&multiply,
normal_form => \&normal_form,
notes2time => \¬es2time,
pcs2forte => \&pcs2forte,
pcs2intervals => \&pcs2intervals,
pitch2freq => \&pitch2freq,
pitch2intervalclass => \&pitch2intervalclass,
pitch2ly => \&pitch2ly,
prime_form => \&prime_form,
recipe => \&recipe,
retrograde => \&retrograde,
rotate => \&rotate,
set2beats => \&set2beats,
set_complex => \&set_complex,
subsets => \&subsets,
tcis => \&tcis,
tcs => \&tcs,
tension => \&tension,
time2notes => \&time2notes,
transpose => \&transpose,
transpose_invert => \&transpose_invert,
variances => \&variances,
whatscalesfit => \&whatscalesfit,
zrelation => \&zrelation,
);
my ( $Flag_Flat, $Flag_Lyout, $Flag_Quiet, $Flag_Tension );
my $Flag_Record_Sep = ','; # mostly for pitch sets e.g. 0,4,7
my @Std_Opts = (
'flats!' => \$Flag_Flat,
'ly' => \$Flag_Lyout,
'quiet!' => \$Flag_Quiet,
'rs=s' => \$Flag_Record_Sep,
'tension=s' => \$Flag_Tension,
);
bin/atonal-util view on Meta::CPAN
warn "notice: incomplete beats ($i < $sd)\n" unless $Flag_Quiet;
}
my $lyify = $Flag_Lyout;
if ( $sd != 12 ) {
$lyify = 0;
}
emit_pitch_set( \@set, lyflag => $lyify, rs => $Flag_Record_Sep );
}
sub circular_permute {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->circular_permute( args2pitchset(@args) ) );
}
sub combos {
my (@args) = @_;
my $mode = 'absolute';
GetOptionsFromArray(
\@args, @Std_Opts,
'concertfreq|cf=s' => \my $concert_freq,
'concertpitch|cp=s' => \my $concert_pitch,
'pitches' => \my $Flag_Pitches,
'relative=s' => \my $relative,
'scala=s' => \my $scala_file,
) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
$Lyu->ignore_register(0);
$Lyu->keep_state(1);
$Lyu->sticky_state(1);
if ( defined $relative ) {
if ( $relative =~ m/^\d+$/ ) {
die "error: relative must be note name\n";
}
$Lyu->mode('relative');
$Lyu->prev_note($relative);
} else {
$Lyu->mode('absolute');
}
my ( $scala, $p2f ) = _init_scala( $concert_freq, $concert_pitch, $scala_file );
my @freqs;
if ( !@args or ( @args == 1 and $args[0] eq '-' ) ) {
while ( my $line = readline *STDIN ) {
push @freqs, split ' ', $line;
}
} else {
for my $arg (@args) {
push @freqs, split ' ', $arg;
}
}
if ( @freqs < 2 ) {
die "Usage: $0 combos [--pitches [--relative=note]] f1 f2 [f3...]\n";
}
# turn on pitch mode if first note looks more a note than a number
if ( $Flag_Pitches or $freqs[0] =~ m/[a-g]/ ) {
@freqs = map $p2f->($_), $Lyu->notes2pitches(@freqs);
}
for my $i ( 1 .. $#freqs ) {
my $plus = $freqs[0] + $freqs[1];
my $minus = $freqs[$i] - $freqs[0];
# (try to) Figure out MIDI pitch of combination tone, and what the
# error is due to presumed equal temperament tuning of said MIDI
# pitches.
my $plus_pitch = 0;
my $minus_pitch = 0;
my $plus_delta = 0;
my $minus_delta = 0;
my $errstr = '';
eval {
$plus_pitch = $scala->freq2pitch($plus);
$minus_pitch = $scala->freq2pitch($minus);
$plus_delta = $p2f->($plus_pitch) - $plus;
$minus_delta = $p2f->($minus_pitch) - $minus;
};
$errstr = "\t/!\\ pitch out of bounds" if $@;
# best effort to get a note name, revert to pitch numbers if out of range
if ( $Flag_Lyout and length $errstr == 0 ) {
eval {
$plus_pitch = $Lyu->p2ly($plus_pitch);
$minus_pitch = $Lyu->p2ly($minus_pitch);
};
$errstr = "\t/!\\ ly note out of bounds" if $@;
}
printf "%.2f+%.2f = %.2f\t(%s error %.2f)%s\n", $freqs[0], $freqs[$i],
$plus,
$plus_pitch, $plus_delta, $errstr;
printf "%.2f-%.2f = %.2f\t(%s error %.2f)%s\n", $freqs[$i], $freqs[0],
$minus,
$minus_pitch, $minus_delta, $errstr;
}
}
sub complement {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->complement( args2pitchset(@args) ) );
}
sub emit_pitch_set {
my ( $pset, %params ) = @_;
my $lyify = exists $params{lyflag} ? $params{lyflag} : $Flag_Lyout;
my $rs = exists $params{rs} ? $params{rs} : ' ';
my $has_nl = exists $params{has_nl} ? $params{has_nl} : 0;
my $str = '';
for my $i (@$pset) {
if ( ref $i eq 'ARRAY' ) {
$has_nl = emit_pitch_set( $i, %params );
bin/atonal-util view on Meta::CPAN
my $s = sprintf "%s\tTi(%d)\t%-${ps_width}s%s", $fnum, $i,
join( ',', @pitches ), $tstr;
$s =~ s/\s+$//;
say $s;
}
}
}
sub fnums {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
_init_tension('cope') if $Flag_Tension;
my $fns = $Atu->fnums;
for my $fn ( sort keys %$fns ) {
my $pset = $fns->{$fn};
my $icc = $Atu->interval_class_content($pset);
my $tstr = '';
if ($Flag_Tension) {
$tstr = sprintf "\t%.03f %.03f %.03f", $Tension->vertical($pset);
}
my $s = sprintf "%s\t%-16s\t%-8s%s", $fn, join( ',', @$pset ),
join( '', @$icc ), $tstr;
$s =~ s/\s+$//;
say $s;
}
}
sub forte2pcs {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
emit_pitch_set( $Atu->forte2pcs( $args[0] ), rs => $Flag_Record_Sep );
}
sub freq2pitch {
my (@args) = @_;
my $mode = 'absolute';
GetOptionsFromArray(
\@args,
@Std_Opts,
'concertfreq|cf=s' => \my $concert_freq,
'concertpitch|cp=s' => \my $concert_pitch,
'relative=s' => \my $relative,
'scala=s' => \my $scala_file,
) or print_help();
my ( $scala, $p2f ) = _init_scala( $concert_freq, $concert_pitch, $scala_file );
if ( !@args or ( @args == 1 and $args[0] eq '-' ) ) {
chomp( @args = readline *STDIN );
}
# Not the default, so if things persist or chain due to some rewrite,
# would need to save the old or create a new object or whatever
$Lyu->keep_state(1);
$Lyu->mode('absolute');
for my $freq ( grep looks_like_number $_, map { split ' ', $_ } @args ) {
die "frequency '$freq' out of range" if $freq < 8 or $freq > 4200;
my $p = $scala->freq2pitch($freq);
# how off is the frequency from the given scale and concertfreq?
my $pitch_freq = $scala->pitch2freq($p);
my $error = $freq - $pitch_freq;
$p = $Lyu->p2ly($p) if $Flag_Lyout;
my $percent = abs($error) / $pitch_freq * 100;
printf "%.2f\t%s\t%+.2f\t%.2f%%\n", $freq, $p, $error, $percent;
}
}
sub gen_melody {
my (@args) = @_;
$Flag_Record_Sep = ' '; # for easier feeding to ly-fu
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->gen_melody, rs => $Flag_Record_Sep );
}
sub half_prime_form {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( scalar $Atu->half_prime_form( args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub interval_class_content {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'scaledegrees|sd=i' => \my $sd );
$Atu->scale_degrees($sd) if $sd;
emit_pitch_set(
scalar $Atu->interval_class_content( args2pitchset(@args) ),
lyflag => 0,
rs => '',
);
}
sub intervals2pcs {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'pitch|n=s' => \my $start_pitch )
or print_help();
$start_pitch = $Lyu->notes2pitches( $start_pitch // 0 );
$Lyu->ignore_register(0);
emit_pitch_set( $Atu->intervals2pcs( $start_pitch, args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub invariance_matrix {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts );
emit_pitch_set(
$Atu->invariance_matrix( args2pitchset(@args) ),
bin/atonal-util view on Meta::CPAN
my $beat_ms = bpm_to_ms( $tempo, $beats );
my ( $scala, $p2f ) = _init_scala( $concert_freq, $concert_pitch, $scala_file );
$Lyu->ignore_register(0);
$Lyu->keep_state(1);
$Lyu->sticky_state(1);
if ( defined $relative ) {
if ( $relative =~ m/^\d+$/ ) {
die "error: relative must be note name\n";
}
$Lyu->mode('relative');
$Lyu->prev_note($relative);
} else {
$Lyu->mode('absolute');
}
my @notes;
if ( !@args or ( @args == 1 and $args[0] eq '-' ) ) {
chomp( @args = readline *STDIN );
}
# split input, as lilypond ' really do not suit the Unix shell, so
# are best enclosed in "" blocks
for my $arg (@args) {
push @notes, split ' ', $arg;
}
if ( !@notes ) {
die "Usage: $0 ly2pitch [--relative=note] [-|notes...]\n";
}
my $prev_dur = $beat_ms / 4;
for my $note (@notes) {
my $pitch = $Lyu->notes2pitches($note);
die "pitch '$pitch' out of range\n"
if defined $pitch and ( $pitch < 0 or $pitch > 108 );
my $duration;
if (
$note =~ m{ (?<dur>\d+) (?<dots>[.]+)? (?:\*(?<rnumer>\d+)/(?<rdenom>\d+))? }x )
{
my $dur = $beats / $+{dur};
my $dots = exists $+{dots} ? length $+{dots} : 0;
my $ratio = exists $+{rnumer} ? $+{rnumer} / $+{rdenom} : 1;
$dur *= $ratio;
$duration = ( 2 * $dur - ( $dur / 2**$dots ) ) * $beat_ms;
}
$duration //= $prev_dur;
my $freq = ( defined $pitch and $pitch != 0 ) ? $p2f->($pitch) : 0;
# zero precision due to Arduino tone call accepting integers
printf "\t{ %.0f, %.0f },\t/* %s */\n", $freq, $duration, $note;
$prev_dur = $duration;
}
}
sub multiply {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'factor|n=s' => \my $factor, )
or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
$factor //= 1;
die "factor must be number\n" unless looks_like_number $factor;
emit_pitch_set( $Atu->multiply( $factor, args2pitchset(@args) ) );
}
sub normal_form {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( ( $Atu->normal_form( args2pitchset(@args) ) )[0],
rs => $Flag_Record_Sep );
}
sub notes2time {
my (@args) = @_;
my $beats = 4;
my $tempo = 60;
my $fraction = 1;
GetOptionsFromArray(
\@args,
'beats=i' => \$beats,
'ms!' => \my $in_ms,
'tempo=i' => \$tempo,
'fraction=s' => \$fraction,
) or print_help();
if ( !looks_like_number($fraction) ) {
if ( $fraction =~ m#^(\d+)/(\d+)$# ) {
$fraction = $1 / $2;
} else {
die "unknown fraction: $fraction (expect 2/3 or such)\n";
}
}
my @durations;
for my $notespec ( map { split ' ' } @args ) {
if ( $notespec =~
m{ (?<dur>\d+) (?<dots>[.]{1,10})? (?:\*(?<rnumer>\d+)/(?<rdenom>\d+))? }x ) {
my $dur = $beats / $+{dur};
my $dots = exists $+{dots} ? length $+{dots} : 0;
my $ratio = exists $+{rnumer} ? $+{rnumer} / $+{rdenom} : 1;
# Arbitrary limit to just nine dots, which is about three times longer
# than I think I've ever seen in notation.
die "error: too many dots in dotted note\n" if $dots > 9;
$dur *= $fraction;
$dur *= $ratio;
push @durations, 2 * $dur - ( $dur / 2**$dots );
} elsif ( !@durations ) {
die "unable to parse duration from '$notespec'\n";
} else {
# assume repeated duration
push @durations, $durations[-1];
}
}
my $beat_ms = bpm_to_ms( $tempo, $beats );
for my $d (@durations) {
my $d_ms = $d * $beat_ms;
print( ( $in_ms ? $d_ms : _ms2abbr_time($d_ms) ), "\n" );
}
if ( @durations > 1 ) {
my $total_ms = sum(@durations) * $beat_ms;
print( '= ' . ( $in_ms ? $total_ms : _ms2abbr_time($total_ms) ), "\n" );
}
}
sub pcs2forte {
my (@args) = @_;
my $fn = $Atu->pcs2forte( args2pitchset(@args) ) || "";
print $fn, "\n";
}
sub pcs2intervals {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->ignore_register(0);
emit_pitch_set(
$Atu->pcs2intervals( args2pitchset(@args) ),
lyflag => 0,
rs => $Flag_Record_Sep,
);
}
sub pitch2freq {
my (@args) = @_;
my $emit = 1;
if ( ref $args[0] ne '' ) {
shift @args;
$emit = 0;
}
my $mode = 'absolute';
GetOptionsFromArray(
\@args,
'concertfreq|cf=s' => \my $concert_freq,
'concertpitch|cp=s' => \my $concert_pitch,
'relative=s' => \my $relative,
'scala=s' => \my $scala_file,
) or print_help();
my ( $scala, $p2f ) = _init_scala( $concert_freq, $concert_pitch, $scala_file );
$Lyu->ignore_register(0);
$Lyu->keep_state(1);
$Lyu->sticky_state(1);
if ( defined $relative ) {
if ( $relative =~ m/^\d+$/ ) {
die "error: relative must be note name\n";
}
$Lyu->mode('relative');
$Lyu->prev_note($relative);
} else {
$Lyu->mode('absolute');
}
if ( !@args or ( @args == 1 and $args[0] eq '-' ) ) {
chomp( @args = readline *STDIN );
}
# If pitch must int() it, otherwise feed to lilypond for conversion so
# do not first need to call ly2pitch on the input.
my @ret;
for my $pitch (
map { looks_like_number $_ ? int $_ : $Lyu->notes2pitches($_) }
map { split ' ', $_ } @args
) {
die "pitch '$pitch' out of range\n" if $pitch < 0 or $pitch > 108;
if ($emit) {
printf "%d\t%.2f\n", $pitch, $p2f->($pitch);
} else {
push @ret, $p2f->($pitch);
}
}
return \@ret unless $emit;
}
sub pitch2intervalclass {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
die "$0 pitch2intervalclass pitch\n"
unless defined $args[0] and $args[0] =~ m/^\d+$/;
print $Atu->pitch2intervalclass( $args[0] ), "\n";
}
sub pitch2ly {
my (@args) = @_;
my $mode = 'absolute';
GetOptionsFromArray( \@args, @Std_Opts, 'mode=s' => \$mode, )
or print_help();
# Not the default, so if things persist or chain due to some rewrite,
# would need to save the old or create a new object or whatever
$Lyu->keep_state(1);
$Lyu->mode($mode) if defined $mode;
my @pitches;
if ( !@args or ( @args == 1 and $args[0] eq '-' ) ) {
chomp( @args = readline *STDIN );
for my $arg (@args) {
push @pitches, split ' ', $arg;
}
} else {
@pitches = @args;
}
print join( ' ', $Lyu->p2ly(@pitches) ), "\n";
}
sub prime_form {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->prime_form( args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub print_help {
warn <<"END_USAGE";
Usage: atonal-util [options] mode mode-args
Atonal music analysis utilities. Options:
bin/atonal-util view on Meta::CPAN
$0 invert --axis=3 0 3 6 7
The following require two pitch sets; specify the pitch sets on STDIN
(one per line) instead of in the arguments:
variances Emits three lines: the intersection, the difference,
and the union of the supplied pitch sets.
zrelation Emits 1 if pitch sets zrelated, 0 if not.
Example:
(echo 0,1,3,7; echo 0,1,4,6) | $0 zrelation
There is also a 'basic' mode that computes both the prime form and
interval class content (and Forte Number, if possible):
$0 --ly basic c e g
Run perldoc(1) on atonal-util for additional documentation.
END_USAGE
exit 64;
}
sub recipe {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'file=s' => \my $rfile )
or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
my $ps = args2pitchset(@args);
my $wps = [@$ps];
open my $fh, '<', $rfile or die "could not open '$rfile': $!\n";
eval {
while ( my $line = readline $fh ) {
my ( $method, @margs ) = split ' ', $line;
next if !$method or $method =~ m/^[\s#]/;
chomp @margs;
die "not a ", ref $Atu, " method" unless $Atu->can($method);
$wps = $Atu->$method( @margs, $wps );
}
};
die "recipe error at '$rfile' line $.: $@" if $@;
emit_pitch_set( $wps, rs => $Flag_Record_Sep );
}
sub retrograde {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->retrograde( args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub rotate {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'rotate|n=s' => \my $r, )
or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
$r //= 0;
die "rotate must be number\n" unless looks_like_number $r;
emit_pitch_set( $Atu->rotate( $r, args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub set2beats {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'scaledegrees|sd=i' => \my $sd, )
or print_help();
if ($sd) {
$Atu = Music::AtonalUtil->new( DEG_IN_SCALE => $sd );
} else {
$sd = $Atu->scale_degrees;
}
$Lyu->ignore_register(0) if $sd != 12;
my $pset = args2pitchset(@args);
my %set;
@set{@$pset} = ();
my $beats;
for my $n ( 0 .. $sd - 1 ) {
$beats .= exists $set{$n} ? 'x' : '.';
}
print $beats, "\n";
}
sub set_complex {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( $Atu->set_complex( args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub subsets {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'length|len=i' => \my $l, )
or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
$l //= -1;
emit_pitch_set( $Atu->subsets( $l, args2pitchset(@args) ),
rs => $Flag_Record_Sep );
}
sub stdin2pitchsets {
my @ss;
while ( my $line = readline *STDIN ) {
my @pset;
if ( $line =~ m/($FORTE_NUMBER_RE)/ ) {
@pset = @{ $Atu->forte2pcs($1) };
die "unknown Forte Number '$1'\n" if !@pset;
} else {
for my $p ( $line =~ /([-\d\w]+)/g ) {
push @pset, $Lyu->notes2pitches($p);
}
}
push @ss, \@pset;
}
return \@ss;
}
sub tcs {
my (@args) = @_;
emit_pitch_set( $Atu->tcs( args2pitchset(@args) ), lyflag => 0 );
}
sub tcis {
my (@args) = @_;
emit_pitch_set( $Atu->tcis( args2pitchset(@args) ), lyflag => 0 );
}
sub tension {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Flag_Tension = 'cope' unless defined $Flag_Tension;
_init_tension();
my ( $t_avg, $t_min, $t_max, $t_ref ) =
$Tension->vertical( args2pitchset(@args) );
printf "%.03f %.03f %.03f\t%s\n", $t_avg, $t_min, $t_max,
join( ',', @$t_ref );
}
sub time2notes {
my (@args) = @_;
my $beats = 4;
my $tempo = 60;
GetOptionsFromArray(
\@args,
'beats=i' => \$beats,
'tempo=i' => \$tempo,
) or print_help();
for my $durms (@args) {
die "argument $durms not a number\n" if !looks_like_number $durms;
my $dur = 1 / ( ms_to_bpm( $durms, $beats ) / $tempo );
# Cheat with lilypond multiplier syntax; breaking things down into c4~ c16.
# would of course be more work.
$dur = 100 * sprintf "%.2f", $dur;
print "c$beats*$dur/100\n";
}
if ( @args > 1 ) {
my $total_ms = sum(@args);
my $dur = 1 / ( ms_to_bpm( $total_ms, $beats ) / $tempo );
$dur = 100 * sprintf "%.2f", $dur;
print "= c$beats*$dur/100\n";
}
}
sub transpose {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts, 'transpose|n=s' => \my $t, )
or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
$t //= 0;
my $pset = args2pitchset(@args);
# if a number, transpose by that; if note, transpose to that note
if ( !looks_like_number($t) ) {
$t = $Lyu->notes2pitches($t) - $pset->[0];
}
emit_pitch_set( $Atu->transpose( $t, $pset ), rs => $Flag_Record_Sep );
}
sub transpose_invert {
my (@args) = @_;
GetOptionsFromArray(
\@args, @Std_Opts,
'axis|a=s' => \my $axis,
'transpose|t=s' => \my $t,
) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
my $pset = args2pitchset(@args);
$axis = defined $axis ? $Lyu->notes2pitches($axis) : 0;
# if a number, transpose by that; if note, transpose to that note
$t //= 0;
if ( !looks_like_number($t) ) {
$t = $Lyu->notes2pitches($t) - $pset->[0];
}
emit_pitch_set( $Atu->transpose_invert( $t, $axis, $pset ),
rs => $Flag_Record_Sep );
}
sub variances {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
emit_pitch_set( [ $Atu->variances( @{ stdin2pitchsets() } ) ],
rs => $Flag_Record_Sep );
}
sub whatscalesfit {
my (@args) = @_;
GetOptionsFromArray( \@args, @Std_Opts ) or print_help();
$Lyu->chrome('flats') if $Flag_Flat;
my $pset = args2pitchset(@args);
for my $p (@$pset) {
$p %= $scale_degrees;
}
my @scales = qw/major dorian phrygian lydian mixolydian aeolian locrian blues/;
push @scales, "harmonic minor", "melodic minor", "hungarian minor";
for my $scale (@scales) {
my @asc = get_scale_nums($scale);
_fit_scale( $scale, $pset, \@asc );
my @dsc = get_scale_nums( $scale, 1 );
if ( join( ' ', @asc ) ne join( ' ', reverse @dsc ) ) {
_fit_scale( $scale, $pset, \@dsc, 1 );
}
}
}
sub zrelation {
emit_pitch_set( [ $Atu->zrelation( @{ stdin2pitchsets() } ) ], lyflag => 0 );
}
END {
# Report problems when writing to stdout (perldoc perlopentut)
unless ( close(STDOUT) ) {
die "error: problem closing STDOUT: $!\n";
}
}
__END__
=head1 NAME
atonal-util - routines for atonal composition and analysis
=head1 SYNOPSIS
Prime form and APIC vector for a pitch set:
( run in 1.424 second using v1.01-cache-2.11-cpan-d8267643d1d )