Demonstration 8 provided the first of two practical components for Chapter 11: “Musical Design II: — Grammars” of my unpublished textbook on composing programs. It illustrates the method of generative grammars and, not coincidentally, the principle of top-down design. This method produces complex musical statements by starting with a fairly simple archetype and applying a set of fairly simple rewrite rules, also known as productions. The productions are applied recursively, which means that productions are capable of operating upon their own products.
Demonstration 8 consists of three consecutive versions (variations) of a basic tune — or in linguistic jargon, three consecutive elaborations of an archetype. Each successive variation ornaments the tune by increasing orders of embellishment. The grammar of Demonstration 8 is ‘functional’ in the sense that the notes of the basic tune directly constitute grammatical units at the highest structural level, while all other notes may be regarded as ‘ornamental’ with varying levels of significance. The productions used to embellish both the basic tune and the higher-level ornaments are drawn from traditional formulae: seconds are embellished by either escaping tones or reaching tones; thirds by changing-note groups (“cambiata”).
The basic tune is illustrated in Figure 1 while the ornaments appear in Figure 2. Notice that the basic tune and each of the ornaments consist solely of seconds and thirds. It is this consistent property which enables the ornamenting process to be applied recursively up to five levels (counting the archetype as level 1): not only does the program elaborate upon the tune itself, but also upon the ornaments, upon the ornaments upon the ornaments, and so forth. Notes from the basic tune receive durations of 8 sixteenths; ornamental notes receive durations of 5, 3, 2, and 1 sixteenths, depending on their recursive levels.
The transcribed product appears in Figure 3.
The trick to implementing top-down recursion in
FORTRAN, which does not employ a system stack and which therefore
does not permit self-referencing code modules, is to set up an explicit collection of parallel stacks so that all of the
attributes characterizing a grammatical unit at an arbitrary level of recursion can be accessed using a single index.
Listing 1: Program
LEVEL provides a single
stack pointer, while the various stacks indexed by this pointer are all implemented as arrays with names ending in
LEVEL and its associated stack arrays are all declared in common memory, which makes them accessible
DEMO8 proper and to the program's subroutines.
Notice also that none of
LEVEL and its associated stack arrays are initialized in the
BLOCK DATA routine. These variables will provide active workspace whose
content will frequently be overwritten.
The following explanations seek to illustrate recursive grammars in play. The explanations are peppered with line numbers, but you are are by no means expected to chase down every line of code. Rather, you should follow through with line numbers only when you have a specific question that the narrative is not answering.
The loop spanning lines 16-26 is indexed by
It iterates through Demonstration 8's three versions; line 19's call to subroutine
generates the individual versions.
The only explicit parameter in this call is
ITIME, which holds the current score time in thirty-second notes.
Understand that FORTRAN passes subroutine arguments by address rather than by value, so that anything
ITIME also affects
ITIME's value here in the main program.
COMPOS takes the
The three-element array
It also passes a value from array
EMBVER to the first location
EMBLEV; this value controls the number of nested ornaments.
The heart of the compositional process is subroutine
COMPOS, presented in Listing 3.
Although the body of this subroutine is implemented as a loop (lines 23-49), this is not your normal iterative sequence.
The loop indexing is bi-directional:
LEVELiterates from the top down and back, where
IDXiterates from left-to-right in the time dimension, where
To complicate things further, each change in
LEVEL disrupts the left-to-right progression of
COMPOS descends to a lower level, it needs set aside (“push”) the current value of
IDX so that
this value can be reclaimed (“pop”) once lower-level processing has completed.
That is what a stack is for.
In particular, array
IDXLEV keeps track of
IDX at the various recursive levels, while array
stores corresponding limits. Each loop iteration begins first by advancing
IDX one position to the right (line 25), then
immediately tests (line 26) whether
IDX has reached its limit.
COMPOS descends to the next (deeper) level in line 40 and immediately thereafter initializes
COMPOS ascends to the previous (shallower) level in line 47, but the value of
IDX is not reclaimed
until the loop iterates (line 25).
COMPOS keeps track of the levels of levels of recursion, each of which ornaments a melodic fragment passed down
from the previous level. The variable
LEVEL indicates the level of recursion, array element
indicates how many items occur in a fragment, and array element
IDXLEV(LEVEL) indicates which of these items
is currently under consideration.
For increased efficiency,
COMPOS transfers this indicator to the holding variable
The pitch of the current item is stored in array element
PCHLEV to the basic tune (lines 13-18), which
is specified to the program as array
TUNE and populated in line 8 of the
BLOCK DATA routine
presented in Figure 4 (d).
Items at lower levels may be either notes or rests; rests are represented as notes with null pitches.
For each item but the last in a fragment,
COMPOS performs the following actions:
WNOTEto append it to the human-readable listing (line 34 for rests, line 36 for notes). The duration of a note is stored in array element
DURLEV(LEVEL)as populated in line 9 of the
EMBLEV(LEVEL)) has yet to be reached, then
COMPOSdoes two things:
DEDUCEto select a possible ornament at the next level down (line 38)
COMPOSthen advances to the next recursive level (lines 40-41).
The last note in a fragment at the current level is the same as the next note in the fragment at the previous level, so
COMPOS backtracks one level (lines 46-47) when
DEDUCE is presented in Listing 4.
Although the subroutine has no formal arguments, information is
passed along from subroutine
DEDUCE through common memory variables. In particular,
gives the current level of recursion,
LEVEL1 gives the level plus 1,
IPCH1 gives the origin pitch, and
gives the goal pitch.
For each pair of consecutive pitches
passed to it either by subroutine
COMPOS, or by a prior level of recursion, subroutine
first decides whether or not to ornament at the current level.
For each pair of consecutive pitches
passed to it either by subroutine
COMPOS, or by a prior level of recursion, subroutine
first decides whether or not to ornament at the current level. If so
DEDUCE first identifies a repertory of
suitable ornaments, then chooses a specific ornament from this repertory.
The decision to ornament (lines 24-31) is a yes-or-no trial which incorporates feedback from corresponding trials at higher
Line 24 executes this decision with a call to the library function
where the probability of a positive outcome is
Here the numerator reflects how many choices-to-ornament are to be made from the current recursive level on down, while the
denominator reflects the number of levels remaining, and thus the number of opportunities left.
EMBLEV(LEVEL1) = EMBLEV(LEVEL)-1.0.
DEDUCEthen proceeds to select which ornament to apply.
EMBLEV(LEVEL1) = EMBLEV(LEVEL).
DEDUCEthen skips the remaining actions and returns immediately to
If it decides in favor of ornamenting an interval at the current level, lines 35-44 of
DEDUCE determine what kind of interval it must
ornament. It locates a repertory of ornaments for this level among the eight repertories illustrated in Figure 2.
All ornamental pitches are expressed (lines 12-19) in array
ORNMNT as offsets from the origin pitch.
How this data is accessed exemplifies the index gymnastics needed to represent non-rectangular data in
For each of the eight intervals, array
PTRORN holds a pointer (array index) indicating which element of
ORNMNT holds the first
offset for the first ornament in the repertory corresponding to the indicated interval.
INCORN holds the number of ornamental pitches (1 for escaping or reaching tones, 2 for changing-note groups);
incrementing the pointer by this number gives successive ornaments in the repertory.
NUMORN indicates the number of ornaments in the repertory.
For example, Table 1 dereferences from
ORNMNT the various ornaments which would apply to
the second interval in Figure 1. This interval ascends from
TUNE(2)=53 (F4) to
The pitch number 53 comes into
DEDUCE through the common variable
while the pitch number 57 comes through
Line 35 calculates the index
I into arrays
IPCH2-IPCH1+5=57-53+5=4+5=9. Array element
PTRORN(9) contains the index value 37.
This is the leftmost position in the seventh line of the multi-line data statement which initializes
ORNMNT (line 18 of the listing).
The number of ornamental pitches in the present example
INCORN(9)=2, which is why Table 1 dereferences
ORNMNT elements in pairs. The number of ornaments
NUMORN(3)=6; therefore Table 1
dereferences six ornaments. These ornaments correspond to the second-from-bottom line of staves in Figure 2.
The six indices 37, 39, 41, 43, 45, and 47 are gathered into the scheduling array
Once it has established a schedule of pointers to ornaments, the next step is to select a specific ornament.
DEDUCE does this in lines 48-61. Selection is heuristic with cumulative feedback, driven by chromatic usage.
This process is uncomplicated by constraints, since the ornaments listed in
SCHED are precisely those deemed
suitable for the present context.2
A complication does exist, however, and this is posed by the changing-note groups: how to reconcile the contributions from
two separate chromatic numbers. The tactic for reconciling such competing influence is called the minimax
Referring to Table 1, consider the ornament E4-G#4 in row 2 and the ornament E!4-G4 in row 4. Assume that E and G# have been moderately used, that E! has been lightly used, and that G4 has been heavily used. Since G4 has already been heavily used, choosing E!4-G4 would throw G4 even more strongly out of balance, despite the leveling effect of using an E. Thus E4-G#4 is the more sensible choice. Thus the minimax principle at work, in a rudimentary way.
The first thing is to eliminate any bias introduced by the physical sequence
of ornaments in array
ORNMNT. This was started by gathering indices into array
SCHED (previous step). Bias-elimination
continues in line 44 by calling the library routine
SHUFLE to randomize the content-order
SCHED and in line 51 by using the index
IDXORN of the ornament-evaluating loop to dereference an ornament locator
DEMO08 maintains statistics of chromatic usage in the 12-element array
DEDUCE through common memory. Within the current code segment, variable
CUM0 holds the smallest
CUMDEG value discovered for an ornament so far; this variable is set very large in line 48 and is reduced in line 58 each time
a more preferable ornament is discovered. Similarly, variable
CUM1 holds the largest
CUMDEG value discovered for the candidate ornament; this variable is set very small in line 50 and is increased in line 54 each time
CUMDEG value is discovered. Coming out of this code segment, an index to the selected ornament has been placed
Having selected an ornament, the next step is to marshal details about the ornament, plus its bracketing origin and goal pitches, into
PCHLEV stack for transmission to the next level of recursion. This happens in lines 65-76. The circumstance of extracting
individual pitches for the ornament also makes this a convenient place to increment chromatic usage in
in line 72 that the incrementing amount is provided by
DURLEV(LEVEL+1). Thus upon program completion
will reflect not the total count of notes employing the
Ith chromatic degree, but rather the combined duration of such notes.
The final action
DEDUCE happens in lines 80-91.
This code employs a Bernoulli trial to decide whether or not to include a rest in the ornament.
If the trial outcome is favorable, then
DEDUCE inserts a rest between two consecutive notes of the ornamented fragment.
|© Charles Ames||Original Text: 1984-11-01||Page created: 2017-03-12||Last updated: 2017-03-12|