circuitpython-synthtools 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/METADATA +3 -1
- {circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/RECORD +7 -6
- synthtools/__init__.py +7 -1
- synthtools/harmony.py +198 -0
- {circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/WHEEL +0 -0
- {circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/top_level.txt +0 -0
{circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-synthtools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: CircuitPython helper library to do help doing synthio
|
|
5
5
|
Author-email: Tod Kurt <tod@todbot.com>
|
|
6
6
|
License: MIT
|
|
@@ -162,6 +162,8 @@ What's Included
|
|
|
162
162
|
``synthio.Note`` graphs (the engines above have their own portamento,
|
|
163
163
|
via ``mono`` + ``glide_time``)
|
|
164
164
|
* ``RollingAverage`` -- moving-average smoothing for noisy knob reads
|
|
165
|
+
* ``Scale`` / ``chord`` -- scales and diatonic/chromatic chords for
|
|
166
|
+
mapping pads and MIDI onto pitch
|
|
165
167
|
|
|
166
168
|
Documentation
|
|
167
169
|
=============
|
{circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/RECORD
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
circuitpython_synthtools-0.
|
|
2
|
-
synthtools/__init__.py,sha256=
|
|
1
|
+
circuitpython_synthtools-0.7.0.dist-info/licenses/LICENSE,sha256=jhhRyxpqyQxyF_D6SzY2-jCA5v8Z8JXfezCtxBS85CM,1075
|
|
2
|
+
synthtools/__init__.py,sha256=n0ZXMz7Uu0pPEqalR51IxkClI9DTwuUbpu5yPj_whn8,2752
|
|
3
3
|
synthtools/ahr_envelope.py,sha256=fWhjp7LahNGjL8qtTgyiAnX5hnviAULuyc9dEJ6t74I,9233
|
|
4
4
|
synthtools/arpeggiator.py,sha256=n2sPnpZcu9soXsFJCh6OP65_0DuPnvQGUP0ao7V8SXM,3901
|
|
5
5
|
synthtools/audio_fx.py,sha256=VzdWT_pCwfQU5GTb8hVGGMwoFV4lxPwWE1Nm7A1a_Fc,6675
|
|
@@ -7,6 +7,7 @@ synthtools/bassline_synth.py,sha256=Gkz7MKKUITgND4Se5nAJly9sZLQjHhFdd-aFrOAjeVM,
|
|
|
7
7
|
synthtools/blocks.py,sha256=EsGcDEzIJX_dvBN8CVlDrewtl16x5leU03tum9jrhr0,1995
|
|
8
8
|
synthtools/fm_synth.py,sha256=-r4NcsESjEechkbmV0tZx4pXayHIz4qUfIznKtQoKgQ,6073
|
|
9
9
|
synthtools/gauge_cluster.py,sha256=lcMkRK-db6KV6pu0PUJRWAOJT8IdjgpE_4bAi0lQwxc,2910
|
|
10
|
+
synthtools/harmony.py,sha256=V_btMIqO-Ep2x64XwM1A1agSeQqwPcbLqpvGjxlnpUE,6206
|
|
10
11
|
synthtools/param_scaler.py,sha256=dmaFkzRhrfZn54DAgPwZs7P4B4BFd90ZUvMHpWIf2i4,6202
|
|
11
12
|
synthtools/paramset.py,sha256=--V89zHR4pUhkSey1CX7iMLNsF5l1hKmKWrAr3j_cRA,9938
|
|
12
13
|
synthtools/patch.py,sha256=vMr-kdVD66uxY8BrrnB0J6uDLREjuu16I8HzfhAkqHs,6033
|
|
@@ -20,7 +21,7 @@ synthtools/utils.py,sha256=deKQbkGXIBdGUOXBOpRCc-UHdPiL8YmybIJ3BBo9aA4,648
|
|
|
20
21
|
synthtools/waves.py,sha256=lwDka71TsoHGJcyVfmG4t-TEf6TjBwftJEXSS4Qxyx0,22407
|
|
21
22
|
synthtools/wavetable.py,sha256=K1rghAptDYIOwy0PcL9xCllnJH6X3h875MpeuXCm6lQ,2492
|
|
22
23
|
synthtools/wavetable_synth.py,sha256=WUs4FfrKd1hosPiPYnf05I4S8t98ngEMi8qhShkbCps,12896
|
|
23
|
-
circuitpython_synthtools-0.
|
|
24
|
-
circuitpython_synthtools-0.
|
|
25
|
-
circuitpython_synthtools-0.
|
|
26
|
-
circuitpython_synthtools-0.
|
|
24
|
+
circuitpython_synthtools-0.7.0.dist-info/METADATA,sha256=WY049mABTbxBi_qkmr_a_Dh3AZ9ImrnVP0w81veUu0M,7189
|
|
25
|
+
circuitpython_synthtools-0.7.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
26
|
+
circuitpython_synthtools-0.7.0.dist-info/top_level.txt,sha256=1xt53cH064i7vvFVDFyuJugc1NogReXvzsXbYGlgnKE,11
|
|
27
|
+
circuitpython_synthtools-0.7.0.dist-info/RECORD,,
|
synthtools/__init__.py
CHANGED
|
@@ -36,7 +36,7 @@ Anything not listed below is still importable by its module path::
|
|
|
36
36
|
|
|
37
37
|
import sys
|
|
38
38
|
|
|
39
|
-
__version__ = "0.
|
|
39
|
+
__version__ = "0.7.0"
|
|
40
40
|
__repo__ = "https://github.com/todbot/CircuitPython_SynthTools.git"
|
|
41
41
|
|
|
42
42
|
#: Public name -> the submodule that defines it. Every entry is loaded on
|
|
@@ -49,6 +49,12 @@ _LAZY = {
|
|
|
49
49
|
"BasslineSynth": "bassline_synth",
|
|
50
50
|
"FMSynth": "fm_synth",
|
|
51
51
|
"GaugeCluster": "gauge_cluster",
|
|
52
|
+
"Scale": "harmony",
|
|
53
|
+
"chord": "harmony",
|
|
54
|
+
"note_name": "harmony",
|
|
55
|
+
"SCALES": "harmony",
|
|
56
|
+
"CHORDS": "harmony",
|
|
57
|
+
"DIATONIC_SHAPES": "harmony",
|
|
52
58
|
"ParamScaler": "param_scaler",
|
|
53
59
|
"Patch": "patch",
|
|
54
60
|
"load_patches": "patch",
|
synthtools/harmony.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Tod Kurt
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
``harmony``
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
Scales and chords for mapping controllers onto musical pitch.
|
|
8
|
+
|
|
9
|
+
A ``Scale`` is a root MIDI note plus a set of semitone offsets. Index it
|
|
10
|
+
by scale degree to get MIDI notes; degrees past the end (or negative)
|
|
11
|
+
wrap with octave shifts, so a row of pads maps straight onto the scale
|
|
12
|
+
without bounds checks. ``Scale.chord()`` stacks in-key chords by degree;
|
|
13
|
+
the module-level ``chord()`` builds chromatic chords from an interval
|
|
14
|
+
table.
|
|
15
|
+
|
|
16
|
+
Pure Python, no dependencies. Part of synthtools.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# The *_NAMES tuples are written out rather than `tuple(SCALES)` because
|
|
21
|
+
# MicroPython does not guarantee dict iteration order (CPython 3.7+ does),
|
|
22
|
+
# and a UI that builds a selector from the order and stores an index needs
|
|
23
|
+
# it stable across interpreters. Keep each tuple in step with its dict.
|
|
24
|
+
|
|
25
|
+
#: name -> ascending semitone offsets from the root, one octave. The root
|
|
26
|
+
#: (0) is always the first entry; the octave (12) is implied, not listed.
|
|
27
|
+
SCALES = {
|
|
28
|
+
"major": (0, 2, 4, 5, 7, 9, 11),
|
|
29
|
+
"minor": (0, 2, 3, 5, 7, 8, 10), # natural minor / aeolian
|
|
30
|
+
"dorian": (0, 2, 3, 5, 7, 9, 10),
|
|
31
|
+
"phrygian": (0, 1, 3, 5, 7, 8, 10),
|
|
32
|
+
"lydian": (0, 2, 4, 6, 7, 9, 11),
|
|
33
|
+
"mixolydian": (0, 2, 4, 5, 7, 9, 10),
|
|
34
|
+
"locrian": (0, 1, 3, 5, 6, 8, 10),
|
|
35
|
+
"harmonic_minor": (0, 2, 3, 5, 7, 8, 11),
|
|
36
|
+
"melodic_minor": (0, 2, 3, 5, 7, 9, 11),
|
|
37
|
+
"major_pentatonic": (0, 2, 4, 7, 9),
|
|
38
|
+
"minor_pentatonic": (0, 3, 5, 7, 10),
|
|
39
|
+
"blues": (0, 3, 5, 6, 7, 10),
|
|
40
|
+
"whole_tone": (0, 2, 4, 6, 8, 10),
|
|
41
|
+
"chromatic": (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
SCALE_NAMES = (
|
|
45
|
+
"major",
|
|
46
|
+
"minor",
|
|
47
|
+
"dorian",
|
|
48
|
+
"phrygian",
|
|
49
|
+
"lydian",
|
|
50
|
+
"mixolydian",
|
|
51
|
+
"locrian",
|
|
52
|
+
"harmonic_minor",
|
|
53
|
+
"melodic_minor",
|
|
54
|
+
"major_pentatonic",
|
|
55
|
+
"minor_pentatonic",
|
|
56
|
+
"blues",
|
|
57
|
+
"whole_tone",
|
|
58
|
+
"chromatic",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
#: name -> semitone offsets from a root note. Chromatic, key-independent.
|
|
62
|
+
CHORDS = {
|
|
63
|
+
"1": (0,),
|
|
64
|
+
"5": (0, 7),
|
|
65
|
+
"oct": (0, 12),
|
|
66
|
+
"maj": (0, 4, 7),
|
|
67
|
+
"min": (0, 3, 7),
|
|
68
|
+
"dim": (0, 3, 6),
|
|
69
|
+
"aug": (0, 4, 8),
|
|
70
|
+
"sus2": (0, 2, 7),
|
|
71
|
+
"sus4": (0, 5, 7),
|
|
72
|
+
"6": (0, 4, 7, 9),
|
|
73
|
+
"min6": (0, 3, 7, 9),
|
|
74
|
+
"maj7": (0, 4, 7, 11),
|
|
75
|
+
"min7": (0, 3, 7, 10),
|
|
76
|
+
"dom7": (0, 4, 7, 10),
|
|
77
|
+
"dim7": (0, 3, 6, 9),
|
|
78
|
+
"add9": (0, 4, 7, 14),
|
|
79
|
+
"maj9": (0, 4, 7, 11, 14),
|
|
80
|
+
"min9": (0, 3, 7, 10, 14),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
CHORD_NAMES = (
|
|
84
|
+
"1",
|
|
85
|
+
"5",
|
|
86
|
+
"oct",
|
|
87
|
+
"maj",
|
|
88
|
+
"min",
|
|
89
|
+
"dim",
|
|
90
|
+
"aug",
|
|
91
|
+
"sus2",
|
|
92
|
+
"sus4",
|
|
93
|
+
"6",
|
|
94
|
+
"min6",
|
|
95
|
+
"maj7",
|
|
96
|
+
"min7",
|
|
97
|
+
"dom7",
|
|
98
|
+
"dim7",
|
|
99
|
+
"add9",
|
|
100
|
+
"maj9",
|
|
101
|
+
"min9",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
#: name -> scale-degree offsets (not semitones). Stacked in the current
|
|
105
|
+
#: scale, so quality follows the key: "triad" on degree 0 of a major
|
|
106
|
+
#: scale is major, on degree 1 it is minor, and so on.
|
|
107
|
+
DIATONIC_SHAPES = {
|
|
108
|
+
"root": (0,),
|
|
109
|
+
"oct": (0, 7),
|
|
110
|
+
"5th": (0, 4),
|
|
111
|
+
"triad": (0, 2, 4),
|
|
112
|
+
"7th": (0, 2, 4, 6),
|
|
113
|
+
"9th": (0, 2, 4, 6, 8),
|
|
114
|
+
"sus": (0, 3, 4),
|
|
115
|
+
"spread": (0, 4, 9),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
DIATONIC_SHAPE_NAMES = ("root", "oct", "5th", "triad", "7th", "9th", "sus", "spread")
|
|
119
|
+
|
|
120
|
+
NOTE_NAMES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def note_name(midi, with_octave=True):
|
|
124
|
+
"""``"C4"``, ``"F#3"`` ... MIDI 60 is C4 here. Software that calls
|
|
125
|
+
middle C "C3" should subtract 1 from the octave."""
|
|
126
|
+
n = NOTE_NAMES[int(midi) % 12]
|
|
127
|
+
return "%s%d" % (n, int(midi) // 12 - 1) if with_octave else n
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def chord(root, name):
|
|
131
|
+
"""MIDI notes of chromatic chord ``name`` (see ``CHORDS``) on ``root``."""
|
|
132
|
+
return [root + i for i in CHORDS[name]]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Scale:
|
|
136
|
+
"""A scale: a root MIDI note and a set of semitone offsets.
|
|
137
|
+
|
|
138
|
+
:param root: MIDI note of scale degree 0
|
|
139
|
+
:param name: a key of ``SCALES``, or an explicit sequence of ascending
|
|
140
|
+
semitone offsets starting at 0
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(self, root=60, name="major"):
|
|
144
|
+
self.root = root
|
|
145
|
+
self.set_scale(name)
|
|
146
|
+
|
|
147
|
+
def set_scale(self, name):
|
|
148
|
+
"""Switch scale in place, keeping the root. ``name`` is a ``SCALES``
|
|
149
|
+
key or an explicit offset sequence; an unknown name falls back to
|
|
150
|
+
major rather than raising, so a knob mapped past the list is safe."""
|
|
151
|
+
if isinstance(name, str):
|
|
152
|
+
self.name = name if name in SCALES else "major"
|
|
153
|
+
self.steps = SCALES[self.name]
|
|
154
|
+
else:
|
|
155
|
+
self.name = "custom"
|
|
156
|
+
self.steps = tuple(name)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def size(self):
|
|
160
|
+
"""Notes per octave in this scale."""
|
|
161
|
+
return len(self.steps)
|
|
162
|
+
|
|
163
|
+
def degree(self, n):
|
|
164
|
+
"""MIDI note for 0-based scale degree ``n``. ``n`` may be any
|
|
165
|
+
integer: it wraps with octave shifts, so ``degree(size)`` is the
|
|
166
|
+
octave above the root and ``degree(-1)`` the step below it."""
|
|
167
|
+
octaves, i = divmod(int(n), len(self.steps))
|
|
168
|
+
return self.root + 12 * octaves + self.steps[i]
|
|
169
|
+
|
|
170
|
+
def __getitem__(self, n):
|
|
171
|
+
return self.degree(n)
|
|
172
|
+
|
|
173
|
+
def chord(self, degree, shape="triad"):
|
|
174
|
+
"""Diatonic chord as a list of MIDI notes: the degree offsets of
|
|
175
|
+
``shape`` (a ``DIATONIC_SHAPES`` key or an explicit offset
|
|
176
|
+
sequence) added to ``degree``, each taken through the scale so the
|
|
177
|
+
chord stays in key."""
|
|
178
|
+
offsets = DIATONIC_SHAPES[shape] if isinstance(shape, str) else shape
|
|
179
|
+
return [self.degree(degree + o) for o in offsets]
|
|
180
|
+
|
|
181
|
+
def contains(self, midi):
|
|
182
|
+
"""Whether ``midi`` is a note of this scale, in any octave."""
|
|
183
|
+
return (int(midi) - self.root) % 12 in self.steps
|
|
184
|
+
|
|
185
|
+
def snap(self, midi):
|
|
186
|
+
"""The scale note nearest ``midi`` (ties round down). Useful for
|
|
187
|
+
quantizing a chromatic source -- MIDI input, an LFO -- onto the
|
|
188
|
+
scale."""
|
|
189
|
+
rel = int(round(midi)) - self.root
|
|
190
|
+
octaves, semis = divmod(rel, 12)
|
|
191
|
+
best = self.steps[0]
|
|
192
|
+
for step in self.steps:
|
|
193
|
+
if abs(step - semis) < abs(best - semis):
|
|
194
|
+
best = step
|
|
195
|
+
return self.root + 12 * octaves + best
|
|
196
|
+
|
|
197
|
+
def __repr__(self):
|
|
198
|
+
return "Scale(root=%d, name=%r)" % (self.root, self.name)
|
|
File without changes
|
|
File without changes
|
{circuitpython_synthtools-0.6.0.dist-info → circuitpython_synthtools-0.7.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|