Notation for Exotic Pitched Events


Contents

Here is a list of all the material within this user manual. You may obtain the source code here, and obtain Nyquist here. Note that this document is slightly modified from the one that is present in the source package.

Preamble

The tuning.lsp library gives the Nyquist user easy access to tuning systems other than 12-Tone Equal Temperament (12-TET). This manual will first describe the library and how it ought to be used, and will follow on with details of the various structures and design decisions made.

Nyquist uses the MIDI Tuning Standard (MTS) with A4=440Hz by default as the standard tuning system; this is compatible with 12-TET, which is the mainstay for Western music tuning for years. The difference between tuning.lsp over the system in Nyquist lies in the fact that more than one tuning system can exist at the same time, which provides a richer pool of pitches that can be used in composing xenharmonic pieces.

Terminology

There are a few terms that keep getting used and re-used in this document, and this section will provide a brief definition of what they are.

Interval ratio
The ratio of the frequency of a pitch to a reference pitch.
Unison
The interval ratio of 1/1.
Octave
The interval ratio of 2/1. The most common interval ratio for repeated pitches to be considered musically ``the same''.
Pseudo-octave
This is an interval ratio that is not 2/1 but is treated as the interval ratio for repeated pitches to be considered musically ``the same''. We tend to use pseudo-octave here as a general term for any interval ratio that is used repeatedly to define pitches that are musically ``the same''.
MIDI Tuning Standard (MTS)
This is a specification of musical pitch agreed upon by the MIDI Manufacturers Association. It specifies a mapping from integers to pitches from 12-TET with A4=440 Hz. Nyquist uses this when parameters are referred to as requiring either pitch or step. One fixed point in MTS to remember is 69=A4.
Equal Temperament
This is a type of tuning that where every pair of music pitches have an identical interval ratio. One classical example of this is 12-TET or 12-Tone Equal Temperament, the prevalent tuning system in Western music today.

Usage Overview

Using tuning.lsp requires two major steps:

  1. Create a tuning and save it to a variable.
  2. Use steps-of and tuning-freq with the saved tuning to obtain the MTS step count and actual pitch frequency respectively in places where they are valid under normal circumstances.

If there are any errors in any of the functions used in any step, it will be displayed on the console of Nyquist, with some feedback on what is wrong.

As a simple overview of how tuning.lsp might be used, consider the following code fragment that uses tuning.lsp and score-gen to create a [boring] melody.

load "tuning.lsp"

define function a-sound()
  begin
    with 12-tet = make-equal-temperament(12, 2, 440.0, 9, 4),
         ; Pitch patterns involving C3, E4 and G3 respectively
         pitch-pat = make-cycle(list(steps-of(12-tet, 0, 3),
                                     steps-of(12-tet, 4, 4),
                                     steps-of(12-tet, 7, 3))),
         rhythm-pat = make-cycle(list(q))
    exec score-gen(save: quote(sound-a),
                   score-len: 5,
                   ioi: next(rhythm-pat),
                   pitch: next(pitch-pat))
    return sound-a
  end

play(a-sound())

We will now examine each step in detail in the next two subsections.

Creating a Tuning

There are two types of tuning systems that tuning.lsp can support; these are Equal Temperament and Explicit Mapping respectively. The following are the two functions necessary to create the two tuning systems.

make-equal-temperament(pitches, pseudo-octave-ratio, ref-freq [,ref-freq-offset [,ref-freq-pseudo-octave]])

Returns a tuple containing information necessary to recreate any pitch within the constructed Equal Temperament scale. The number of pitches in the Equal Temperament system is given by pitches, the ratio of the frequency of the highest pitch to the lowest in one cycle of the scale is given by pseudo-octave-ratio, the reference frequency is given by ref-freq. If ref-freq-offset and ref-freq-pseudo-octave are given, ref-freq is assumed to be the pitch equal to ref-freq-pseudo-octave cycles of the scale followed by ref-freq-offset notes into the cycle above the ``start'' point.

For example, for 12-TET with A4=440Hz, we can create and save the scale to a variable 12-tet using the following:

load "tuning.lsp"
set 12-tet = make-equal-temperament(12, 2, 440, 9, 4)

Observe that ref-freq-pseudo-octave and ref-freq-offset are set to 9 and 4 respectively since A is nine pitches above the reference of C, and A4 is 4 octaves over the lowest note we want to reference from, i.e. C0.

