Skip to content

intervals

primary_interval(prime_limit, sub_harmonic=False, max_pot_exp=500)

Returns the primary interval for a given prime limit.

Parameters:

Name Type Description Default
prime_limit int

A prime number

required
sub_harmonic bool

Whether the primary interval is a harmonic primary (the default) or a sub-harmonic primary.

False
max_pot_exp int

Maximum power of two exponent to limit iterations

500

Examples:

>>> primary_interval(7)
JustInterval(7, 4)
>>> primary_interval(19)
JustInterval(19, 16)
>>> primary_interval(3, sub_harmonic=True)
JustInterval(4, 3)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def primary_interval(
    prime_limit: int,
    sub_harmonic: bool = False,
    max_pot_exp: int = 500,
) -> JustInterval:
    """Returns the primary interval for a given prime limit.

    Parameters:
        prime_limit: A prime number
        sub_harmonic: Whether the primary interval is a harmonic primary
            (the default) or a sub-harmonic primary.
        max_pot_exp: Maximum power of two exponent to limit iterations

    Examples:
        >>> primary_interval(7)
        JustInterval(7, 4)

        >>> primary_interval(19)
        JustInterval(19, 16)

        >>> primary_interval(3, sub_harmonic=True)
        JustInterval(4, 3)
    """
    pot = 2
    exp = 2
    max_pot = 2**max_pot_exp
    while (pot**exp < prime_limit) and (pot < max_pot):
        exp += 1
    pot = pot ** (exp - 1)
    if sub_harmonic:
        return JustInterval(prime_limit, pot).complement
    return JustInterval(prime_limit, pot)

JustInterval

