Just Intonation with Python

I wrote this library back in 2000. I got away from playing around with JI for years, but recently started working with Supercollider (Sept 2014), so I wanted to resuscitate this code.

Another interesting library is Just Intonation Experiments in Python.




In the last few months I've become a big fan of Just Intonation. One tedious aspect of this is that you end up fondling ratios a lot. The math gets boring after a while, though I do believe you should be able to do the math on your own to get a feel for what it is you're doing.

Having said that, I decided I needed some help because I got sick of reducing multiplied ratios.

I've written a quick Python module, ratio.py, which handles a lot of the tedium. In particular, building up JI tetrachords and scales based on justly intuned chords or by katapyknosis is pretty simple for me now.

Here's an example interactive Python session of me diddling with the generation of a subminor scale.

Python 1.5.1 (#1, Jul  9 1998, 17:44:38)  [GCC 2.7.2.3] on sunos5
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> from ratio import *
>>> s = Ratio(1, 1).chord_of('subminor-7')
>>> s
[<1:1>, <7:6>, <3:2>, <11:6>]
>>> s.append(Ratio(4, 3))
>>> s.sort()
>>> s
[<1:1>, <7:6>, <4:3>, <3:2>, <11:6>]
>>> s = s + Ratio(3, 2).chord_of('subminor')
>>> s
[<1:1>, <7:6>, <4:3>, <3:2>, <11:6>, <3:2>, <7:4>, <9:8>]
>>> s.sort()
>>> s
[<1:1>, <9:8>, <7:6>, <4:3>, <3:2>, <3:2>, <7:4>, <11:6>]
>>> del s[4]
>>> s
[<1:1>, <9:8>, <7:6>, <4:3>, <3:2>, <7:4>, <11:6>]
>>> s.append(Ratio(2, 1))
>>> s
[<1:1>, <9:8>, <7:6>, <4:3>, <3:2>, <7:4>, <11:6>, <2:1>]
>>> gamut2intervals(s)
[<9:8>, <28:27>, <8:7>, <9:8>, <7:6>, <22:21>, <12:11>]
>>> ratiotable(s)
  1:1 |   1:1    9:8    7:6    4:3   3:2    7:4   11:6    2:1
  9:8 |   8:9    1:1  28:27  32:27   4:3   14:9  44:27   16:9
  7:6 |   6:7  27:28    1:1    8:7   9:7    3:2   11:7   12:7
  4:3 |   3:4  27:32    7:8    1:1   9:8  21:16   11:8    3:2
  3:2 |   2:3    3:4    7:9    8:9   1:1    7:6   11:9    4:3
  7:4 |   4:7   9:14    2:3  16:21   6:7    1:1  22:21    8:7
 11:6 |  6:11  27:44   7:11   8:11  9:11  21:22    1:1  12:11
  2:1 |   1:2   9:16   7:12    2:3   3:4    7:8  11:12    1:1
>>> # Well, that was a little difficult.  Time for a Lattice...
>>> g = Duodene(vrat=Ratio(7, 6))
>>> g
    <14:9>     <7:6>     <7:4>   <21:16>
     <4:3>     <1:1>     <3:2>     <9:8>
     <8:7>    <12:7>     <9:7>   <27:14>

>>> g.gamut()
[<1:1>, <9:8>, <8:7>, <7:6>, <9:7>, <21:16>, <4:3>, <3:2>, <14:9>, <12:7>, <7:4>, <27:14>]
>>> cents(g.gamut())
[0.0, 203.910001731, 231.174093531, 266.870905603, 435.084095261, \
470.780907334, 498.044999134, 701.955000865, 764.915904737, \
933.129094395, 968.825906468, 1137.03909613]
>>> 

Manual

The module file itself is well documented, and should be perfectly clear to any Pythoneer. The only strange thing to observe is that since I think of interval addition as addition, the code snippet Ratio(4, 3) + Ratio(9, 8) is actually doing a fraction multiply behind the scenes.

Ratio methods

Most of the tedious bits hang out in the Ratio class, as you can tell from the example above.

__float__
This is called by the float function. It simply turns the ratio into a float (real) value. See cents
complement(against=None)
Returns (2:1 - the ratio) if against isn't set.
cents()
Returns the ratio value in cents.
reduce()
Simplifies the ratio.
octave()
Moves the ratio into the range 1:1 =< ratio <= 2:1.
katapykne(n=1)
Do n divisions of the ratio by katapyknosis.

If n==1, the (non-superparticular) ratio is simply divided. So, <5:3> will return [<5:4>, <4:3>].

This does 1:1 katapyknosis.

katapykne_ab(a, b)
For people who find 1:1 katapyknosis boring, this does a/b katapyknosis by the formula in Chalmers' "Divisions of the Tetrachord", p. 13 (chapter 2).

You may also do this using the Python slice notation. That is, r[1:1] will do 1:1 katapyknosis on the Ratio r

chord_of(chord_name, ident=0)

Return a list of ratios which make a chord of the named type. The types available: major, minor, diminished, subminor, dom-7, major-7, minor-7, subminor-7, dom-9, major-9, minor-9. See the source for a complete list.

The default is to make the instance ration the lowest identity of the chord. If you wanted to make a septimal minor (or subminor) triad with 3:2 as the second note, you'd do this:

            >>> Ratio(3, 2).chord_of('subminor', 1)
            [<9:7>, <3:2>, <27:14>]

Since Python is zero-indexed, '1' is the second identity in the chord.

Finally, you may use a list or tuple of harmonic identities instead of a chord name:

            >>> Ratio(1, 1).chord_of((5, 7, 9, 33), 1)
            [<10:7>, <1:1>, <9:7>, <33:28>]

Duodene methods

This class should probably be called Latice but the default behavior looks like Ellis's duodene, so that's what it's called.

__init__(hrat=Ratio(3, 2), hn=4, vrat=Ratio(5, 4), vn=3)
Generate a new duodene.
d = Duodene(horizontal_ratio, horizontal_number, vert_ratio, vert_num)
d = Duodene(vrat=Ratio(7, 4), hn=5, vn=5)

The default is for the horizontal scale to be 4 3:2 (perfect 5ths), and for the vertical scale to be 3 5:4 (major 3rds). The chart will be centered on 1:1, but is biased toward producing more ratios above 1:1, rather than below.

gamut
This simply returns a list of all ratios in the duodene. If you leave hn and vn at their default values, this will be 12 ratios.

Utilities

In addition the the Ratio methods listed above, there are a number of utility functions which operate on a list of either scale or interval ratios.

cents(intervals)
Returns the sequence of cent values from a sequence of scale degrees.
intervals2gamut(intervals)
Returns the sequence of intervals from a sequence of scale degrees.
gamut2intervals(ratios)
Returns a sequence of scale degrees from a sequnce of intervals.
gamut2series(gamut)
Return a harmonic overtone list based on a sequence of pitches.

This gets a touch confused when the series spans more than an octave. You may need to fiddle with the argument to guarantee a completely reduced answer.

gamut2identities(gamut)
Return a harmonic overtone identity list based on a sequence of pitches.

That is, give us [1, 5, 3] from a major chord, instead of [4, 5, 6].

ratiotable(ratios)
Generate a table of inter-scale-degree ratios.
triads(ratios)
This will try to find all triads it knows about in the given collection of scale degrees.
tetrads(ratios)
This will try to find all four-note chords it knows about in the given collection of scale degrees.