Objective
To use HTML, CSS, JavaScript, and PHP to make a script that will list basic information (via music set theory) about a collection of pitches.
Sources
set-info.php (form using ‘post’ method and PHP code)
set-help.php (HTML help page)
set-table.php (array of set-classes for table lookup)
si-style.css (css styles)
Link: mikelkuehn.com/codex/set-info.php
Overview
Here’s a fascinating musical question: how many combinations of pitches are possible within one octave? Using some math, there are 212 subsets of 12 elements, yielding 4096 subsets of the octave. Since one of those is the empty (null) set, there are 4095 distinct ways to depress combinations of notes within one octave (from all single pitches, through various chords, to all 12 notes at once). We can think of these combinations as “chords” (though it wouldn’t be proper to use that musical term for the 12 that are just single notes). Those 4095 “chords” aren’t all unique in terms of their harmonic/structural content, many possess identical intervalic content. Using a “major triad” as an example, we could have several versions (C, E, G / F, A, C / E, G#, B / etc). As music evolved and tuning systems for Western music moved into the standard of equal temperament (12 equally spaced notes within the octave), composers began to think about ways to identify chords with a single “normalized” label. Musicians often give examples in the key of C to simplify a concept in order to compare it with other musical structures. We might talk about a C major triad but, in theory, know that it implies we could have 11 other major triads built on various roots (C#, D, D#, … A#, B). The American composer Elliot Carter was one of the first to make a list of all possible chords and give them each a name (though his list isn’t commonly known or used). While we already have labels for classes of common chords that occur in the periods of Bach, Beethoven, and Brahms (i.e., major, dominant 7th, diminished, etc.), there are other chords that don’t have standardized names—for example: C, C♯, E.
In his 1973 book, The Structure of Atonal Music, music theorist Allen Forte created a normalized list of all the classes of possible chords, which he called set classes. (“Set class” because each set of pitches can be assigned a single representation from many related possibilities — for example, think of using a C major triad as the representation for all major triads.) The term “prime form” describes the unique representation (in pitch-classes) of all intervalic-related chords of a specific cardinality (i.e., size). Forte’s book contains the first published list of set class labels and prime forms. For example the notation “3-8[026]” states that the set-class consists of 3 elements and has the label “8” since it occurs, somewhat arbitrarily, as the 8th element in Forte’s table. “[026]” is the prime form representation of pitch-classes. As mentioned above, the 4095 possible chords aren’t all unique; when boiled-down to their prime form examples there are 223 unique set-classes (chords of cardinality 1=1, 2=6, 3=12, 4=29, 5=38, 6=50, 7=38, 8-29, 9=12, 10=6, 11=1, 12=1).
More information on pitch-class sets and prime form can be found here.
A Program to Compute Prime Form
In 1989, I took a course in music set theory taught by composer and music theorist Robert Morris at the Eastman School of Music. During that course I wrote a program in Basic that would compute the prime form of any group of pitches via table lookup and a complicated algorithm. While it worked, the problem was that the algorithm involved a method invented for humans which translated into lots of operations that permuted arrays of integers. Looking-up large sets of pitches created a noticeable delay on my 1989 16Mhz 286 IBM clone (from a fraction of a second for small sets to around 5 seconds for large sets of 9+ elements). In 1990, during a year-long course in Pascal (at Eastman with Aleck Brinkman), I was able to rewrite the code with an existing computer algorithm based on bit vectors. In short, a bit vector is a technique of representing groups of smaller numbers encoded into the bits of a larger number. For example, using 12-bit numbers (0-4095) we can encode any unique combination of pitch-classes as bits. Say we have the pitch-classes 1, 2, and 5: we can represent them using a binary number (i.e., bit vector by flipping on representative bits like this:
0000 0010 0110
Reading this number as a binary number, and adding the on-bits from right to left, we get the decimal number 38 — that is, we add the successive powers of two that are “turned-on”: (21 = 2) + (22=4) + (25=32) = 38.
It is much more efficient to represent a pitch-class set using a bit vector than an array. In addition, transposition can be accomplished by using the bitwise operators shift left (<<) and shift right (>>). “Shift left” transposes up one semitone, “shift right” down one semitone.
The bit vector prime form algorithm is:1.
- get a pcset of non-duplicating pcs, call it W
- encode W as a bit vector, call it X
- encode the inversion (12-pcs) of W as a bit vector, call it Y
- Shift X left (12-bit circular2) 12 times (all transpositions), save the version with the lowest decimal value.
- Shift Y left (12-bit circular) 12 times (all inversions via transpositions), save the version with the lowest decimal value.
- compare the decimal value of X and Y: If X < Y then X = the prime form, else Y = the prime form
- Use the prime form as the index (hash key) to find the SC number in the data structure (i.e., table lookup).
The 1992 C implementation of the program does more than just calculate the prime form — it provides other useful musical information such as the complement (missing pcs in the octave), the operational mapping from the original pcs to the prime form, and various other technical information (M/MI and Z relation, interval vector, invariance vector, etc.), which is beyond the scope of this post. In short, the program is quite calculation intensive making the use of bit vectors much more efficient than array-based methods.
The PHP rewrite
In designing the PHP rewrite in 2018, I made a few changes. First, I decided to only include the pertinent information about the set-class as found at the top of the second screen.
The main reason for this decision was to accommodate various screen sizes (desktop/mobile) and to get the information to display quickly since the table at the bottom of the original screen is calculation intensive. The unused information from the original version would be better suited to a separate program.
In this version I have made a few changes to the original 1992 C version:
- The initial help screen has now been moved to a linked “instruction” page.
- The output can now exist in several formats including note names (e.g., “C#” instead of “1”).
- The information is listed in a nicely formatted HTML table and the prime form is displayed in color so that it can be seen quickly.
Set Info: help page
Implementation
The main decisions in the PHP version were how best implement the table lookup (in order to find the Forte label/number of each set-class) and how to retrieve the input in a simple manner.
Table lookup
In the original C version, the table lookup is coded as a 223-element array where each index holds the following struct data type.
struct sets {
int set; /* decimal hash key value */
int card; /* cardinality of set-class */
int num; /* Forte number */
int mmi; /* M/MI relation */
int z; /* Z relation */
};
Since PHP does not have a “struct” type I could have implemented the lookup-table as a simple class (object) or a two-dimensional array. Because this project is fairly simple, I decided upon the array model since it cut down on the complexity of the code. In the new version, each subscript of the 223-element array holds a 5-element subarray reflecting the data in the fields of the “set” struct above. (Another way that this could have been coded was by building-in some specific key values to the PHP array, which would have been a bit nasty to enter.) An example of the PHP data structure (2-dimensional array):
/* subarray position: hash index, card, Forte #, M/MI, Z */ $sets = array( array(1,1,1,1,0), array(3,2,1,5,0), array(5,2,2,2,0), array(7,3,1,9,0), array(9,2,3,3,0), array(11,3,2,7,0), array(15,4,1,23,0), array(17,2,4,4,0), array(19,3,3,11,0), array(21,3,6,6,38), array(23,4,2,22,0), ... )
As implied above, the prime form algorithm can be seen as a hash function: it calculates a “code” that is used to index the information in the set table (i.e., hash table) (above). The first value is the “key” or hash value which can be accessed quickly using a binary search algorithm.
Input Parsing
Perhaps the most important part of the PHP implementation was deciding how to input the data (pitch classes) in the most practical and efficient manner for the user. My objective was to allow keyboard only input (if desired) in a simple HTML form text input box. The code needed to distinguish between data entered in two formats:
- a string of characters represented as single digit pitch classes (0-9, AaBbTtEe) that could include optional separations by white space and/or commas.
- a Forte-style set-class number such as 6-19.
Additionally, I wanted to check for certain input errors such as:
- illegal characters
- too many characters
- illegal Forte set-class numbers
- non-legal first characters (‘,’ and ‘-‘)
The input function can take various types of text input in the HTML text entry box:
input | stored as | comments |
---|---|---|
71t6e | 7, 1, 10, 6, 11 | compact input |
0, 1, 6, 7 | 0, 1, 6, 7 | formatted |
4-29 | 0, 1, 3, 7 | Forte # lookup |
48147597ab932075558102910e | 0, 1, 2, 3, 4, 5, 7, 8, 9, A, B | pruned and sorted |
The main function for getting the user input (pitch classes or set-id number) is shown below. My strategy was to try and check for the most obvious errors first. If an error is found, the code calls”error” —a user defined function— which prints a specific message to the user based on the error thrown.
To facilitate the use of the computer keyboard (instead of the mouse), I added an “autofocus” attribute to the text input box in the HTML form. This ensures that the text field is highlighted and ready for keyboard input when landing on the page. Since the text input is activated by return and the submit button is the next form field, all information can be accomplished with the keyboard only (if desired).
In the interest of brevity, I have left out most details of the code, but hope that this demonstrates the basic objectives and implementation of this new version of Set Info. Based on years of using and optimizing the original Unix command-line C version, my overriding goal was to create an internet accessible tool that could be used quickly and easily.
Footnotes
1: This algorithm is well-documented in Brinkman, Alexander R., Pascal Programming for Music Research, University of Chicago Press, 1989. pp. 628-29. My code for the functions that calculate the prime form via bit vectors is based on Brinkman’s. I’ve translated and adjusted much of the code from his Pascal version to PHP. Individuals who made important contributions to the early (1970s) computer algorithm are: Bo Alphonse, Leland Smith, Daniel Starr, and Aleck Brinkman. The SC table was designed by Allen Forte with adjustments made by John Rahn and Robert Morris.
2: 12-bit circular: Essentially we shift all the bit values one bit to the left. However, since this is a mod 12 system we basically construct a ring or loop out of the bits so that if the 12th bit is on, shifting right will move that bit to the 0th bit.