Scales and Tuning

FoxDot takes quite a lot of inspiration from traditional Western music theory including the use of musical scales. A scale is essentially a subset of the musical notes (pitches) between one note, e.g. C, and the same one an octave higher. The starting note is the key of the scale. So starting at C, these notes are:

C, C#, D, D#, E, F, F#, G, G#, A, A#, B

This set of all the notes is called the chromatic scale. If this was a Python list called chromatic, then chromatic[0] would return C, chromatic[1] would return C#, chromatic[2] would return D, and so until chromatic[11], which would return B. Because each musical scale is a subset of these pitches, we can think of each scale as a list of indices for accessing pitches in the chromatic scale. Let’s look at the major scale as an example:

The C Major scale is made up of C, D, E, F, G, A, and B:

chromatic = [C, C#, D, D#, E, F, F#, G, G#, A, A#, B]
C = chromatic[0]
D = chromatic[2]
E = chromatic[4]
F = chromatic[5]
G = chromatic[7]
A = chromatic[9]
B = chromatic[11]

One way of creating the major scale using a loop might be:

steps = [0, 2, 4, 5, 7, 9, 11]
scale = []
for step in scale:
    scale.append(chromatic[step])

# scale == [C, D, E, F, G, A, B]

Our list steps contains the indices needed to create the major scale. So when we use the following code to play all the notes in the scale, we are supplying another list of indices, which is being used to access the scale list:

# Play each note in the scale
p1 >> pluck([0, 1, 2, 3, 4, 5, 6, 7])

# We are essentially playing:
p1 >> pluck([scale[0], scale[1], scale[2], ..., scale[7])

# Which is equivalent to:
p1 >> pluck([chromatic[0], chromatic[2], chromatic[4], scale[5], ..., chromatic[11])

Sometimes you might want to use a note that isn’t in the scale, such a “sharp” like C# in the C Major scale, which is called an “accidental” even though it’s on purpose. You can actually use floating point numbers and FoxDot will approximate the note you are trying to access. Usually you can just use .5 to specify a note between two notes in the scale. e.g. to access C# in the C major scale, you would use scale[0.5] because C# is between C and D which are scale[0] and scale[1] respectively.

List of scales

To see a list of the scales available just run command print(Scale.names()). This will return a list of all the scales as strings like so:

>>> print(Scale.names())
['aeolian', 'chinese', 'chromatic', 'custom', 'default', 'diminished', 'dorian',
'dorian2', 'egyptian', 'freq', 'harmonicMajor', 'harmonicMinor', 'indian',
'justMajor', 'justMinor', 'locrian', 'locrianMajor', 'lydian', 'lydianMinor',
'major', 'majorPentatonic', 'melodicMajor', 'melodicMinor', 'minor',
'minorPentatonic', 'mixolydian', 'phrygian', 'prometheus', 'romanianMinor', 'yu',
'zhi']

Each scale object can be accessed by typing Scale followed by a “.” and then the name of the scale:

# View the major scale semitones
print(Scale.major)

# Access the length of the major scale
print(len(Scale.major))

Setting the scale

By default, each player uses a globally accessibly default scale called Scale.default. This can be changed in a number of ways:

# Simply assigning the scale object to Scale.default
Scale.default  = Scale.minor

# You can using the string name
Scale.default = "minor"

# You can also use the "set" method, which allows more options
Scale.default.set("minor")

Individual players can use independent scales simply by supplying a scale object to the scale keyword as you would any other player attribute:

# Set the scale to major
Scale.default = Scale.major

# Force a player to use the minor scale
p1 >> pluck([0,1,2,3], scale=Scale.minor)

Tuning and customisation

Musical tuning is essentially the ratio relationships between frequencies of notes in a scale. The most commonly used tuning in western popular music is the 12 step equal tempered tuning, which you’ll find on instruments such as a piano. Another popular tuning system is “just intonation”, which will look at implementing below. You can read more about these tuning systems by clicking the links in the text above.

Just as we can look at the scale objects, we can look at tuning objects too:

# Equal tempered
print(Tuning.ET12)

# Just intonation
print(Tuning.just)

Using Scale.default.set we can also set the tuning for the default scale, so let’s set the tuning to just intonation while playing a major chord and listen for the slight change in colour:

# Play a chord on repeat
p1 >> pluck((0, 2, 4))

# Set the tuning to just
Scale.default.set("major", tuning=Tuning.just)

# Set back to equal tempered
Scale.default.set("major", tuning=Tuning.ET12)

You don’t have to use existing scales or tuning – you can even use your own! Just using a list or pattern of numbers in place of the scale and tuning names.

Scale.default.set([0, 2, 3, 5, 6, 9, 10], tuning=[0.1, 0.9, 2.1, 2.9, 4.1, 4.9, 6.1, 6.9, 8.1, 8.9, 10.1, 10.9])