circuitpython-synthtools 0.7.0__py3-none-any.whl → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-synthtools
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -125,16 +125,17 @@ Usage Example
125
125
  What's Included
126
126
  ===============
127
127
 
128
+ * ``Patch`` -- inert, JSON-able patch data; save/load with
129
+ ``save_patches()`` / ``load_patches()``
128
130
  * ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
129
- with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
131
+ complex filter & pitch envelopes, and a ``mono`` mode for a single-voice
132
+ synth with ``glide_time`` portamento
130
133
  * ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
134
+ * ``WavetableSynth`` -- wavetable-playback with adjustable LFO for wave_pos
131
135
  * ``FMSynth`` -- two-operator phase-modulation voice: a carrier waveform
132
136
  pre-rendered from ``sin(theta + fm_index*sin(fm_ratio*theta))``.
133
137
  ``fm_ratio`` must be an integer; ``fm_index`` is PM depth in radians,
134
- 0 = plain single-oscillator. (Not a live audio-rate bend modulator --
135
- synthio's Math/LFO blocks only update every 256 samples, too slow for
136
- that; see the module docstring for why.)
137
- * ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
138
+ 0 = plain single-oscillator. (Not a full FM synth, see module for details)
138
139
  * ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
139
140
  oscillator, a decay-only filter sweep, per-step slide and accent. Can
140
141
  own its own filter/distortion/echo effects chain via ``fx_*`` patch
@@ -144,26 +145,24 @@ What's Included
144
145
  ``tracking_filter()`` builds extra filter stages that follow the synth's
145
146
  own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
146
147
  the build)
147
- * ``Patch`` -- inert, JSON-able patch data; save/load with
148
- ``save_patches()`` / ``load_patches()``
148
+ * ``Waves`` -- waveform factory (saw, square, sine, triangle, noise, and
149
+ "analog" variants)
149
150
  * ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
150
151
  * ``AHREnvelope`` -- shared-block attack/release envelope, used for both
151
152
  the filter and pitch envelopes
152
- * ``Waves`` -- waveform factory (saw, square, sine, triangle, noise, and
153
- "analog" variants)
154
153
  * ``Arpeggiator`` / ``StepSequencer`` / ``TrigSequencer`` -- poll-based
155
154
  sequencers with on/off callbacks
156
- * ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
157
- knobs than parameters
158
- * ``ParamScaler`` -- proportional ("scale") knob takeover for a single
159
- control, when you are not using ``ParamSet``
160
- * ``GaugeCluster`` -- a bar-graph display of a parameter page
161
155
  * ``Glider`` -- a standalone pitch-slide block for hand-built
162
156
  ``synthio.Note`` graphs (the engines above have their own portamento,
163
157
  via ``mono`` + ``glide_time``)
164
- * ``RollingAverage`` -- moving-average smoothing for noisy knob reads
165
158
  * ``Scale`` / ``chord`` -- scales and diatonic/chromatic chords for
166
159
  mapping pads and MIDI onto pitch
160
+ * ``Param`` / ``ParamSet`` -- (UI) knob-pickup and scaling for UIs with fewer
161
+ knobs than parameters
162
+ * ``ParamScaler`` -- (UI) proportional ("scale") knob takeover for a single
163
+ control, when you are not using ``ParamSet``
164
+ * ``GaugeCluster`` -- (UI) a bar-graph display of a parameter page
165
+ * ``RollingAverage`` -- moving-average smoothing for noisy knob reads
167
166
 
168
167
  Documentation
169
168
  =============
@@ -1,9 +1,9 @@
1
- circuitpython_synthtools-0.7.0.dist-info/licenses/LICENSE,sha256=jhhRyxpqyQxyF_D6SzY2-jCA5v8Z8JXfezCtxBS85CM,1075
2
- synthtools/__init__.py,sha256=n0ZXMz7Uu0pPEqalR51IxkClI9DTwuUbpu5yPj_whn8,2752
1
+ circuitpython_synthtools-0.8.0.dist-info/licenses/LICENSE,sha256=jhhRyxpqyQxyF_D6SzY2-jCA5v8Z8JXfezCtxBS85CM,1075
2
+ synthtools/__init__.py,sha256=dB2eJTgbLs_sfEMCFcm8F_FNoblgNcZd6toMpHFQThw,2752
3
3
  synthtools/ahr_envelope.py,sha256=fWhjp7LahNGjL8qtTgyiAnX5hnviAULuyc9dEJ6t74I,9233
