Dichord orbifold

The space of unordered pairs of pitch classes :math:`mathbb{T}^2/mathcal{S}_2` is a Mӧbius strip. Tymoczko, Dmitri. “The geometry of musical chords.” Science 313.5783 (2006): 72-74.

Introduction

This notebook is a demonstration of Orbichord, a project to explore topologically non-trivial space of music chords that are global-quotient orbifolds, see references:

  • Project page: https://orbichord.github.io

  • Project Github: https://github.com/orbichord/orbichord

  • Tymoczko, Dmitri. “The geometry of musical chords.” Science 313.5783 (2006): 72-74.

  • Callender, Clifton, Ian Quinn, and Dmitri Tymoczko. “Generalized voice-leading spaces.” Science 320.5874 (2008): 346-348.

  • Dmitri Tymoczko, A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, Oxford University Press, 2011.

Orbichord comes from combining the words orbifold and chord. It is a collection of python modules build on top of music21 project.

Importing modules

Import music and graphite modules

[1]:
# Import music modules
from music21.scale import ChromaticScale
from orbichord.chordinate import standardSimplex
from orbichord.generator import Generator

# Import graphic modules
import holoviews as hv
from holoviews import opts

hv.extension('bokeh')
hv.output(size=180)
defaults = dict(width=300, height=300, padding=0.1)
hv.opts.defaults(
    opts.EdgePaths(**defaults), opts.Graph(**defaults), opts.Nodes(**defaults))

Configure an orbichord generator

Create a dichord generator using a chromatic scale starting from C pitch. By default, chords are identified by a normal ordered string, resulting in the space chord defined by a collection of pitch-class sets. I will also be using the normal ordered string to label the dichords to avoid any confusion because of the enharmonic equivalance.

[2]:
scale = ChromaticScale('C')

chord_generator = Generator(
    dimension = 2,
    pitches = scale.getPitches('C','B'),
    select = None
)

Generator dichords form a Mӧbius strip

We can explore the space of dichords and show that it is a Mӧbius strip. For this, I first extract the chord normal order coordinates and move the pitch-class set to standard simplex using the algorithm described in figure B2, page 404 in Dmitri Tymoczko, A Geometry of Music. Moreover, I also applied affine transformation over the simplex coordinates.

[3]:
# Generate data points and their labels
data = []
names = []
for chord in chord_generator.run():
    data.append(standardSimplex(chord, scale))
    names.append(chord.normalOrderString)

# Plot the T^2/S_2 orbifold
points = hv.Points(data)
labels = hv.Labels({('x', 'y'): data, 'labels': names}, ['x', 'y'], 'labels')
overlay = (points * labels).redim.range(x=(-0.1, 1.1), y=(-0.1, 1.1))
overlay.opts(
    opts.Labels(text_font_size='10pt', yoffset=+.025),
    opts.Points(color='blue', size=5))
[3]:

The resulting plot is similar to the one shown in the Tymoczko, Dmitri paper The geometry of musical chords or Figure 3.6.1 of A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, by Dmitri Tymoczko.. The only missing points relative to Tymoczko’s plot are those that are identified as the same chord. I manually added those chords closing their normal ordered string between squared brackets to show how to stick together the Mӧbius strip.

[4]:
identified_chords = (
    [1, 0], [1, 2/12], [1, 4/12], [1, 6/12], [1, 8/12], [1, 10/12], [1,1]
)
chord_names = (
    '[<6>]', '[<57>]', '[<48>]', '[<39>]', '[<A2>]', '[<B1>]', '[<0>]'
)
for index in range(len(chord_names)):
    data.append(identified_chords[index])
    names.append(chord_names[index])

# Plot the T^2/S_2 orbifold with identified chords
points = hv.Points(data)
labels = hv.Labels({('x', 'y'): data, 'labels': names}, ['x', 'y'], 'labels')
overlay = (points * labels).redim.range(x=(-0.1, 1.1), y=(-0.1, 1.1))
overlay.opts(
    opts.Labels(text_font_size='10pt', yoffset=+.025),
    opts.Points(color='blue', size=5))
[4]: