NAME
Music::VoiceGen - musical voice generation
SYNOPSIS
use Music::VoiceGen;
# C4 to A4 in the C-Major scale, allowing major and minor
# seconds and thirds ascending and descending, equal odds
# of (allowed) intervals
my $voice = Music::VoiceGen->new(
pitches => [qw/60 62 64 65 67 69/],
intervals => [qw/1 2 3 4 -1 -2 -3 -4/],
);
# get eight random notes into a string
join ' ', map { $voice->rand } 1..8
# see what the possibilities are
use Data::Dumper;
print Dumper $voice->possibles;
# force a start from a particular note (use before ->rand
# is called)
$voice->context(60);
# set custom possibilities
$voice->update(
{ 60 => { 62 => 8, 64 => 4, 65 => 1 },
62 => { 60 => 1, ... },
...
}
);
# or the same thing via new (instead of pitches & intervals)
Music::VoiceGen->new( possibles => { ... } );
# pitches and intervals can be weighted via a custom function;
# this one makes descending intervals more likely
my $voice = Music::VoiceGen->new(
pitches => [qw/60 62 64 65 67 69/],
intervals => [qw/1 2 3 4 -1 -2 -3 -4/],
weightfn => sub {
my ($from, $to, $interval) = @_;
$interval < 0 ? 3 : 1
},
);
DESCRIPTION
This module offers the ability to generate a voice (a series of notes or melody) using only certain pitches and intervals, or otherwise a custom set of possible choices (via a hash of hashes) that a given pitch (an integer) will move to some other pitch. The design suits choral work, where leaps of a tritone or similar must be forbidden, and the range of pitches confined to a certain ambitus. With suitable input this module could be made to produce more chromatic lines over larger ranges.
Walker's alias method (via Math::Random::Discrete) is used to efficiently select weighted random values. The Moo documentation may be helpful to understand the source and some of the terminology used in this documentation.
CONSTRUCTOR
The new method accepts any of the "ATTRIBUTES". The pitches and intervals attributes must be set, or otherwise custom possibles must be supplied.
An additional weightfn parameter may be supplied to new when using pitches and intervals; this parameter must be a code reference that will be called with the starting pitch, destination pitch, and interval, and should return a numeric weight (the default is to evenly weight available possibilities). The weightfn is not relevant if possibles is used; that data structure manually includes the weights.
ATTRIBUTES
- _choices
-
Where the Math::Random::Discrete lookup tables are stored. This is an internal detail that may change in future releases.
- _context
-
The previous notes used by rand, if any. Limited by the MAX_CONTEXT attribute, and only relevant if the possibles take context into account. Use instead the context or clear_context methods to interact with the contents of this attribute.
- contextfn
-
A code reference that is called by rand when _context is available, arguments being the previous choice (which will be undef on the first call), a Math::Random::Discrete object, and a counter that indicates how many times the contextfn has been called inside this rand call. Return values should be the choice, and a boolean that if true will stop the loop through available _context. The following example shows a weighted sampling algorithm (see the "random line" entry in perlfaq5 for background) that prefers to use a selection from the longest context, but may sometimes instead use a choice from a shorter context chain.
$voice->contextfn( sub { my ( $choice, $mrd, $count ) = @_; if ( CORE::rand( $count + ( $count - 1 ) / 2 ) < 1 ) { $choice = $mrd->rand; } return $choice, 0; } );
- intervals
-
A list of allowed intervals a voice is allowed to make, by positive and negative semitones for ascending and descending melodic motion. A common set would allow oblique motion (
0
), intervals up to a minor sixth in both directions (-8
,8
), the octave, but not the tritone:qw/0 1 2 3 4 5 7 8 12 -1 -2 -3 -4 -5 -7 -8 -12/
Only unique intervals are used. That is, specifying
0 1 2 3 3 3 ...
to intervals will not increase the odds that an ascending minor third is used. Intervals can be weighted differently via the weightfn attribute, or by supplying custom possibles.Intervals are only allowed where the resulting pitch exists in the pitches attribute, so the number of possible pitches from a given pitch will be limited, especially if the pitch is near an extreme of the pitch range, or if the ambitus is limited, or if the intervals are a poor fit for the allowed pitches.
Setting this attribute outside of new will have no effect (use the update method instead to change the odds).
The intervals are otherwise only for reference, and will be wiped out should an update call be made without the preserve option. intervals will not be set if custom possibles are passed to new.
- MAX_CONTEXT
-
How many context notes to retain (1 by default). Higher values will have no effect (save for burning needless CPU cycles) unless appropriate possibles have been supplied.
- startfn
-
A code reference called by rand when there is no available _context. This call is passed a list of possible starting items as a list reference, and should return a value in that list to be used as the starting point.
- pitches
-
What pitches are allowed for the voice, in semitones as integers. The
ly2pitch
mode ofatonal-util
(via App::MusicTools) may be handy to convert lilypond note names into appropriate pitch numbers, as well as theinterval_class_content
calculation (see docs in Music::AtonalUtil) that details what intervals (up to and including the tritone) are present in a set of pitches:$ atonal-util ly2pitch --relative=c\' c d e f g a bes c d e 60 62 64 65 67 69 70 72 74 76 $ atonal-util interval_class_content c d e f g a bes c d e 254361
Setting this attribute outside of new will have no effect (use the update method instead to change the odds).
The pitches are otherwise only for reference, and will be wiped out should an update call be made without the preserve option. pitches will not be set if custom possibles are passed to new.
- possibles
-
The possible choices for what pitches can be reached from a given pitch, with weights. Consider it read-only once the object has been created; changes to possibles should be made via the update method.
my $p = $voice->possibles; # ... alter $p as necessary ... $voice->update($p);
possibles may make use of context by providing choices for dot- joined strings of other possibilities:
my $voice = Music::VoiceGen->new( MAX_CONTEXT => 3, possibles => { 60 => { 65 => 1 }, "60.65" => { 67 => 1 }, 65 => { -1 => 1 }, "60.65.67" => { 65 => 1 }, }, ); $voice->context(60);
In this case,
60.65
and not65
would be used by the next call to rand, as that is a more specific choice. If a more specific choice is not available, then rand will fall back to using shorter and shorter chains. This behavior can be changed via the contextfn attribute.If there is context, and no pitch can be used, then rand will die with an exception. This is a known issue.
METHODS
- clear_context
-
Empties the current context, if any. The next call to rand will pick a starting possibility from an equal weighting of all available possibilities.
- context
-
With no arguments, returns the current context, an array reference that records previous results from rand up to the MAX_CONTEXT attribute. With an argument, sets the context to the provided list or array references.
Returns the object, so can be chained with other method calls.
- rand
-
Takes no arguments. Returns a random pitch, perhaps adjusted by any context, otherwise when lacking context picking with an equal chance from any of the pitches or top-level possibles supplied, unless the default startfn or contextfn attributes have be overridden and instructed to behave otherwise.
- subsets min max coderef list
-
Utility method, calls the given coderef with each of the min to max element subsets of the given list. In particular, this can be used to generate possibles from a given musical voice. For example, assuming a MAX_CONTEXT of 3, all possibles from one to three notes plus the destination pitch could be tallied via:
my %poss; $voice->subsets( 2, 4, sub { $poss{ join ".", @_[0..$#_-1] }{ $_[-1] }++ }, [qw/65 67 69 60 62/] ); use Data::Dumper; print Dumper \%poss;
Returns the object, so can be chained with other method calls.
- update possibles [ preserve_pitches => 1 ]
-
Offers the ability to update the possibles attribute (and also _choices) with the supplied reference to a hash of hash references. Unless the preserve_pitches parameter is supplied, the pitches and intervals, if any, will be wiped out by this call.
Returns the object, so can be chained with other method calls.
BUGS
Reporting Bugs
Please report any bugs or feature requests to bug-music-voicegen at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Music-VoiceGen.
Patches might best be applied towards:
https://github.com/thrig/Music-VoiceGen
Known Issues
It is fairly easy to trigger the "could not find a choice" error should a particular pitch be a dead end (when there are no allowed intervals leading from a pitch to any other allowed pitch), or if undef
has gotten into the possibles attribute. As a workaround, inspect the contents of the relevant attributes and remove or fix any such problems, e.g. for any dead-end pitches return a "stop" value that causes the calling code to not make additional calls to rand.
$voice->update( { 66 => { -1 => 1 }, ... } );
# and elsewhere...
while ($something) {
my $pitch = $voice->rand;
last if $pitch == -1;
}
Also, if there are possibilities at depth, these will always be used, unless a custom contextfn is supplied to sometimes not always select from the chain of most context.
SEE ALSO
MIDI::Simple or Music::Scala or Music::PitchNum have means to convert numbers into MIDI events, frequencies, or various forms of note names. Music::Tension::Cope is one method to score the consonance of resulting pitch sets, perhaps against the output of multiple voice generators each with their own set of allowed pitches.
Consult the eg/
and t/
directories under this module's distribution for more example code.
AUTHOR
thrig - Jeremy Mates (cpan:JMATES) <jmates at cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2016 by Jeremy Mates
This program is distributed under the (Revised) BSD License: http://www.opensource.org/licenses/BSD-3-Clause