4
4
  synthtools/arpeggiator.py,sha256=n2sPnpZcu9soXsFJCh6OP65_0DuPnvQGUP0ao7V8SXM,3901
5
- synthtools/audio_fx.py,sha256=VzdWT_pCwfQU5GTb8hVGGMwoFV4lxPwWE1Nm7A1a_Fc,6675
6
- synthtools/bassline_synth.py,sha256=Gkz7MKKUITgND4Se5nAJly9sZLQjHhFdd-aFrOAjeVM,28112
5
+ synthtools/audio_fx.py,sha256=PjnETf5kKIlG_tLKwM49PCvSkM2txfZf2o7MuEnU2W4,8778
6
+ synthtools/bassline_synth.py,sha256=X8efKBgemBP2qAX0hL3PgWk3c_jkBWo0aX4KrgccHKE,51547
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
@@ -16,12 +16,12 @@ synthtools/step_sequencer.py,sha256=SkQuDsRtpAdKOXU3OsFmngeDPVQ0OXt3Gj5wqFCkEPM,
16
16
  synthtools/subtractive_synth.py,sha256=WIpmhbiiLtyQRFV9mHWWM-duST5RfY9HUN4SRf5wTOw,3382
17
17
  synthtools/swarm_synth.py,sha256=kCYsdVuaafTKkD06_m22iFaPUMCzeBKX67mkrsEq3pk,10945
18
18
  synthtools/synth.py,sha256=ZdmAKWBVzJqPyzU5W1hxnJ6j09D4rnCWm77s5jRW_Ww,36528
19
- synthtools/trig_sequencer.py,sha256=PaWgsTqOvuRjFUJ6VjxMLBuRkyeYCML9WQXBzx02Onk,2921
19
+ synthtools/trig_sequencer.py,sha256=J3_PdbNmYEoFkz7Hj58qNzMRZQuUm-sf2dKZIrBTV8U,3052
20
20
  synthtools/utils.py,sha256=deKQbkGXIBdGUOXBOpRCc-UHdPiL8YmybIJ3BBo9aA4,648
21
21
  synthtools/waves.py,sha256=lwDka71TsoHGJcyVfmG4t-TEf6TjBwftJEXSS4Qxyx0,22407
22
- synthtools/wavetable.py,sha256=K1rghAptDYIOwy0PcL9xCllnJH6X3h875MpeuXCm6lQ,2492
23
- synthtools/wavetable_synth.py,sha256=WUs4FfrKd1hosPiPYnf05I4S8t98ngEMi8qhShkbCps,12896
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,,
22
+ synthtools/wavetable.py,sha256=7zQdAvHyiSp7wiVjl-lIfeifpbdsp7M5cCVbMT7eku0,3725
23
+ synthtools/wavetable_synth.py,sha256=nggGXEr3tlADvTI38gCDRSw8XEaB5kHkzvQGSFfw16E,7969
24
+ circuitpython_synthtools-0.8.0.dist-info/METADATA,sha256=K6l8EWT1soJUgzphRNq1V1Qhi3mdcCYXt0VAHjE5MFw,7139
25
+ circuitpython_synthtools-0.8.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
26
+ circuitpython_synthtools-0.8.0.dist-info/top_level.txt,sha256=1xt53cH064i7vvFVDFyuJugc1NogReXvzsXbYGlgnKE,11
27
+ circuitpython_synthtools-0.8.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.7.0"
39
+ __version__ = "0.8.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
synthtools/audio_fx.py CHANGED
@@ -26,10 +26,14 @@
26
26
  # Identical resonant sections stacked this way do pile up gain at the cutoff,
27
27
  # each stage adding its own peak on top of the last. At a squelchy filt_q
28
28
  # that is real headroom to watch, and also most of why a cascaded resonant
29
- # filter reads as more aggressive than a single one.
29
+ # filter reads as more aggressive than a single one. tracking_filter()'s
30
+ # `spread` is the lever: offsetting the stages spreads those peaks out the
31
+ # way a real ladder's mismatched poles do.
30
32
 
31
33
  import synthio
32
34
 
35
+ from .blocks import product
36
+
33
37
  try:
34
38
  import audiofilters
35
39
  except ImportError: # not in every CircuitPython build
@@ -103,33 +107,66 @@ class EffectsChain:
103
107
  src = stage
104
108
 
105
109
 