This class implements just intonation intervals.

Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
class JustInterval:
    """This class implements just intonation intervals."""

    def __init__(self, numerator: int, denominator: int):
        """Initializes a JustInterval.

        Parameters:
            numerator: Numerator
            denominator: Denominator

        Examples:
            >>> JustInterval(4, 3)
            JustInterval(4, 3)
        """
        if not (isinstance(numerator, int) and isinstance(denominator, int)):
            msg = "Both components must be integers. "
            msg += "Got numerator: {}, denominator: {}".format(
                type(numerator), type(denominator)
            )
            raise TypeError(msg)

        if denominator > numerator:
            msg = "Numerator must be greater than or equal to denominator. "
            msg += "Got numerator: {}, denominator: {}".format(numerator, denominator)
            raise ValueError(msg)

        if denominator == 0:
            raise ZeroDivisionError("JustRatio({}, 0)".format(numerator))

        if denominator < 1:
            msg = "Denominator must be greater than 0. "
            msg += "Got: {}".format(numerator)
            raise ValueError(msg)

        common = gcd(numerator, denominator)
        numerator //= common
        denominator //= common

        self._numerator = numerator
        self._denominator = denominator

    @classmethod
    def from_string(cls, interval: str) -> JustInterval:
        """Creates a JustInterval from a string representation.

        Parameters:
            interval: A string representation of an interval in the
                `numerator:denominator` format.

        Returns:
            A JustInterval

        Examples:
            >>> JustInterval.from_string('3:2')
            JustInterval(3, 2)

            >>> JustInterval.from_string('3:3')
            JustInterval(1, 1)

            >>> JustInterval.from_string('6:3')
            JustInterval(2, 1)
        """
        re_match = _RATIO_FORMAT.match(interval)
        if re_match is None:
            msg = "Invalid literal for JustRatio: {}".format(interval)
            raise ValueError(msg)
        try:
            numerator = int(re_match.group("num"))
            denominator = int(re_match.group("denom"))
            return JustInterval(numerator=numerator, denominator=denominator)
        except ValueError:
            msg = "Invalid literal for JustRatio: {}".format(interval)
            raise ValueError(msg)

    @classmethod
    def from_two_hertz(cls, apitch: float, bpitch: float) -> JustInterval:
        """Creates a JustInterval from two Hertz values.

        Parameters:
            apitch: A value in Hertz (which will be truncated to an int)
            bpitch: A value in Hertz (which will be truncated to an int)

        Returns:
            A JustInterval

        Examples:
            >>> JustInterval.from_two_hertz(220, 440)
            JustInterval(2, 1)
        """
        smaller = apitch if apitch <= bpitch else bpitch
        greater = apitch if apitch > bpitch else bpitch

        return cls(int(greater), int(smaller))

    def divisions(
        self,
        divisor: int,
        prime_limit: int = 7,
        max_iterations: int = 30,
    ) -> List[JustInterval]:
        """Divides a just interval into intervals that respect a prime limit.

        Parameters:
            divisor: Number of divisions.
            prime_limit: The prime limit, a prime number.
            max_iterations: A limit to how many divisions this method will
                try before giving up on dividing within the prime limit.

        Returns:
            The resulting JustIntervals

        Examples:
            >>> JustInterval(2, 1).divisions(4, 5)
            [JustInterval(10, 9), JustInterval(9, 8), JustInterval(6, 5),
            JustInterval(4, 3)]

            >>> JustInterval(16, 15).divisions(2, 31)
            [JustInterval(32, 31), JustInterval(31, 30)]
        """
        if not is_prime(prime_limit):
            msg = "The prime limit must be a prime number. "
            msg += "Got: '{}'".format(prime_limit)
            raise ValueError(msg)

        factor = divisor
        divisions: List[JustInterval] = []
        divrange: List[int] = []
        i = 0
        while i <= max_iterations:
            num = self.numerator * factor
            denom = self.denominator * factor

            divrange = list(range(denom, num + 1))
            primes: List[int] = []
            for number in divrange:
                if is_prime(number) and (number > prime_limit):
                    primes.append(number)
            for prime in primes:
                divrange.remove(prime)
            if len(divrange) != divisor + 1:
                factor += 1
                i += 1
                continue
            else:
                break

        divrange.reverse()
        for j, number in enumerate(divrange):
            try:
                divisions.append(JustInterval(number, divrange[j + 1]))
            except IndexError:
                continue

        return sorted(divisions)

    @property
    def numerator(self) -> int:
        """JustInterval numerator."""
        return self._numerator

    @property
    def denominator(self) -> int:
        """JustInterval denominator."""
        return self._denominator

    @property
    def prime_limit(self) -> int:
        """JustInterval prime limit.

        Examples:
            >>> JustInterval(64, 49).prime_limit
            7
        """
        if self.base_octave == JustInterval(1, 1):
            return 1
        num = max(prime_factors(self.numerator))
        denom = max(prime_factors(self.denominator))

        return max(num, denom)

    @property
    def is_superparticular(self) -> bool:
        """Superparticular just intervals are of the form x+1:x

        Examples:
            >>> JustInterval(3, 2).is_superparticular
            True
        """
        return self.numerator == self.denominator + 1

    @property
    def base_octave(self) -> JustInterval:
        """The interval within the range 1:1 to 2:1

        Examples:
            >>> JustInterval.from_string('9:4').base_octave
            JustInterval(9, 8)
        """
        cself = deepcopy(self)
        while cself >= JustInterval(2, 1):
            cself -= JustInterval(2, 1)
        return cself

    @property
    def complement(self) -> JustInterval:
        """JustInterval complement.

        The interval which, when added to this interval, yields an
        octave. This only applies to intervals smaller than the octave and
        will return `NotImplemented` if self is an interval larger than an
        octave.

        Returns:
            The interval's complement

        Note:
            Returns a copy

        Examples:
            >>> JustInterval(3, 2).complement
            JustInterval(4, 3)
        """
        octave = JustInterval(2, 1)
        if self < octave:
            return octave - self
        if self == octave:
            return JustInterval(1, 1)
        return NotImplemented

    @property
    def cents(self) -> float:
        """JustInterval expressed in Cents.

        Returns:
            The interval in Cents

        Examples:
            >>> round(JustInterval(3, 2).cents, 3)
            701.955
        """
        return log(self.numerator / self.denominator, 10) * (1200 / log(2, 10))

    def _prepare_division(self, number: int) -> Tuple[int, int]:
        """Helper for JustInterval division."""
        if not isinstance(number, int):
            msg = "Must be int, not {}".format(type(number))
            raise TypeError(msg)

        if not self.is_superparticular:
            if number % (self.numerator - self.denominator):
                msg = "{} is not divisible by {}".format(self, number)
                raise ValueError(msg)
            factor = number // (self.numerator - self.denominator)
            num = self.numerator * factor
            denom = self.denominator * factor

        else:
            num = self.numerator * number
            denom = self.denominator * number

        return num, denom

    def __repr__(self):
        """repr(self)"""
        return "{}({}, {})".format(
            self.__class__.__name__, self._numerator, self.denominator
        )

    def __add__(self, other: JustInterval) -> JustInterval:
        """JustInterval addition.

        Examples:

        >>> JustInterval(3, 2) + JustInterval(4, 3)
        JustInterval(2, 1)
        """
        if not isinstance(other, JustInterval):
            other = JustInterval(other.numerator, other.denominator)
        return JustInterval(
            self.numerator * other.numerator, self.denominator * other.denominator
        )

    def __radd__(self, other: JustInterval) -> JustInterval:
        """JustInterval right of operator addition.

        Examples:

        >>> sum((JustInterval(3, 2), JustInterval(4, 3)))
        JustInterval(2, 1)
        """
        if other == 0:
            other = JustInterval(1, 1)
        return self + other

    def __sub__(self, other: JustInterval) -> JustInterval:
        """JustInterval substraction.

        Examples:

        >>> JustInterval(3, 2) - JustInterval(9, 8)
        JustInterval(4, 3)
        >>> JustInterval(1, 1) - JustInterval(3, 2)
        JustInterval(4, 3)
        """
        if other > self:
            cself = deepcopy(self)
            cself += JustInterval(2, 1)
            return cself - other
        return JustInterval(
            self.numerator * other.denominator, self.denominator * other.numerator
        )

    def __mul__(self, other):
        """JustInterval left of operator multiplication."""
        return NotImplemented

    def __rmul__(self, other: float) -> float:
        """JustInterval right of operator multiplication.

        This is used to calculate the absolute frequency of a pitch _this_
        interval above it.

        Examples:

        >>> 440 * JustInterval(3, 2)
        660.0
        """
        try:
            return other * (self.numerator / self.denominator)
        except TypeError:
            msg = "Can't multiply sequence by non-int of type '{}'".format(
                self.__class__.__name__
            )
            raise TypeError(msg)

    def __pow__(self, other: int) -> JustInterval:
        """JustInterval raised to a power. Used for chaining an interval.

        Examples:

        >>> JustInterval(3, 2) ** 3
        JustInterval(27, 8)
        """
        if other < 0:
            msg = "JustInterval can only be raised to positive powers. "
            msg += "Got '{}'".format(other)
            raise ValueError(msg)
        elif other == 0:
            return JustInterval(1, 1)
        return sum([self] * other)  # type: ignore

    def __truediv__(self, other: int) -> List[JustInterval]:
        """Naive JustInterval division.

        For divisions within a given prime limit, use self.divisions()

        :rtype: JustInterval

        Examples:

        >>> JustInterval(2, 1) / 2
        [JustInterval(4, 3), JustInterval(3, 2)]
        >>> JustInterval(2, 1) / 3
        [JustInterval(6, 5), JustInterval(5, 4), JustInterval(4, 3)]
        >>> JustInterval(7, 4) / 3
        [JustInterval(7, 6), JustInterval(6, 5), JustInterval(5, 4)]
        """
        num, denom = self._prepare_division(other)

        return sorted([JustInterval(n, n - 1) for n in range(denom + 1, num + 1)])

    def __rtruediv__(self, other):
        """There's no sense dividing something by a JustInterval."""
        return NotImplemented

    def _richcmp(self, other: JustInterval, oper: Callable) -> bool:
        """Helper for comparison operators, for internal use only."""
        return oper(
            self.numerator / self.denominator, other.numerator / other.denominator
        )

    def __eq__(self, other: Any) -> bool:
        """self == other"""
        if not isinstance(other, JustInterval):
            return False
        return self._richcmp(other, operator.eq)

    def __lt__(self, other: JustInterval) -> bool:
        """self < other"""
        return self._richcmp(other, operator.lt)

    def __gt__(self, other: JustInterval) -> bool:
        """self > other"""
        return self._richcmp(other, operator.gt)

    def __le__(self, other: JustInterval) -> bool:
        """self <= other"""
        return self._richcmp(other, operator.le)

    def __ge__(self, other: JustInterval) -> bool:
        """self >= other"""
        return self._richcmp(other, operator.ge)

    def __bool__(self) -> bool:
        """self != 0"""
        return self._numerator != 0

prime_limit: int property

JustInterval prime limit.

Examples:

>>> JustInterval(64, 49).prime_limit
7

is_superparticular: bool property

Superparticular just intervals are of the form x+1:x

Examples:

>>> JustInterval(3, 2).is_superparticular
True

base_octave: JustInterval property

The interval within the range 1:1 to 2:1

Examples:

>>> JustInterval.from_string('9:4').base_octave
JustInterval(9, 8)

complement: JustInterval property

JustInterval complement.

The interval which, when added to this interval, yields an octave. This only applies to intervals smaller than the octave and will return NotImplemented if self is an interval larger than an octave.

Returns:

Type Description
JustInterval

The interval's complement

Note

Returns a copy

Examples:

>>> JustInterval(3, 2).complement
JustInterval(4, 3)

cents: float property

JustInterval expressed in Cents.

Returns:

Type Description
float

The interval in Cents

Examples:

>>> round(JustInterval(3, 2).cents, 3)
701.955

__init__(numerator, denominator)

Initializes a JustInterval.

Parameters:

Name Type Description Default
numerator int

Numerator

required
denominator int

Denominator

required

Examples:

>>> JustInterval(4, 3)
JustInterval(4, 3)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(self, numerator: int, denominator: int):
    """Initializes a JustInterval.

    Parameters:
        numerator: Numerator
        denominator: Denominator

    Examples:
        >>> JustInterval(4, 3)
        JustInterval(4, 3)
    """
    if not (isinstance(numerator, int) and isinstance(denominator, int)):
        msg = "Both components must be integers. "
        msg += "Got numerator: {}, denominator: {}".format(
            type(numerator), type(denominator)
        )
        raise TypeError(msg)

    if denominator > numerator:
        msg = "Numerator must be greater than or equal to denominator. "
        msg += "Got numerator: {}, denominator: {}".format(numerator, denominator)
        raise ValueError(msg)

    if denominator == 0:
        raise ZeroDivisionError("JustRatio({}, 0)".format(numerator))

    if denominator < 1:
        msg = "Denominator must be greater than 0. "
        msg += "Got: {}".format(numerator)
        raise ValueError(msg)

    common = gcd(numerator, denominator)
    numerator //= common
    denominator //= common

    self._numerator = numerator
    self._denominator = denominator

