PR(\aiMel)


A melody player that automatically generates variations on input material.


Initialization


To begin playing, you need to chuck the BP instance into a VC to specify what should play the notes, and chuck in a MIDIRecBuf holding the notes to play.


Note: Code in this help file is intended for illustration only. Proper execution depends on resources that will not be created in this file. For a working example, see ChuckExamples.


PR(\aiMel) => BP(\mel); // create the instance

BP(\mel) => VC(\instrument); // assign to a Voicer to render the notes

melodyMIDIRecBuf =>.mel BP(\mel);


Whenever you chuck a MIDIRecBuf into this process, if you don't specify an adverb (.mel in the statement above), it will look to the properties stored in the MIDI buffer. To specify that a MIDI buffer contains source material, set the properties as illustrated below.


Throughout this document, "parameters" refers to data which, as above, may be a member of the properties collection belonging to MIDI buffer, or can be overridden using the parms dictionary in a chuck operation.


// Using a MIDI buffer property to indicate the buffer type.

// I assume here that you're using [MBM] to store MIDI buffers.

// You could also keep it in a variable.

MIDIRecBuf(\melody, [... note array ...], (type: \mel)) => MBM(0);

MBM(0)[\melody] => BP(\mel);


// Using a chuck parameter to indicate the buffer type.

MBM(0)[\melody].chuck(BP(\mel), parms: (type: \mel));


Several properties/parameters may be set at the same time.


Modal representation


The MIDI buffers should contain the pitches in a chromatic representation, typically MIDI note numbers but you can use a different representation for non twelve-tone temperaments. It converts the notes to a modal representation by reference to a ModalSpec object, which may be held in the Mode storage class.


If no mode is specified, Mode(\default) is used. You may specify the mode as a property of the MIDI buffer, or as a chucking parameter.


// Assumes that you have defined Mode(\cmaj).

MIDIRecBuf(\melody, [... notes array ...], (type: \mel, mode: \cmaj)) => MBM(0);


// Or, override the buffer parameter.

MBM(0)[\melody].chuck(BP(\mel), parms: (mode: \gmaj));


Splitting into phrases and segments


For additional playback control, and to treat different parts of the melody individually while generating variations, the input data are split into phrases and segments.


Phrases are the outer layer of organization. Specify the algorithm to split the notes using the splitFunc parameter (which, like mode, can be given in the MIDI buffer properties or chuck parameters). The value is the name of a Func() object, whose function does the work. The following are provided in the distribution:


\noSplit: Do not split -- keep everything in one big phrase.

\defaultMelSplit: Calculate split points based on rhythm (tends to split after longer notes).

\userSplit: The user specifies the number of notes in each phrase in the \phrSplits parameter.

\barMelSplit: Given a \barLength parameter, split the melody so that each phrase has exactly that number of beats. Dummy rests are inserted when a note spans a barline.


During playback, you can insert rests between phrases by assigning or chucking a numeric pattern to the ~macro variable in the process.


Pwhite(1, 4, inf) =>.macro BP(\mel); // rest randomly 1, 2, 3 or 4 beats between each phrase

// or:

BP(\mel).macro = Pwhite(1, 4, inf); // same result


Segments are the basic unit for generating variations.


\noSplit: Do not split -- keep the whole phrase in one segment.

\defaultMelSegmenter: Look for larger than average intervals as places to break segments.


You may, of course, write your own functions -- just chuck the function into a Func storage object, and reference it by name in the parameters. See the definitions of these functions for their arguments.


Generating variations ("adaptations")


To generate variations, the process needs to know what secondary material to introduce into the source and, for each segment, which Func to use to produce the variation.


// Use the "adapt" adverb...

MBM(0)[\adaptationNotes] =>.adapt BP(\mel);


// Or set type: \adapt in the buffer properties or chuck parameters.

// While we're at it, let's set its mode as well.

MIDIRecBuf(\adaptationNotes, [... note array ...], (type: \adapt, mode: \cmaj)) => MBM(0);


The Func names are drawn from a pattern that returns symbols.


Prand([\absSplice, \intSplice, \delete], inf) =>.adapt BP(\mel);


