Skip to content

lattice

JustLattice

This class implements just intonation lattices.

Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 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
 55
 56
 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
class JustLattice:
    """This class implements just intonation lattices."""

    def __init__(self, fundamental: float, prime_limit: int = 7):
        """Initializes a JustLattice.

        Parameters:
            prime_limit: The prime limit. A prime number.
            fundamental: The 1/1 pitch in Hertz

        Examples:
            >>> JustLattice(60)
            JustLattice(60.0 Hz, 1/1, 60.0 Hz)
        """
        self._fundamental: float = float(fundamental)
        self._prime_limit: int = prime_limit

        self._tone: JustInterval = JustInterval(1, 1)
        self._node: List[int] = [0, 0, 0]
        self._path: List[List[int]] = [self._node]

    def traverse(self, vector: List[int]):
        """Traverses a just intonation lattice.

        Parameters:
            vector: Number of steps along each axis. Steps are assigned to
                an axis based on position. The first argument is on the three-limit
                axis, the second on the five-limit, third on the seven-limit,
                etc. To stay in place on an axis, pass 0 for that axis.

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0])
            JustLattice(60.0 Hz, 3/2, 90.0 Hz)
            >>> lattice.traverse([-2, 0, 0])
            JustLattice(60.0 Hz, 4/3, 80.0 Hz)
            >>> lattice.traverse([1, 1, 0])
            JustLattice(60.0 Hz, 5/4, 75.0 Hz)
            >>> lattice.traverse([0, -2, 0])
            JustLattice(60.0 Hz, 8/5, 96.0 Hz)
            >>> lattice.traverse([0, 1, 1])
            JustLattice(60.0 Hz, 7/4, 105.0 Hz)
            >>> lattice.traverse([0, 0, -2])
            JustLattice(60.0 Hz, 8/7, 68.5714 Hz)
            >>> lattice.traverse([2, 0, 1])
            JustLattice(60.0 Hz, 9/8, 67.5 Hz)
            >>> lattice.traverse([-4, 0, 0])
            JustLattice(60.0 Hz, 16/9, 106.6667 Hz)
        """
        primary_intervals = [
            primary_interval(prime) for prime in generate_primes(self.prime_limit)[1:]
        ]
        if len(vector) > len(primary_intervals):
            return NotImplemented

        for axis, steps in enumerate(list(vector)):
            interval = primary_intervals[axis] ** abs(steps)
            if steps > 0:
                self._tone += interval
            elif steps < 0:
                self._tone -= interval
            else:
                continue

        self._tone = self._tone.base_octave
        self._node = vector
        self._path.append(vector)

        return self

    def undo(self, steps: int = 1):
        """Undo a traversal.

        Parameters:
            steps: Number of steps to undo

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0])
            JustLattice(60.0 Hz, 3/2, 90.0 Hz)
            >>> lattice.undo(1)
            JustLattice(60.0 Hz, 1/1, 60.0 Hz)
        """
        for _ in range(steps):
            vector = self._path.pop()
            self.traverse([-1 * distance for distance in vector])

        return self

    def to_fundamental(self):
        """Return to 1/1 without losing path history.

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0])
            JustLattice(60.0 Hz, 3/2, 90.0 Hz)
            >>> lattice.to_fundamental()
            JustLattice(60.0 Hz, 1/1, 60.0 Hz)
        """
        axes = len(self._node)
        root_node = [0] * axes
        self._node = root_node
        self._tone = JustInterval(1, 1)
        self._path.append(root_node)
        self._pitch = self._fundamental

        return self

    def reset_path(self):
        """Clear path history and return to 1/1."""
        self.to_fundamental()
        axes = len(self._node)
        root_node = [0] * axes
        self._path = [root_node]

        return self

    def to_node(self, node: List[int]):
        """Traverse to a specific node.

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0])
            JustLattice(60.0 Hz, 3/2, 90.0 Hz)
            >>> lattice.to_node([0, 0, 0])
            JustLattice(60.0 Hz, 1/1, 60.0 Hz)
        """
        self.to_fundamental()
        self.traverse(node)

        return self

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

        Returns:
            The prime limit of the lattice

        Examples:
            >>> JustLattice(60).prime_limit
            7
        """
        return self._prime_limit

    @prime_limit.setter
    def prime_limit(self, value: int):
        """The prime limit.

        Parameters:
            value: A prime number.

        Returns:
            The prime limit of the lattice

        Examples:
            >>> JustLattice(60).prime_limit
            7
        """
        if not is_prime(value):
            msg = "Prime limit must be a prime number. "
            msg += "Got '{}'".format(value)
        self._prime_limit = value

    @property
    def fundamental(self) -> float:
        """Fundamental (1/1) pitch in Hertz.

        Returns:
            The fundamental's frequency in Hertz

        Examples:
            >>> JustLattice(60.0).fundamental
            60.0
        """
        return self._fundamental

    @fundamental.setter
    def fundamental(self, value: float):
        """Set the fundamental (1/1) pitch in Hertz."""
        try:
            self._fundamental = float(value)
            self._pitch = self._fundamental * self._tone
        except ValueError:
            msg = "Fundamental must be numeric. Got '{}'.".format(type(value))
            raise ValueError(msg)

    @property
    def hertz(self) -> float:
        """Current node's pitch in Hertz.

        Returns:
            The current node's pitch in Hertz

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0]).hertz
            90.0
        """
        return self._fundamental * self._tone

    @property
    def cents(self) -> float:
        """Current node's interval in Cents.

        Returns:
            The current node's interval in Cents

        Examples:
            >>> lattice = JustLattice(60)
            >>> round(lattice.traverse([1, 0, 0]).cents, 3)
            701.955
        """
        return self._tone.cents

    @property
    def tone(self) -> JustInterval:
        """Current node's tone.

        Returns:
            The current node's tone as a JustInterval

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([1, 0, 0]).tone
            JustInterval(3, 2)
        """
        return self._tone

    @property
    def node(self) -> List[int]:
        """Current node as a vector with length equivalent to number of axes.

        Returns:
            The current node as a vector with length equivalent to number of axes

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([0, 2, 3]).node
            [0, 2, 3]
        """
        return self._node

    @property
    def path(self) -> List[List[int]]:
        """Current traversal history.

        Returns:
            The traversal history across the lattice

        Examples:
            >>> lattice = JustLattice(60)
            >>> lattice.traverse([0, 2, 3]).path
            [[0, 0, 0], [0, 2, 3]]
        """
        return self._path

    def __repr__(self):
        """repr(self)"""
        return "{}({} Hz, {}/{}, {} Hz)".format(
            self.__class__.__name__,
            self.fundamental,
            self.tone.numerator,
            self.tone.denominator,
            round(self.hertz, 4),
        )

prime_limit: int writable property

The prime limit.

Returns:

Type Description
int

The prime limit of the lattice

Examples:

>>> JustLattice(60).prime_limit
7

fundamental: float writable property

Fundamental (1/1) pitch in Hertz.

Returns:

Type Description
float

The fundamental's frequency in Hertz

Examples:

>>> JustLattice(60.0).fundamental
60.0

hertz: float property

Current node's pitch in Hertz.

Returns:

Type Description
float

The current node's pitch in Hertz

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0]).hertz
90.0

cents: float property

Current node's interval in Cents.

Returns:

Type Description
float

The current node's interval in Cents

Examples:

>>> lattice = JustLattice(60)
>>> round(lattice.traverse([1, 0, 0]).cents, 3)
701.955

tone: JustInterval property

Current node's tone.

Returns:

Type Description
JustInterval

The current node's tone as a JustInterval

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0]).tone
JustInterval(3, 2)

node: List[int] property

Current node as a vector with length equivalent to number of axes.

Returns:

Type Description
List[int]

The current node as a vector with length equivalent to number of axes

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([0, 2, 3]).node
[0, 2, 3]

path: List[List[int]] property

Current traversal history.

Returns:

Type Description
List[List[int]]

The traversal history across the lattice

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([0, 2, 3]).path
[[0, 0, 0], [0, 2, 3]]

__init__(fundamental, prime_limit=7)

Initializes a JustLattice.

Parameters:

Name Type Description Default
prime_limit int

The prime limit. A prime number.

7
fundamental float

The 1/1 pitch in Hertz

required

Examples:

>>> JustLattice(60)
JustLattice(60.0 Hz, 1/1, 60.0 Hz)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def __init__(self, fundamental: float, prime_limit: int = 7):
    """Initializes a JustLattice.

    Parameters:
        prime_limit: The prime limit. A prime number.
        fundamental: The 1/1 pitch in Hertz

    Examples:
        >>> JustLattice(60)
        JustLattice(60.0 Hz, 1/1, 60.0 Hz)
    """
    self._fundamental: float = float(fundamental)
    self._prime_limit: int = prime_limit

    self._tone: JustInterval = JustInterval(1, 1)
    self._node: List[int] = [0, 0, 0]
    self._path: List[List[int]] = [self._node]

traverse(vector)

Traverses a just intonation lattice.

Parameters:

Name Type Description Default
vector List[int]

Number of steps along each axis. Steps are assigned to an axis based on position. The first argument is on the three-limit axis, the second on the five-limit, third on the seven-limit, etc. To stay in place on an axis, pass 0 for that axis.

required

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0])
JustLattice(60.0 Hz, 3/2, 90.0 Hz)
>>> lattice.traverse([-2, 0, 0])
JustLattice(60.0 Hz, 4/3, 80.0 Hz)
>>> lattice.traverse([1, 1, 0])
JustLattice(60.0 Hz, 5/4, 75.0 Hz)
>>> lattice.traverse([0, -2, 0])
JustLattice(60.0 Hz, 8/5, 96.0 Hz)
>>> lattice.traverse([0, 1, 1])
JustLattice(60.0 Hz, 7/4, 105.0 Hz)
>>> lattice.traverse([0, 0, -2])
JustLattice(60.0 Hz, 8/7, 68.5714 Hz)
>>> lattice.traverse([2, 0, 1])
JustLattice(60.0 Hz, 9/8, 67.5 Hz)
>>> lattice.traverse([-4, 0, 0])
JustLattice(60.0 Hz, 16/9, 106.6667 Hz)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def traverse(self, vector: List[int]):
    """Traverses a just intonation lattice.

    Parameters:
        vector: Number of steps along each axis. Steps are assigned to
            an axis based on position. The first argument is on the three-limit
            axis, the second on the five-limit, third on the seven-limit,
            etc. To stay in place on an axis, pass 0 for that axis.

    Examples:
        >>> lattice = JustLattice(60)
        >>> lattice.traverse([1, 0, 0])
        JustLattice(60.0 Hz, 3/2, 90.0 Hz)
        >>> lattice.traverse([-2, 0, 0])
        JustLattice(60.0 Hz, 4/3, 80.0 Hz)
        >>> lattice.traverse([1, 1, 0])
        JustLattice(60.0 Hz, 5/4, 75.0 Hz)
        >>> lattice.traverse([0, -2, 0])
        JustLattice(60.0 Hz, 8/5, 96.0 Hz)
        >>> lattice.traverse([0, 1, 1])
        JustLattice(60.0 Hz, 7/4, 105.0 Hz)
        >>> lattice.traverse([0, 0, -2])
        JustLattice(60.0 Hz, 8/7, 68.5714 Hz)
        >>> lattice.traverse([2, 0, 1])
        JustLattice(60.0 Hz, 9/8, 67.5 Hz)
        >>> lattice.traverse([-4, 0, 0])
        JustLattice(60.0 Hz, 16/9, 106.6667 Hz)
    """
    primary_intervals = [
        primary_interval(prime) for prime in generate_primes(self.prime_limit)[1:]
    ]
    if len(vector) > len(primary_intervals):
        return NotImplemented

    for axis, steps in enumerate(list(vector)):
        interval = primary_intervals[axis] ** abs(steps)
        if steps > 0:
            self._tone += interval
        elif steps < 0:
            self._tone -= interval
        else:
            continue

    self._tone = self._tone.base_octave
    self._node = vector
    self._path.append(vector)

    return self

to_fundamental()

Return to 1/1 without losing path history.

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0])
JustLattice(60.0 Hz, 3/2, 90.0 Hz)
>>> lattice.to_fundamental()
JustLattice(60.0 Hz, 1/1, 60.0 Hz)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def to_fundamental(self):
    """Return to 1/1 without losing path history.

    Examples:
        >>> lattice = JustLattice(60)
        >>> lattice.traverse([1, 0, 0])
        JustLattice(60.0 Hz, 3/2, 90.0 Hz)
        >>> lattice.to_fundamental()
        JustLattice(60.0 Hz, 1/1, 60.0 Hz)
    """
    axes = len(self._node)
    root_node = [0] * axes
    self._node = root_node
    self._tone = JustInterval(1, 1)
    self._path.append(root_node)
    self._pitch = self._fundamental

    return self

to_node(node)

Traverse to a specific node.

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0])
JustLattice(60.0 Hz, 3/2, 90.0 Hz)
>>> lattice.to_node([0, 0, 0])
JustLattice(60.0 Hz, 1/1, 60.0 Hz)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def to_node(self, node: List[int]):
    """Traverse to a specific node.

    Examples:
        >>> lattice = JustLattice(60)
        >>> lattice.traverse([1, 0, 0])
        JustLattice(60.0 Hz, 3/2, 90.0 Hz)
        >>> lattice.to_node([0, 0, 0])
        JustLattice(60.0 Hz, 1/1, 60.0 Hz)
    """
    self.to_fundamental()
    self.traverse(node)

    return self

undo(steps=1)

Undo a traversal.

Parameters:

Name Type Description Default
steps int

Number of steps to undo

1

Examples:

>>> lattice = JustLattice(60)
>>> lattice.traverse([1, 0, 0])
JustLattice(60.0 Hz, 3/2, 90.0 Hz)
>>> lattice.undo(1)
JustLattice(60.0 Hz, 1/1, 60.0 Hz)
Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def undo(self, steps: int = 1):
    """Undo a traversal.

    Parameters:
        steps: Number of steps to undo

    Examples:
        >>> lattice = JustLattice(60)
        >>> lattice.traverse([1, 0, 0])
        JustLattice(60.0 Hz, 3/2, 90.0 Hz)
        >>> lattice.undo(1)
        JustLattice(60.0 Hz, 1/1, 60.0 Hz)
    """
    for _ in range(steps):
        vector = self._path.pop()
        self.traverse([-1 * distance for distance in vector])

    return self

reset_path()

Clear path history and return to 1/1.

Source code in /opt/hostedtoolcache/Python/3.11.3/x64/lib/python3.11/site-packages/jintonic/lattice.py
118
119
120
121
122
123
124
125
def reset_path(self):
    """Clear path history and return to 1/1."""
    self.to_fundamental()
    axes = len(self._node)
    root_node = [0] * axes
    self._path = [root_node]

    return self