from_string(interval) classmethod

Creates a JustInterval from a string representation.

Parameters:

Name Type Description Default
interval str

A string representation of an interval in the numerator:denominator format.

required

Returns:

Type Description
JustInterval

A JustInterval

Examples:

>>> JustInterval.from_string('3:2')
JustInterval(3, 2)
>>> JustInterval.from_string('3:3')
JustInterval(1, 1)
>>> JustInterval.from_string('6:3')
JustInterval(2, 1)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@classmethod
def from_string(cls, interval: str) -> JustInterval:
    """Creates a JustInterval from a string representation.

    Parameters:
        interval: A string representation of an interval in the
            `numerator:denominator` format.

    Returns:
        A JustInterval

    Examples:
        >>> JustInterval.from_string('3:2')
        JustInterval(3, 2)

        >>> JustInterval.from_string('3:3')
        JustInterval(1, 1)

        >>> JustInterval.from_string('6:3')
        JustInterval(2, 1)
    """
    re_match = _RATIO_FORMAT.match(interval)
    if re_match is None:
        msg = "Invalid literal for JustRatio: {}".format(interval)
        raise ValueError(msg)
    try:
        numerator = int(re_match.group("num"))
        denominator = int(re_match.group("denom"))
        return JustInterval(numerator=numerator, denominator=denominator)
    except ValueError:
        msg = "Invalid literal for JustRatio: {}".format(interval)
        raise ValueError(msg)