// To save time in performance, you can define the adaptation symbol pattern as a Pdefn,

// and use the Pdefn name to chuck into the adapt adverb.

// This works only with chucking syntax; variable assignment syntax does not work in that case.

Pdefn(\adaptKeys, Prand([\absSplice, \intSplice, \delete], inf));

\adaptKeys =>.adapt BP(\mel);


The adaptation functions in the distribution are:


\absSplice: Splice adaptation notes into the source notes with no transposition.

\intSplice: Splice adaptation notes, preserving the intervals at the splice points by transposing material as needed.

\fixedSplice: Splice adaptation notes, dropping source notes so that the resulting segment has exactly the same rhythmic duration as the source.

\delete: Delete notes from the source. Adaptation notes are ignored.

\delHoldDur: Delete notes from the source, and add rests so that the resulting segment has exactly the same rhythmic duration as the source.

\dropNote: Remove one note from the source, but preserve the other notes' rhythm.

\noteSwap: Switch the positions of two of the source notes

\splitNote: Divide a source note into two, randomly generating a pitch for the second note within the range of the segment.

\splitNote2: Divide a source note into two, but the second note may go just outside the range of the segment.


Testing variations for validity


Since machine generated variations might not fit in with the existing material, each variation is tested using a Func. The function returns no less the variation is okay, or a score indicating its degree of unacceptability -- the larger the number, the worst the fit. Once a segment object reaches the maximum number of variations it can contain, whichever variation has the worst score will be deleted. (The original material always remains in place.)


The test function name should be assigned to the process' eugTest variable. (Inspired by the terminology of genetic programming, and apparently unconcerned with the unsavory history of the word, I chose "eug" as short for "eugenicize.")


// Test that the new variation remains within the range given in the ~range variable.

PR(\mel).eugTest = \eugRangeOnlyTest;

PR(\mel).range = NumericRange(14, 35);


The following functions are supplied in the distribution:


\dummyEugTest: Step validation completely -- every variation is accepted without question. (When there are too many variations in a segment already, the oldest one is removed.)

\eugRangeOnlyTest: Test only the range of notes in the new variation. Specify a range, as above, in the range variable as a NumericRange object, using modal representations of the low and high notes. MIDI note numbers are not OK here.

\eugRangeTest: Performs the same test as in eugTest, plus the range test.

\eugTest: Each segment calculates some metrics to try to describe the melody in terms of overall contour, angularity and similar factors. This function is a crude attempt to match the metrics from the new variation against those of the original material. It's too crude to be really musically useful at this point.


Rhythm profiles


You might want to replace the source material's rhythm with algorithmically generated note values.


Before chucking the melody in, set which rhythm profile you would like to use:


BP(\mel).rhythmProfileProto = \randRhythm; // e.g.


Then, before playback, set the useRh flag to activate the rhythm profile for sequencing.


BP(\mel).useRh = true;


PR(\randRhythm): For each note, randomly choose a note value from any of the source melody's notes.

PR(\markovRhythm): Uses Julian Rohrhuber's MarkovSet class to build a first order Markov chain of the note values in the source melody, and streams out rhythmic values according to the chain. Before constructing the Markov chain, the melody is quantized by the factor given in BP(\mel).rhythmQuant; by default it is 0.25, or 16th notes. (If the melody is not quantized, likely every duration will be slightly different, defeating the purpose of Markov analysis!)


Other parameters


BP(\mel).adaptProb -- By default this is 1.0. The probability of generating a new variation upon entering a segment.

BP(\mel).variantThreshold -- The maximum number of variants one segment can hold before variants get killed off.

BP(\mel).repeats -- How many times to play through all segments before stopping. Normally inf.

BP(\mel).storageProto -- The name of the PR object to use for storage of melody components. Usually \melodyStorage, but you could use \melRandStorage to shuffle the phrases at play time in random order.

BP(\mel).mel.makePhrPattern -- A function creating a pattern to output the phrase objects in the order desired for play. By default it returns a Pseq. You could replace it with a different function, to output the phrases in any arbitrary order. If you change this function, do it after chucking in the source melody but before play.