make-tuning(intervals, ref-freq [, ref-freq-offset [,ref-freq-pseudo-octave]])

Returns a tuple containing information necessary to recreate any pitch within the constructed scale. To use this function, construct a list of interval ratios that govern the relationships of pitches relative to the reference frequency and pass this as intervals; provide the reference frequency in ref-freq. The two remaining optional parameters work the same way as defined in make-equal-temperament above.

There are four assumptions made about the Explicit Mapped tuning system thus defined:

  1. The scale is always monotonically increasing---interval ratios will be sorted to ensure this fact.
  2. The smallest interval ratio is always 1/1---if the smallest ratio is not 1/1, all interval ratios will be normalised till this is so.
  3. The largest interval ratio determines the interval ratio for the pseudo-octave.
  4. Integer pitch numbers are guaranteed to have the correct [in-order] ratios---fractional pitch numbers will be estimated using interpolation of an Equal Temperament curve with the normalised unison and normalised pseudo-octave as fixed points.

These four assumptions will explain some of the behaviour when malformed interval ratios are provided.

For example, if we want to construct a 12-tone Pythagorean scale explicitly and save the scale to a variable 12-pyth, we can use the following:
load "tuning.lsp"
set 12-pyth = make-tuning(list(1.0, 2187.0 / 2048.0, 9.0 / 8.0, 32.0 / 27.0,
                               81.0 / 64.0, 4.0 / 3.0, 729.0 / 512.0, 3.0 / 2.0,
                               6561.0 / 4096.0, 27.0 / 16.0, 16.0 / 9.0,
                               243.0 / 128.0, 2.0), 440.0)

Using a Created Tuning

There are two general purpose functions that will allow us to generate the correct MTS pitch number and frequency for a required note given a variable storing the tuning system that was constructed using make-equal-temperament and make-tuning. These functions will be explained below.

steps-of(tuning, pitch-number, pseudo-octave)

Returns the equivalent MTS step number of a pitch in the different scale given a tuple tuning created with make-equal-temperament/make-tuning, and the pitch specification in pseudo-octave and the specific pitch number within the pseudo-octave in pitch-number. Note that steps-of depends on *A4-Hertz*, so expect the MTS pitch numbers to vary if *A4-Hertz* has been changed. The consistent thing about steps-of will be that the frequency of the output MTS step will be correct for the required pitch in the different tuning system.

Here is an example on how to use steps-of with make-equal-temperament:

load "tuning.lsp"
set *A4-Hertz* = 440.0
exec set-pitch-names()
set 12-tet = make-equal-temperament(12, 2, 440, 9, 4)
print(steps-of(12-tet, 5, 4))

The output of this fragment of SAL code will be 65, which is the MTS pitch number for F4. Recall that A4 is the 9-th pitch in the 4-th octave (pitches and octaves begin counting from 0).

And now for something more exotic, 12-tone Pythagorean tuning made with make-tuning:

load "tuning.lsp"
set *A4-Hertz* = 440.0
exec set-pitch-names()
set 12-pyth = make-tuning(list(1.0, 2187.0 / 2048.0, 9.0 / 8.0, 32.0 / 27.0,
                               81.0 / 64.0, 4.0 / 3.0, 729.0 / 512.0, 3.0 / 2.0,
                               6561.0 / 4096.0, 27.0 / 16.0, 16.0 / 9.0,
                               243.0 / 128.0, 2.0), 440.0)
print(steps-of(12-pyth, 11, 4))

The output of this fragment of SAL code will be 71.0391, which is the MTS pitch number equivalent for B4 under the 12-tone Pythagorean Tuning. Recall once more that A4 is the 9-th pitch in the 4-th octave (pitches and octaves begin counting from 0).

tuning-freq(tuning, pitch-number, pseudo-octave)

Returns the frequency of a pitch in the different scale given a tuple tuning created with make-equal-temperament/make-tuning, and the pitch specification in pseudo-octave and the specific pitch number within the pseudo-octave in pitch-number.

Here is an example on how to use tuning-freq with make-equal-temperament:

load "tuning.lsp"
set *A4-Hertz* = 440.0
exec set-pitch-names()
set 12-tet = make-equal-temperament(12, 2, 440, 9, 4)
print(tuning-freq(12-tet, 9, 5))