from_two_hertz(apitch, bpitch) classmethod

Creates a JustInterval from two Hertz values.

Parameters:

Name Type Description Default
apitch float

A value in Hertz (which will be truncated to an int)

required
bpitch float

A value in Hertz (which will be truncated to an int)

required

Returns:

Type Description
JustInterval

A JustInterval

Examples:

>>> JustInterval.from_two_hertz(220, 440)
JustInterval(2, 1)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@classmethod
def from_two_hertz(cls, apitch: float, bpitch: float) -> JustInterval:
    """Creates a JustInterval from two Hertz values.

    Parameters:
        apitch: A value in Hertz (which will be truncated to an int)
        bpitch: A value in Hertz (which will be truncated to an int)

    Returns:
        A JustInterval

    Examples:
        >>> JustInterval.from_two_hertz(220, 440)
        JustInterval(2, 1)
    """
    smaller = apitch if apitch <= bpitch else bpitch
    greater = apitch if apitch > bpitch else bpitch

    return cls(int(greater), int(smaller))

divisions(divisor, prime_limit=7, max_iterations=30)

Divides a just interval into intervals that respect a prime limit.

Parameters:

Name Type Description Default
divisor int

Number of divisions.

required
prime_limit int

The prime limit, a prime number.

7
max_iterations int

A limit to how many divisions this method will try before giving up on dividing within the prime limit.

30

Returns:

Type Description
List[JustInterval]

The resulting JustIntervals

Examples:

>>> JustInterval(2, 1).divisions(4, 5)
[JustInterval(10, 9), JustInterval(9, 8), JustInterval(6, 5),
JustInterval(4, 3)]
>>> JustInterval(16, 15).divisions(2, 31)
[JustInterval(32, 31), JustInterval(31, 30)]
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/intervals.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def divisions(
    self,
    divisor: int,
    prime_limit: int = 7,
    max_iterations: int = 30,
) -> List[JustInterval]:
    """Divides a just interval into intervals that respect a prime limit.

    Parameters:
        divisor: Number of divisions.
        prime_limit: The prime limit, a prime number.
        max_iterations: A limit to how many divisions this method will
            try before giving up on dividing within the prime limit.

    Returns:
        The resulting JustIntervals

    Examples:
        >>> JustInterval(2, 1).divisions(4, 5)
        [JustInterval(10, 9), JustInterval(9, 8), JustInterval(6, 5),
        JustInterval(4, 3)]

        >>> JustInterval(16, 15).divisions(2, 31)
        [JustInterval(32, 31), JustInterval(31, 30)]
    """
    if not is_prime(prime_limit):
        msg = "The prime limit must be a prime number. "
        msg += "Got: '{}'".format(prime_limit)
        raise ValueError(msg)

    factor = divisor
    divisions: List[JustInterval] = []
    divrange: List[int] = []
    i = 0
    while i <= max_iterations:
        num = self.numerator * factor
        denom = self.denominator * factor

        divrange = list(range(denom, num + 1))
        primes: List[int] = []
        for number in divrange:
            if is_prime(number) and (number > prime_limit):
                primes.append(number)
        for prime in primes:
            divrange.remove(prime)
        if len(divrange) != divisor + 1:
            factor += 1
            i += 1
            continue
        else:
            break

    divrange.reverse()
    for j, number in enumerate(divrange):
        try:
            divisions.append(JustInterval(number, divrange[j + 1]))
        except IndexError:
            continue

    return sorted(divisions)