106
- def tracking_filter(synth, stages=1, buffer_size=1024, mix=1.0):
110
+ def tracking_filter(synth, stages=1, buffer_size=1024, mix=1.0, hpf_f=None, spread=1.0):
107
111
  """Build one ``audiofilters.Filter`` holding ``stages`` extra Biquads
108
112
  that track ``synth.filter``'s cutoff AND resonance; see the module
109
113
  comment for why that's automatic once they share the live blocks.
110
114
  ``stages`` extra 12 dB/octave sections plus the synth's own give
111
115
  ``12*(stages+1)`` overall.
112
116
 
117
+ ``spread`` offsets each stage's cutoff above the last by that ratio,
118
+ approximating a real ladder's wide pole spread instead of stacking
119
+ identical resonant peaks on one frequency; 1.0 shares one node and
120
+ allocates nothing.
121
+
122
+ ``hpf_f`` adds a fixed high-pass section at that frequency in Hz, in
123
+ the SAME biquad tuple, so it costs no extra buffer and no extra pass.
124
+ It is a plain number rather than a shared block on purpose: it must
125
+ not follow the cutoff. A TB-303's thin bass comes from small coupling
126
+ capacitors between the filter and the VCA, which sit at one corner
127
+ whatever the filter is doing. ``stages=0`` is legal with it, for a
128
+ high-pass alone.
129
+
113
130
  Raises ``ImportError`` if this build has no ``audiofilters``,
114
- ``ValueError`` if ``stages < 1`` or ``synth`` has no filter to track
115
- (either a poly synth, which has no ``.filter`` at all, or a mono one
116
- built with ``filt_type=None``).
131
+ ``ValueError`` if ``stages < 1`` without an ``hpf_f``, or if tracking
132
+ stages were asked for and ``synth`` has no filter to track (either a
133
+ poly synth, which has no ``.filter`` at all, or a mono one built with
134
+ ``filt_type=None``).
117
135
  """
118
136
  if audiofilters is None:
119
137
  raise ImportError("audiofilters is not in this CircuitPython build")
120
- if stages < 1:
138
+ if stages < 1 and not hpf_f:
121
139
  raise ValueError("stages must be >= 1")
122
140
  src = getattr(synth, "filter", None)
123
- if src is None:
141
+ if stages >= 1 and src is None:
124
142
  raise ValueError("synth has no filter to track (mono-only, and filt_type must be set)")
125
143
  synthesizer = synth.synthio
126
144
  # Copies sharing src's frequency AND Q blocks, both live, so both track
127
145
  # the synth (sweep, accent, a filt_q turn) with nothing to keep in sync
128
146
  # by hand. One Filter holding a tuple, not a Filter each: `filter` runs
129
147
  # the sample through them in order, saving a buffer and a pass per stage.
130
- biquads = tuple(
131
- synthio.Biquad(src.mode, frequency=src.frequency, Q=src.Q) for _ in range(stages)
132
- )
148
+ # `spread` offsets each stage above the last by that ratio. Identical
149
+ # cascaded sections pile their resonant peaks on one frequency; real
150
+ # ladder filters do not, and the TB-303's least of all, its bottom
151
+ # ladder capacitor being half the value of the other three. Mismatched
152
+ # stages approximate that wider pole spread and take the edge off the
153
+ # ringing. 1.0, the default, shares one node and allocates nothing.
154
+ #
155
+ # An offset node needs NO rooting: measured on rp2040, a Math reachable
156
+ # only through a Biquad inside an audiofilters.Filter tracks the sweep
157
+ # exactly, while an otherwise identical one reachable from nothing at
158
+ # all reads 0.0 forever. Nesting counts across the audiofilters
159
+ # boundary, so `synthesizer.blocks` stays out of this.
160
+ sections = []
161
+ ratio = 1.0
162
+ for _ in range(stages):
163
+ freq = src.frequency if ratio == 1.0 else product(src.frequency, ratio)
164
+ sections.append(synthio.Biquad(src.mode, frequency=freq, Q=src.Q))
165
+ ratio *= spread
166
+ biquads = tuple(sections)
167
+ if hpf_f:
168
+ # last, so it sheds low end from everything ahead of it
169
+ biquads += (synthio.Biquad(synthio.FilterMode.HIGH_PASS, frequency=hpf_f, Q=0.707),)
133
170
  return audiofilters.Filter(
134
171
  filter=biquads,
135
172
  mix=mix,