The output of this fragment of SAL code will be 880, which is the frequency of A5 under MTS with A4=440Hz. Recall that A4 is the 9-th pitch in the 4-th octave (pitches and octaves begin counting from 0).

And now for something more exotic, 12-tone Pythagorean tuning made with make-tuning:

load "tuning.lsp"
set *A4-Hertz* = 440.0
exec set-pitch-names()
set 12-pyth = make-tuning(list(1.0, 2187.0 / 2048.0, 9.0 / 8.0, 32.0 / 27.0, 81.0 / 64.0,
                             4.0 / 3.0, 729.0 / 512.0, 3.0 / 2.0, 6561.0 / 4096.0,
                             27.0 / 16.0, 16.0 / 9.0, 243.0 / 128.0, 2.0), 440.0, 9, 4)
print(tuning-freq(12-pyth, 11, 4))

The output of this fragment of SAL code will be 495, which is the frequency of B4 under the 12-tone Pythagorean Tuning. Recall once more that A4 is the 9-th pitch in the 4-th octave (pitches and octaves begin counting from 0).

Saving/Loading Scales

While make-equal-temperament and make-tuning are great for programmatically creating new tuning systems based on known parameters like pseudo-octave ratios and interval ratios respectively, sometimes one might want to use scales that were designed by others which can be difficult to replicate using either of these two functions by hand (like Partch's 43-tone system). Fortunately, there is a free tool available for creating scales---this tool is SCALA. The tuning.lsp supports the loading of scale files that were created with SCALA; the file format is fairly flexible and straightforward and can be found here.

Briefly speaking, SCALA scale files store the interval ratios in the scale. As such, tuning systems created with make-equal-temperament will need to have the interval ratios extracted and saved to the file. To do this task in a general way, we can use the one function get-intervals, which works for tuples generated by either make-equal-temperament or make-tuning.

get-intervals(tuning)

Returns a list of the pitch interval ratios within the pseudo-octave determined at creation time of the tuple tuning. The tuple tuning can be created either with make-equal-temperament or make-tuning.

Saving/Loading to .scl Files

There are two functions that help in saving and loading from SCALA Scale files (also known as ``.scl files''). Both of these functions use scales that are in interval ratio form (see get-intervals for more information on how to convert a scale to a list of interval ratios).

load-intervals-from-scala(filename)

Returns a list of interval ratios that is loaded from the .scl file specified in the string filename.

Here is an example to load the ``12-tet.scl'' SCALA scale file and use the loaded interval to create a tuning tuple:

set interval-ratios = load-intervals-from-scala("12-tet.scl")
set 12-tet = make-tuning(interval-ratios, 440.0, 9, 4)

Notice that we can only use make-tuning to create a tuning system from the loaded .scl file. This is true even if the .scl file we are loading represents an Equal Temperament scale.

save-intervals-to-scala(filename, interval-list [,scale-name])

Saves the interval ratios in the list interval-list to the .scl file specified by filename, optionally writing out the string scale-name as a part of the name of the scale.

Note that save-intervals-to-scala will always save interval ratios in musical cents format. This is true even if the interval ratios to be saved were originally defined through rational division like 4.0/5.0.

Here is an example that creates a 7-TET tuning system and saves it to ``7-tet.scl'' SCALA scale file. Notice how get-intervals is used to obtain the interval ratios necessary for save-intervals-from-scale.

set 7-tet = make-equal-temperament(7, 2, 100.0)
set interval-ratios = get-intervals(7-tet)
exec save-intervals-to-scala("7-tet.scl", interval-ratios, "7-Tone Equal Temperament")

We can do this for a tuple created with make-tuning in a similar way.

Support on SCALA .scl files are fairly robust in non-degenerate cases. One limitation of .scl files will be that only the interval ratios are stored; the reference frequency is not stored anywhere in the .scl file.

Programming Notes

Contained within this section are some of the design decisions and specifications that are related to the development of tuning.lsp. It is unnecessary for a user of tuning.lsp to understand the intricacies of the code design to use this library. These notes are meant to supplement the plentiful comments that exist within the source files themselves.

Dependencies

There are two forms of dependencies that we will be looking at, namely the file dependencies and next the function dependencies.

File dependencies diagram

Referring to the diagram above, we see the relationships of the files with each other. Note that lib here refers to the default Nyquist directory for all other libraries, while lib/tuning is where all the other support files are located. The user will have to issue a load "tuning.lsp" statement in SAL and the search path will automatically find tuning.lsp, which will automatically load the three component files et-tuning.lsp, em-tuning.lsp and scala.lsp that implement much of the functionality.

The file et-tuning.lsp contains functions related to the Equal Temperament tuning system (which builds equal temperament scales according to the interval ratio of the pseudo-octave, number of pitches required and parameters for the reference frequency); the file em-tuning.lsp contains the functions related to the Explicit Mapping tuning system (which constructs a tuning system based on explicit interval-ratios for each pitch relative to some reference pitch, and again parameters for the reference frequency); the file scala.lsp contains the code necessary for reading and writing to .scl files.

Again referring to the diagram above, we find that the globally viewable functions are grouped according to which files they can be found---this will aid in isolation and debugging specific parts of the code should things not work out well.

Function dependencies diagram

The diagram immediately above lists the function dependencies of all the globally accessible functions. Globally accessible here refers to the fact that any user program that issues a load "tuning.lsp" statement will be able to see these functions. The inclusion of the dependencies among functions are to aid in the modification of the library in the future.

Tuning Tuple

This section talks a little about the tuples that make-equal-temperament and make-tuning create and how these are used by make-tuning and get-intervals to detect the correct way to decode the information.

The tuple created by make-equal-temperament is in reality a list, with the following items in it:

  1. First element set to the string "et" for ``equal temperament''.
  2. Second element set to a float that stores the interval ratio between consecutive pairs of notes (they are all equal, so we can just store one of them).
  3. Third element set to the number of pitches to the pseudo-octave.
  4. Last element is a reference frequency based off the one that was passed to make-equal-temperament. This frequency is calculated by reducing the passed reference frequency by the supposed position of that passed reference frequency so that we get the actual frequency for the pitch whose pitch number is 0 and whose pseudo-octave is also 0.

The data that are stored in this list are designed to reduce computations required to generate the correct frequency for a particular pitch given the pitch's position within the pseudo-octave and the number of pseudo-octaves away relative to the reference frequency stored in the list.

The tuple created by make-tuning is more complex. Again the tuple is really a list, but this time, it has the following items in it:

  1. First element set to the strong "em" for ``explicit mapping''.
  2. Second elemnt set to an ascending list of floating point numbers, canonically known as tet-fractions. This list always begins with 0.0 and ends with 1.0, and each element in this list corresponds to the relative position of the interval ratio on an equal temperament curve whose lowest end point (0.0 mark) is on the lowest interval ratio and whose highest end point (1.0 mark) is on the highest interval ratio (or the pseudo-octave). The choice of this representation is to make it easier to interpolate for pitch numbers that are not integral, i.e. falling in between the ``cracks'' of the defined scale. The choice of an equal temperament curve for the interpolation is to be consistent with the interpretation in the case where the interval ratios define an equal temperament scale.
  3. Third element confirms the number of pitches in the scale, which allows for some sanity checking and reduction in use of length to figure out the number of pitches.
  4. Fourth element stores the interval ratio for the pseudo octave, which allows us to scale tet-fractions accordingly when we need to obtain actual ratios.
  5. Last element is a reference frequency based off the one that was passed to make-tuning, defined in the tuple description for make-equal-temperament.

So, much of the magic in tuning-freq and get-intervals (steps-of is dependent on tuning-freq and thus does not need to perform this ``magic'') in determining which functions to call when given a tuning tuple comes from reading off the information in the first element of the tuple, which basically uniquely identifies each of the tuning systems that were used to generate that tuple. From there, the functions will despatch the arguments passed to it to the correct functions from either et-tuning.lsp or em-tuning.lsp and return the result. This maintains the illusion that the tuning.lsp library is versatile from the perspective of the user.

SCALA .scl File Support

As noted earlier in Saving/Loading to .scl Files, support for .scl files are rudimentary. The .scl file format is remarkably flexible, which leads to the following problem: if the scale in the .scl file is not monotonically increasing, then make-tuning will fail terribly due to the failure of the assumption that all tuning scales will have increasing interval ratios as the pitch number increases. As far as I can tell, this is usually the case, and should the non-monotonically increasing scale be required, a workaround will be to write an intermediate layer to translate from the scale's ``proper'' pitch number to the one that is in order that make-tuning uses. But as noted, since this problem does not seem to occur in real life, no work has been done to ensure that the support for non-monotonically increasing interval ratios in a scale behaves correctly.

Index of All Globally Accessible Functions

Here is a list of all the globally accessible functions (and variables) and a brief description of what they do in alphabetical order. Refer to the sources for more details.

*tuning-dir*
This is set to "tuning/" when deployed; set this to "" when testing locally.
cents-to-ratio(cents)
Converts musical cents into an interval ratio.
em-frequency(tuning, pitch-number, pseudo-octave)
Deals with the generation of the correct frequency for an explicitly mapped tuning system. This function is here so that we can consolidate all the ``magic'' parts of the tuple relevant to the explicit mapping in one place (instead of building into tuning.lsp, which confuses things).
em-intervals(tuning)
Deals with the generation of the list of intervals for saving as a SCALA .scl file.
em-linear-scale-of(tet-fractions, pitch)
Returns the TET-scale that the pitch value represents, given a list of TET-fractions.
et-frequency(tuning, pitch-number, pseudo-octave)
Deals with the generation of the correct frequency for an equal temperament tuning. This function is here so that we can consolidate all the ``magic'' parts of the tuple relevant to equal temperament in one place (instead of building them into tuning.lsp, which confuses things).
et-intervals(tuning)
Deals with the generation of the list of interval ratios for saving as SCALA .scl file format by save-intervals-to-scala.
et-linear-scale-to-ratio(pseudo-octave-ratio, linear-scale)
Takes a pseudo-octave ratio and ``linear'' scale, and returns the associated ratio.
et-ratio-to-linear-scale(pseudo-octave-ratio, interval-ratio)
Takes a pseudo-octave ratio and an interval ratio, and returns the log-based scale of the interval ratio with respect to the pseudo-octave ratio.
fifth(L)
Surprisingly, fifth is not defined, so this returns the fifth element in the list L.
get-intervals(tuning)
A wrapper that extracts interval information given the tuning tuple.
load-intervals-from-scala(filename)
Loads an explicit mapped scale from a SCALA .scl file.
make-equal-temperament(pitches, pseudo-octave-ratio, ref-freq [,ref-freq-offset [,ref-freq-pseudo-octave]])
Creates a tuple that will allow tuning-freq to create the correct frequencies.
make-tuning(intervals, ref-freq [,ref-freq-offset [,ref-freq-pseudo-octave]])
Takes a list of intervals and creates a tuple necessary to compute the correct pitch frequency.
ratio-to-cents(interval-ratio)
Conerts an interval ratio into musical cents.
save-intervals-to-scala(filename, interval-list [,scale-name])
Takes a list of intervals and writes a SCALA .scl file.
steps-of(tuning, pitch-number, pseudo-octve)
A wrapper to obtain the ``steps'' the Nyquist uses from directly calculating the frequency given the tuning system.
tuning-freq(tuning, pitch-number, pseudo-octave)
Takes a tuple of tuning information, a pseudo-octave, a pitch number and returns the frequency of the pitch given the tuning tuple.

Testing/Deployment

Unit tests for each of the core components can be found in test.lsp. The unit test harness was written from scratch and suffers from the single flaw of not being able to intercept errors that were thrown by the functions it is unit-testing---this can be a future work in expanding Nyquist to have a unit-testing framework.

When developing, all the files are in a ``flat'' structure and unit tests are run locally on the files. When creating the package for deployment, a directory named tuning ought to be made and all source and documentation files ought to be in that directory. The only file that should sit outside of tuning is tuning.lsp. The *tuning-dir* variable must be set to "tuning/" so that the other dependencies can be found when tuning.lsp is loaded by the user.

Acknowledgements

I would like to thank Professor Roger Dannenberg for writing Nyquist, which is one of the more interesting programming tools for sound/music generation that I have used thus far. Indeed, I probably would not have the chance to write a system in LISP had this class not be taught in Nyquist/SAL. I would also like to thank the TAs for the time they spent in listening to my compositions. The Introduction to Computer Music class has been a most interesting class, and I daresay that it is the best way to end my undergraduate career this way.


Last updated on 2017-07-25T11:43:36+0800.

XHTML 1.0 StrictScreen CSSPrint CSSTTY CSS