Interval Fans¶
An interval fan is a harmonic primitive describing the relative interval distances of the elements of a scale or a sequence from the vantage point of a tonal center. This is in contrast to the interval sequence primitive, which describes the relative distances of notes/pitches in a scale or sequence to each other.
Interval fans describe movement in relation to a tonal center without specifying that center. This situates them closely to both transformation theory and tonal music. However, they are also simply a common form to denote an abstract scale, e.g. in Scale Workshop or Scala.
We start with an example from tonal analysis:
from xenharmlib import WesternNotation
western = WesternNotation()
# the melodic structure of the first measure of Bach's
# Fuga 1 from "Das Wohltemperierte Klavier" (BWV 846)
seq = western.seq([western.note(pcs, 4) for pcs in 'CDEFGFEA'])
tonic = western.note('C', 4)
ifan = seq.to_interval_fan(tonic)
print(ifan)
WesternNoteIntervalFan([P1, M2, M3, P4, P5, P4, M3, M6])
We received an abstract formulation of the melodic line. We start from “the tonic” (whatever it is), then move a minor second over the tonic, then a minor third, etc. We can now analyze the piece from a different viewpoint, not as a sequence of notes, but a sequence of distances from the “center of gravity”. Vice versa, we can use the interval fan to descend again to the layer of notes, “de-abstract” it by giving it a new tonic, for example D#:
new_tonic = western.note('D#', 4)
print(new_tonic.seq(ifan))
WesternNoteSeq([D#4, E#4, Fx4, G#4, A#4, G#4, Fx4, B#4])
We can also start directly from an interval fan and then use it as a template to “stamp out” different scales and sequences. Especially in writings about Just Intonation, scales are often given as interval fans:
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
major_chord = limit5.rs_interval_fan(['1/1', '5/4', '3/2'])
G0 = limit5.rs_pitch('3/2')
print(G0.scale(major_chord))
PrimeLimitPitchScale([3/2, 15/8, 9/4], 5-Limit)
Deriving Interval Fans from Intervals¶
You can create interval fans by an iterable of single interval objects
with the default builder method
interval_fan():
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
P1 = edo31.diff_interval(0)
M3 = edo31.diff_interval(10)
P5 = edo31.diff_interval(18)
ifan = edo31.interval_fan([P1, M3, P5])
print(ifan)
EDOPitchIntervalFan([0, 10, 18], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
P1 = limit5.rs_interval('1/1')
M3_pure = limit5.rs_interval('5/4')
P5 = limit5.rs_interval('3/2')
ifan = limit5.interval_fan([P1, M3_pure, P5])
print(ifan)
PrimeLimitPitchIntervalFan([1, 5/4, 3/2], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
P1 = western.shorthand_interval('P', 1)
M3 = western.shorthand_interval('M', 3)
P5 = western.shorthand_interval('P', 5)
ifan = western.interval_fan([P1, M3, P5])
print(ifan)
WesternNoteIntervalFan([P1, M3, P5])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
P1 = n_edo31.shorthand_interval('P', 1)
super_M3 = n_edo31.shorthand_interval('^M', 3)
P5 = n_edo31.shorthand_interval('P', 5)
ifan = n_edo31.interval_fan([P1, super_M3, P5])
print(ifan)
UpDownNoteIntervalFan([P1, ^M3, P5], 31-EDO)
Element-wise Construction¶
Interval fans can be constructed element-wise. In line with xenharmlib’s immutable object design, this is not done with an append method like in standard Python, but with a method that returns a scale with an additional element:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
P1 = edo31.diff_interval(0)
M3 = edo31.diff_interval(10)
P5 = edo31.diff_interval(18)
# start with an empty fan
ifan = edo31.interval_fan()
for i in [P1, M3, P5]:
ifan = ifan.with_interval(i)
print(ifan)
EDOPitchIntervalFan([0], 31-EDO)
EDOPitchIntervalFan([0, 10], 31-EDO)
EDOPitchIntervalFan([0, 10, 18], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
P1 = limit5.rs_interval('1/1')
M3_pure = limit5.rs_interval('5/4')
P5 = limit5.rs_interval('3/2')
# start with an empty fan
ifan = limit5.interval_fan()
for i in [P1, M3_pure, P5]:
ifan = ifan.with_interval(i)
print(ifan)
PrimeLimitPitchIntervalFan([1], 5-Limit)
PrimeLimitPitchIntervalFan([1, 5/4], 5-Limit)
PrimeLimitPitchIntervalFan([1, 5/4, 3/2], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
P1 = western.shorthand_interval('P', 1)
M3 = western.shorthand_interval('M', 3)
P5 = western.shorthand_interval('P', 5)
# start with an empty fan
ifan = western.interval_fan()
for i in [P1, M3, P5]:
ifan = ifan.with_interval(i)
print(ifan)
WesternNoteIntervalFan([P1])
WesternNoteIntervalFan([P1, M3])
WesternNoteIntervalFan([P1, M3, P5])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
P1 = n_edo31.shorthand_interval('P', 1)
super_M3 = n_edo31.shorthand_interval('^M', 3)
P5 = n_edo31.shorthand_interval('P', 5)
# start with an empty fan
ifan = n_edo31.interval_fan()
for i in [P1, super_M3, P5]:
ifan = ifan.with_interval(i)
print(ifan)
UpDownNoteIntervalFan([P1], 31-EDO)
UpDownNoteIntervalFan([P1, ^M3], 31-EDO)
UpDownNoteIntervalFan([P1, ^M3, P5], 31-EDO)
Instead of appending intervals at the end of the fan, users can also give an additional parameter to define where the new interval should appear:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
P1 = edo31.diff_interval(0)
M3 = edo31.diff_interval(10)
P5 = edo31.diff_interval(18)
# start with an empty fan
ifan = edo31.interval_fan()
for i in [P1, M3, P5]:
ifan = ifan.with_interval(i, 0)
print(ifan)
EDOPitchIntervalFan([0], 31-EDO)
EDOPitchIntervalFan([10, 0], 31-EDO)
EDOPitchIntervalFan([18, 10, 0], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
P1 = limit5.rs_interval('1/1')
M3_pure = limit5.rs_interval('5/4')
P5 = limit5.rs_interval('3/2')
# start with an empty fan
ifan = limit5.interval_fan()
for i in [P1, M3_pure, P5]:
ifan = ifan.with_interval(i, 0)
print(ifan)
PrimeLimitPitchIntervalFan([1], 5-Limit)
PrimeLimitPitchIntervalFan([5/4, 1], 5-Limit)
PrimeLimitPitchIntervalFan([3/2, 5/4, 1], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
P1 = western.shorthand_interval('P', 1)
M3 = western.shorthand_interval('M', 3)
P5 = western.shorthand_interval('P', 5)
# start with an empty fan
ifan = western.interval_fan()
for i in [P1, M3, P5]:
ifan = ifan.with_interval(i, 0)
print(ifan)
WesternNoteIntervalFan([P1])
WesternNoteIntervalFan([M3, P1])
WesternNoteIntervalFan([P5, M3, P1])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
P1 = n_edo31.shorthand_interval('P', 1)
super_M3 = n_edo31.shorthand_interval('^M', 3)
P5 = n_edo31.shorthand_interval('P', 5)
# start with an empty fan
ifan = n_edo31.interval_fan()
for i in [P1, super_M3, P5]:
ifan = ifan.with_interval(i, 0)
print(ifan)
UpDownNoteIntervalFan([P1], 31-EDO)
UpDownNoteIntervalFan([^M3, P1], 31-EDO)
UpDownNoteIntervalFan([P5, ^M3, P1], 31-EDO)
Index-based Construction¶
Interval fans can also conveniently be constructed by providing a list of indices, which - depending on origin context - can be integers or lattice points. This type of construction also works on the notation layer by employing the notation’s enharmonic strategy.
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([0, 10, 18])
print(ifan)
EDOPitchIntervalFan([0, 10, 18], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.diff_interval_fan(
[
limit5.lattice.point((0, 0, 0)),
limit5.lattice.point((-3, 2, 0)),
limit5.lattice.point((-2, 0, 1)),
]
)
print(ifan)
PrimeLimitPitchIntervalFan([1, 9/8, 5/4], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([0, 5, 7])
print(ifan)
WesternNoteIntervalFan([P1, P4, P5])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([0, 11, 18])
print(ifan)
UpDownNoteIntervalFan([P1, d4, P5], 31-EDO)
Origin contexts built on lattice point indexing also allow construction
from an iterable of tuples with
vec_interval_fan()
as an alternative to the more verbose above method, which expects
a full LatticePoint object.
from xenharmlib import PrimeLimitTuning
limit3 = PrimeLimitTuning(3)
major_triad = limit3.vec_interval_fan(
[(0, 0), (-6, 4), (-1, 1)]
)
print(major_triad)
PrimeLimitPitchIntervalFan([1, 81/64, 3/2], 3-Limit)
from xenharmlib import MultiGenTuning
from xenharmlib import FrequencyRatio
sg237 = MultiGenTuning(
[FrequencyRatio(p) for p in [2, 3, 7]],
eq_diff_vec=(1, 0, 0)
)
ifan = sg237.vec_interval_fan(
[(0, 0, 0), (-2, 1, 1), (-1, -1, 1)]
)
print(ifan)
MultiGenPitchIntervalFan([(0, 0, 0), (-2, 1, 1), (-1, -1, 1)], G=(2, 3, 7))
Construction Based on Closest Frequency Ratio¶
Origin contexts based on integer indices allow construction based on the
approximation of frequency ratios. Given an arbitrary iterable of frequency
ratios, the
closest_interval_fan()
method returns the interval fan of an origin context that is closest to it:
from xenharmlib import EDOTuning
from xenharmlib import FrequencyRatio
edo31 = EDOTuning(31)
ratios = [
FrequencyRatio(1, 1),
FrequencyRatio(5, 4),
FrequencyRatio(3, 2),
FrequencyRatio(7, 4),
]
ifan = edo31.closest_interval_fan(ratios)
print(ifan)
EDOPitchIntervalFan([0, 10, 18, 25], 31-EDO)
from xenharmlib import WesternNotation
from xenharmlib import FrequencyRatio
western = WesternNotation()
ratios = [
FrequencyRatio(1, 1),
FrequencyRatio(5, 4),
FrequencyRatio(3, 2),
FrequencyRatio(7, 4),
]
ifan = western.closest_interval_fan(ratios)
print(ifan)
WesternNoteIntervalFan([P1, M3, P5, A6])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
from xenharmlib import FrequencyRatio
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ratios = [
FrequencyRatio(1, 1),
FrequencyRatio(5, 4),
FrequencyRatio(3, 2),
FrequencyRatio(7, 4),
]
ifan = n_edo31.closest_interval_fan(ratios)
print(ifan)
UpDownNoteIntervalFan([P1, M3, P5, A6], 31-EDO)
Iteration¶
Interval fans support iteration over their elements:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 7, 10])
for interval in ifan:
print('-->', interval)
--> EDOPitchInterval(10, 31-EDO)
--> EDOPitchInterval(8, 31-EDO)
--> EDOPitchInterval(7, 31-EDO)
--> EDOPitchInterval(10, 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '5/4', '10/9'])
for interval in ifan:
print('-->', interval)
--> PrimeLimitPitchInterval(5/4, 5-Limit)
--> PrimeLimitPitchInterval(6/5, 5-Limit)
--> PrimeLimitPitchInterval(5/4, 5-Limit)
--> PrimeLimitPitchInterval(10/9, 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([3, 5, 5, 3, 5])
for interval in ifan:
print('-->', interval)
--> WesternNoteInterval(A, 2)
--> WesternNoteInterval(P, 4)
--> WesternNoteInterval(P, 4)
--> WesternNoteInterval(A, 2)
--> WesternNoteInterval(P, 4)
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([10, 11, 10, 10, 18])
for interval in ifan:
print('-->', interval)
--> UpDownNoteInterval(M, 3, 31-EDO)
--> UpDownNoteInterval(d, 4, 31-EDO)
--> UpDownNoteInterval(M, 3, 31-EDO)
--> UpDownNoteInterval(M, 3, 31-EDO)
--> UpDownNoteInterval(P, 5, 31-EDO)
Containment¶
If you want to know if a specific interval is contained inside of an
interval fan, you can use the in operator:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 7, 10])
interval_a = edo31.diff_interval(7)
interval_b = edo31.diff_interval(9)
print(interval_a in ifan)
print(interval_b in ifan)
True
False
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '5/4', '10/9'])
interval_a = limit5.rs_interval('6/5')
interval_b = limit5.rs_interval('3/2')
print(interval_a in ifan)
print(interval_b in ifan)
True
False
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([3, 5, 5, 3, 5])
interval_a = western.shorthand_interval('P', 4)
interval_b = western.shorthand_interval('P', 5)
print(interval_a in ifan)
print(interval_b in ifan)
True
False
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([10, 11, 10, 10, 18])
interval_a = n_edo31.shorthand_interval('M', 3)
interval_b = n_edo31.shorthand_interval('vM', 3)
print(interval_a in ifan)
print(interval_b in ifan)
True
False
Identity¶
Two interval fans are considered identical if each interval in one interval fan corresponds to another interval in the other interval fan at the same position. This relation works across origin contexts, for example 12-EDO and 24-EDO interval fans, or western note interval fans can be identical:
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
from xenharmlib import WesternNotation
edo24 = EDOTuning(24)
n_edo24 = UpDownNotation(edo24)
western = WesternNotation()
Cm_1 = edo24.diff_interval_fan([8, 6])
Cm_2 = n_edo24.interval_fan(
[
n_edo24.shorthand_interval('M', 3),
n_edo24.shorthand_interval('m', 3),
]
)
Cm_3 = western.interval_fan(
[
western.shorthand_interval('M', 3),
western.shorthand_interval('m', 3),
]
)
print(Cm_1 == Cm_2 == Cm_3)
True
Keep in mind that even if two interval fans might have the same symbolic string representation in a notation, they are not necessarily equal. A major third interval in 31-EDO has, for example, a different frequency ratio than a major third interval in 12-EDO:
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
edo12 = EDOTuning(12)
n_edo12 = UpDownNotation(edo12)
Cm_1 = n_edo31.interval_fan(
[
n_edo31.shorthand_interval('M', 3),
n_edo31.shorthand_interval('m', 3),
]
)
Cm_2 = n_edo12.interval_fan(
[
n_edo12.shorthand_interval('M', 3),
n_edo12.shorthand_interval('m', 3),
]
)
print(Cm_1)
print(Cm_2)
print(Cm_1 == Cm_2)
UpDownNoteIntervalFan([M3, m3], 31-EDO)
UpDownNoteIntervalFan([M3, m3], 12-EDO)
False
Equality/Identity also translates to Python’s built-in set type. If two interval fans are considered equal, combining them in a Python set will result in a one-element set:
from xenharmlib import EDOTuning
from xenharmlib import WesternNotation
edo24 = EDOTuning(24)
western = WesternNotation()
ifan_a = edo24.diff_interval_fan(
[0, 4, 10, 14, 18]
)
ifan_b = western.diff_interval_fan(
[0, 2, 5, 7, 9]
)
# since the second element is equal to the first
# only the first element will be added to the set
print({ifan_a, ifan_b})
{EDOPitchIntervalFan([0, 4, 10, 14, 18], 24-EDO)}
Item Retrieval and Slicing¶
Single intervals from the interval fan can be obtained by their index. You can also use slices (including step size) to extract portions of an interval sequence:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 7, 10])
print(ifan[2])
print(ifan[1:3])
print(ifan[::2])
EDOPitchInterval(7, 31-EDO)
EDOPitchIntervalFan([8, 7], 31-EDO)
EDOPitchIntervalFan([10, 7], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '5/4', '10/9'])
print(ifan[2])
print(ifan[1:3])
print(ifan[::2])
PrimeLimitPitchInterval(5/4, 5-Limit)
PrimeLimitPitchIntervalFan([6/5, 5/4], 5-Limit)
PrimeLimitPitchIntervalFan([5/4, 5/4], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([3, 5, 5, 3, 5])
print(ifan[2])
print(ifan[1:4])
print(ifan[::2])
WesternNoteInterval(P, 4)
WesternNoteIntervalFan([P4, P4, A2])
WesternNoteIntervalFan([A2, P4, P4])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([10, 11, 10, 10, 18])
print(ifan[1])
print(ifan[1:4])
print(ifan[::2])
UpDownNoteInterval(d, 4, 31-EDO)
UpDownNoteIntervalFan([d4, M3, M3], 31-EDO)
UpDownNoteIntervalFan([M3, M3, P5], 31-EDO)
Search¶
The index() method returns
the first index position where an interval was found (or raises
ValueError if the interval does not exist in the interval fan).
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 7, 10])
interval = edo31.diff_interval(7)
print(ifan.index(interval))
2
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '5/4', '10/9'])
interval = limit5.rs_interval('6/5')
print(ifan.index(interval))
1
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([3, 5, 5, 3, 5])
interval = western.shorthand_interval('P', 4)
print(ifan.index(interval))
1
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([10, 11, 10, 10, 18])
interval = n_edo31.shorthand_interval('M', 3)
print(ifan.index(interval))
0
Counting¶
For statistical analysis, xenharmlib can calculate the number of times a specific interval occurs in an interval fan:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 7, 10])
interval = edo31.diff_interval(7)
print(ifan.count(interval))
1
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '5/4', '10/9'])
interval = limit5.rs_interval('5/4')
print(ifan.count(interval))
2
from xenharmlib import WesternNotation
western = WesternNotation()
ifan = western.diff_interval_fan([3, 5, 5, 3, 5])
interval = western.shorthand_interval('P', 4)
print(ifan.count(interval))
3
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
ifan = n_edo31.diff_interval_fan([10, 11, 10, 10, 18])
interval = n_edo31.shorthand_interval('P', 5)
print(ifan.count(interval))
1
Concatenation¶
Given two interval fans, the + glues them together into one.
(In programming this is known under the term “concatenation”).
Concatenation of interval fans is especially useful in tonal music if you want to make a collage of two melodic sequences from different scores that are written for a different tonic, centering them both around a new tonic. To achieve this, you first transform the two sequences into two interval fans by providing the respective tonic, then concatenate the two interval fans and make an instance for a different tonic from the result.
from xenharmlib import WesternNotation
western = WesternNotation()
# first four vocal measures from Schubert's "Frühlingssehnsucht"
# ("Säuselnde Lüfte, ...") written for the tonic of Bb
seq_a = western.seq(
[western.note(pcs, 5) for pcs in 'DCC'] +
[western.note(pcs, 4) for pcs in ['Bb', 'A', 'Bb']] +
[western.note(pcs, 5) for pcs in 'CC'] +
[western.note('A', 4)]
)
ifan_a = seq_a.to_interval_fan(western.note('Bb', 4))
# first two vocal measures from Schubert's "Liebesbotschaft"
# ("Rauschendes Bächlein, ...") written for the tonic of G
seq_b = western.seq([western.note(pcs, 4) for pcs in 'BCCDBBEDDC'])
ifan_b = seq_b.to_interval_fan(western.note('G', 4))
# concatenation into one collage
collage = ifan_a + ifan_b
# transform into sequence centered around the tonic C
collage_in_c = western.note('C', 4).seq(collage)
print(collage_in_c)
WesternNoteSeq([E4, D4, D4, C4, B3, C4, D4, D4, B3, E4, F3, F3, G3, E4, E4, A3, G3, G3, F3])
For illustrative purposes, here is also a more abstract set of examples for each tuning / notation:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan_a = edo31.diff_interval_fan([10, 3, 4, 5])
ifan_b = edo31.diff_interval_fan([0, 10, 1])
print(ifan_a + ifan_b)
EDOPitchIntervalFan([10, 3, 4, 5, 0, 10, 1], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan_a = limit5.rs_interval_fan(['5/4', '6/5', '6/5'])
ifan_b = limit5.rs_interval_fan(['1/1', '3/2'])
print(ifan_a + ifan_b)
PrimeLimitPitchIntervalFan([5/4, 6/5, 6/5, 1, 3/2], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
P1 = western.shorthand_interval('P', 1)
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
ifan_a = western.interval_fan([M3, P1, m3])
ifan_b = western.interval_fan([m3, M3, m3])
print(ifan_a + ifan_b)
WesternNoteIntervalFan([M3, P1, m3, m3, M3, m3])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
P1 = n_edo31.shorthand_interval('P', 1)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
ifan_a = n_edo31.interval_fan([M3, P1, m3])
ifan_b = n_edo31.interval_fan([m3, M3, m3])
print(ifan_a + ifan_b)
UpDownNoteIntervalFan([M3, P1, m3, m3, M3, m3], 31-EDO)
Repetition¶
Repetition of an interval fan can be achieved by multiplying
(*) an interval fan with an integer.
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([5, 7, 5, -7, -5])
print(3 * ifan)
EDOPitchIntervalFan([5, 7, 5, -7, -5, 5, 7, 5, -7, -5, 5, 7, 5, -7, -5], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['16/15', '9/8', '9/8', '8/9'])
print(3 * ifan)
PrimeLimitPitchIntervalFan([16/15, 9/8, 9/8, 8/9, 16/15, 9/8, 9/8, 8/9, 16/15, 9/8, 9/8, 8/9], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
M2 = western.shorthand_interval('M', 2)
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
M2down = western.shorthand_interval('M', -2)
ifan = western.interval_fan([M2, M3, m3, M2down, M2down])
print(3 * ifan)
WesternNoteIntervalFan([M2, M3, m3, M-2, M-2, M2, M3, m3, M-2, M-2, M2, M3, m3, M-2, M-2])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
P1 = n_edo31.shorthand_interval('P', 1)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
ifan = n_edo31.interval_fan([M3, P1, m3])
print(3 * ifan)
UpDownNoteIntervalFan([M3, P1, m3, M3, P1, m3, M3, P1, m3], 31-EDO)
Index Masks and Partial Interval Fans¶
Like other sequence-like primitives, interval fans allow the extraction
of “substructures” with the
partial() method, which
expects an index mask expression, i.e., a tuple with indices pointing
to fan elements that should be extracted into its own new interval fan.
The mask (1, 3, 4), for example, extracts the second, fourth, and
fifth element of the fan (like with item retrieval indices in a mask
expression start with 0):
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([0, 10, 18, -18, -28, -10])
print(ifan.partial((1, 3, 4)))
EDOPitchIntervalFan([10, -18, -28], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '9/8', '5/6', '9/10'])
print(ifan.partial((1, 3, 4)))
PrimeLimitPitchIntervalFan([6/5, 5/6, 9/10], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
A1 = western.shorthand_interval('A', 1)
ifan = western.interval_fan([M3, m3, A1, A1, M3, m3])
print(ifan.partial((1, 3, 4)))
WesternNoteIntervalFan([m3, A1, M3])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
A1 = n_edo31.shorthand_interval('A', 1)
ifan = n_edo31.interval_fan([M3, m3, A1, A1, M3, m3])
print(ifan.partial((1, 3, 4)))
UpDownNoteIntervalFan([m3, A1, M3], 31-EDO)
Mask expressions targeted at longer continuous spans inside the interval
fan can be written as a “shortform” with the ellipsis symbol
(...):
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([0, 10, 18, -18, -28, -10])
# an ellipsis as a prefix matches all elements from
# the start of the fan until and including (!)
# the first mask index
print(ifan.partial((..., 3, 4)))
# an ellipsis between two indices matches the two indices
# in the fan and all elements between them
print(ifan.partial((1, ..., 4)))
# an ellipsis at the end matches all remaining indices
# in the fan after the last mask index
print(ifan.partial((0, 3, ...)))
EDOPitchIntervalFan([0, 10, 18, -18, -28], 31-EDO)
EDOPitchIntervalFan([10, 18, -18, -28], 31-EDO)
EDOPitchIntervalFan([0, -18, -28, -10], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '9/8', '5/6', '9/10'])
# an ellipsis as a prefix matches all elements from
# the start of the fan until and including (!)
# the first mask index
print(ifan.partial((..., 3, 4)))
# an ellipsis between two indices matches the two indices
# in the fan and all elements between them
print(ifan.partial((1, ..., 4)))
# an ellipsis at the end matches all remaining indices
# in the fan after the last mask index
print(ifan.partial((0, 3, ...)))
PrimeLimitPitchIntervalFan([5/4, 6/5, 9/8, 5/6, 9/10], 5-Limit)
PrimeLimitPitchIntervalFan([6/5, 9/8, 5/6, 9/10], 5-Limit)
PrimeLimitPitchIntervalFan([5/4, 5/6, 9/10], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
A1 = western.shorthand_interval('A', 1)
ifan = western.interval_fan([M3, m3, A1, A1, M3, m3])
# an ellipsis as a prefix matches all elements from
# the start of the fan until and including (!)
# the first mask index
print(ifan.partial((..., 3, 4)))
# an ellipsis between two indices matches the two indices
# in the fan and all elements between them
print(ifan.partial((1, ..., 4)))
# an ellipsis at the end matches all remaining indices
# in the fan after the last mask index
print(ifan.partial((0, 3, ...)))
WesternNoteIntervalFan([M3, m3, A1, A1, M3])
WesternNoteIntervalFan([m3, A1, A1, M3])
WesternNoteIntervalFan([M3, A1, M3, m3])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
A1 = n_edo31.shorthand_interval('A', 1)
ifan = n_edo31.interval_fan([M3, m3, A1, A1, M3, m3])
# an ellipsis as a prefix matches all elements from
# the start of the fan until and including (!)
# the first mask index
print(ifan.partial((..., 3, 4)))
# an ellipsis between two indices matches the two indices
# in the fan and all elements between them
print(ifan.partial((1, ..., 4)))
# an ellipsis at the end matches all remaining indices
# in the fan after the last mask index
print(ifan.partial((0, 3, ...)))
UpDownNoteIntervalFan([M3, m3, A1, A1, M3], 31-EDO)
UpDownNoteIntervalFan([m3, A1, A1, M3], 31-EDO)
UpDownNoteIntervalFan([M3, A1, M3, m3], 31-EDO)
Selection can also be inverted with the
partial_not() method that
returns all elements not covered by the mask expression:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 10, 9, 10, 8])
print(ifan.partial_not((1, 3, 4)))
EDOPitchIntervalFan([10, 10, 8], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '9/8', '6/5', '10/9'])
print(ifan.partial_not((1, 3, 4)))
PrimeLimitPitchIntervalFan([5/4, 9/8], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
A1 = western.shorthand_interval('A', 1)
ifan = western.interval_fan([M3, m3, A1, A1, M3, m3])
print(ifan.partial_not((1, 3, 4)))
WesternNoteIntervalFan([M3, A1, m3])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
A1 = n_edo31.shorthand_interval('A', 1)
ifan = n_edo31.interval_fan([M3, m3, A1, A1, M3, m3])
print(ifan.partial_not((1, 3, 4)))
UpDownNoteIntervalFan([M3, A1, m3], 31-EDO)
To get both the substructure indicated by the mask and its complement as
a tuple, you can use the
partition() method:
from xenharmlib import EDOTuning
edo31 = EDOTuning(31)
ifan = edo31.diff_interval_fan([10, 8, 10, 9, 10, 8])
a, b = ifan.partition((1, 3, 4))
print(a)
print(b)
EDOPitchIntervalFan([8, 9, 10], 31-EDO)
EDOPitchIntervalFan([10, 10, 8], 31-EDO)
from xenharmlib import PrimeLimitTuning
limit5 = PrimeLimitTuning(5)
ifan = limit5.rs_interval_fan(['5/4', '6/5', '9/8', '6/5', '10/9'])
a, b = ifan.partition((1, 3, 4))
print(a)
print(b)
PrimeLimitPitchIntervalFan([6/5, 6/5, 10/9], 5-Limit)
PrimeLimitPitchIntervalFan([5/4, 9/8], 5-Limit)
from xenharmlib import WesternNotation
western = WesternNotation()
M3 = western.shorthand_interval('M', 3)
m3 = western.shorthand_interval('m', 3)
A1 = western.shorthand_interval('A', 1)
ifan = western.interval_fan([M3, m3, A1, A1, M3, m3])
a, b = ifan.partition((1, 3, 4))
print(a)
print(b)
WesternNoteIntervalFan([m3, A1, M3])
WesternNoteIntervalFan([M3, A1, m3])
from xenharmlib import EDOTuning
from xenharmlib import UpDownNotation
edo31 = EDOTuning(31)
n_edo31 = UpDownNotation(edo31)
M3 = n_edo31.shorthand_interval('M', 3)
m3 = n_edo31.shorthand_interval('m', 3)
A1 = n_edo31.shorthand_interval('A', 1)
ifan = n_edo31.interval_fan([M3, m3, A1, A1, M3, m3])
a, b = ifan.partition((1, 3, 4))
print(a)
print(b)
UpDownNoteIntervalFan([m3, A1, M3], 31-EDO)
UpDownNoteIntervalFan([M3, A1, m3], 31-EDO)