circuitpython-synthtools 0.5__tar.gz → 0.5.1__tar.gz
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.5 → circuitpython_synthtools-0.5.1}/PKG-INFO +18 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/README.rst +17 -4
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/circuitpython_synthtools.egg-info/PKG-INFO +18 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthtools_bassline_demo.py +10 -11
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/pyproject.toml +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/__init__.py +2 -2
- circuitpython_synthtools-0.5.1/synthtools/audio_fx.py +162 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/bassline_synth.py +303 -2
- circuitpython_synthtools-0.5.1/tests/test_audio_fx.py +399 -0
- circuitpython_synthtools-0.5/synthtools/audio_fx.py +0 -180
- circuitpython_synthtools-0.5/tests/test_audio_fx.py +0 -189
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.gitattributes +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.github/workflows/build.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.github/workflows/failure-help-text.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.github/workflows/release_gh.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.github/workflows/release_pypi.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.gitignore +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.pre-commit-config.yaml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.pylintrc +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/.readthedocs.yaml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/CODE_OF_CONDUCT.md +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/LICENSE +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/LICENSES/CC-BY-4.0.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/LICENSES/MIT.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/LICENSES/Unlicense.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/README.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/circuitpython_synthtools.egg-info/SOURCES.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/circuitpython_synthtools.egg-info/dependency_links.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/circuitpython_synthtools.egg-info/requires.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/circuitpython_synthtools.egg-info/top_level.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/conftest.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/_static/custom.css +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/_static/favicon.ico +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/_static/favicon.ico.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/api.rst +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/api.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/conf.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/examples.rst +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/examples.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/index.rst +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/index.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/docs/requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/REUSE.toml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/arpeggiator_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/step_sequencer_bassdemo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/step_sequencer_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synth_setup.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthinst_code1.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthinst_fenv_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthinst_pad_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthinst_pitch_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthinst_wave_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthtools_ahr_envelope_bend.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthtools_simpletest.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthtools_wavetable_simple.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/trig_sequencer_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavetables/BRAIDS02.WAV +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavetables/PLAITS02.WAV +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/00_909kick4.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/01_909snare2.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/02_909hatclosed2a.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/03_909hatopen5.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/04_909clap1.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/05_909tommed.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/06_909blip.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/07_909cym2.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/wavs/kit0_909/readme.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/optional_requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/ruff.toml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/setup.cfg +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/ahr_envelope.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/arpeggiator.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/blocks.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/paramset.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/patch.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/pitch_glider.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/step_sequencer.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/subtractive_synth.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/synth.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/trig_sequencer.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/ui/gauge_cluster.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/ui/param.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/ui/param_scaler.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/utils.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/waves.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/wavetable.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/synthtools/wavetable_synth.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/README.md +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/hw/run_on_device.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/hw/test_device.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/plot_env.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/run_tests.sh +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/stubs/synthio.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/stubs/ulab/__init__.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/stubs/ulab/numpy.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/stubs_fx/audiodelays.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/stubs_fx/audiofilters.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_arpeggiator.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_env_shapes.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_mono.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_patch.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_pytest_smoke.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/tests/test_wiring.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-synthtools
|
|
3
|
-
Version: 0.5
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: CircuitPython helper library to do help doing synthio
|
|
5
5
|
Author-email: Tod Kurt <tod@todbot.com>
|
|
6
6
|
License: MIT
|
|
@@ -44,6 +44,15 @@ Introduction
|
|
|
44
44
|
|
|
45
45
|
CircuitPython library with tools for making synths with synthio
|
|
46
46
|
|
|
47
|
+
This library is a collection of tools derived from my many years of playing
|
|
48
|
+
with CircuitPython, ``synthio``, and building synthesizers in general.
|
|
49
|
+
Concepts pulled from these projects and others:
|
|
50
|
+
|
|
51
|
+
- `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
|
|
52
|
+
- `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
|
|
53
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
54
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
55
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
47
56
|
|
|
48
57
|
Dependencies
|
|
49
58
|
=============
|
|
@@ -120,10 +129,14 @@ What's Included
|
|
|
120
129
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
121
130
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
122
131
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
123
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
133
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
134
|
+
fields
|
|
135
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
136
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
137
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
138
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
139
|
+
the build)
|
|
127
140
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
128
141
|
``save_patches()`` / ``load_patches()``
|
|
129
142
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
|
@@ -24,6 +24,15 @@ Introduction
|
|
|
24
24
|
|
|
25
25
|
CircuitPython library with tools for making synths with synthio
|
|
26
26
|
|
|
27
|
+
This library is a collection of tools derived from my many years of playing
|
|
28
|
+
with CircuitPython, ``synthio``, and building synthesizers in general.
|
|
29
|
+
Concepts pulled from these projects and others:
|
|
30
|
+
|
|
31
|
+
- `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
|
|
32
|
+
- `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
|
|
33
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
34
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
35
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
27
36
|
|
|
28
37
|
Dependencies
|
|
29
38
|
=============
|
|
@@ -100,10 +109,14 @@ What's Included
|
|
|
100
109
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
101
110
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
102
111
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
103
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
113
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
114
|
+
fields
|
|
115
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
116
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
117
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
118
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
119
|
+
the build)
|
|
107
120
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
108
121
|
``save_patches()`` / ``load_patches()``
|
|
109
122
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-synthtools
|
|
3
|
-
Version: 0.5
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: CircuitPython helper library to do help doing synthio
|
|
5
5
|
Author-email: Tod Kurt <tod@todbot.com>
|
|
6
6
|
License: MIT
|
|
@@ -44,6 +44,15 @@ Introduction
|
|
|
44
44
|
|
|
45
45
|
CircuitPython library with tools for making synths with synthio
|
|
46
46
|
|
|
47
|
+
This library is a collection of tools derived from my many years of playing
|
|
48
|
+
with CircuitPython, ``synthio``, and building synthesizers in general.
|
|
49
|
+
Concepts pulled from these projects and others:
|
|
50
|
+
|
|
51
|
+
- `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
|
|
52
|
+
- `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
|
|
53
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
54
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
55
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
47
56
|
|
|
48
57
|
Dependencies
|
|
49
58
|
=============
|
|
@@ -120,10 +129,14 @@ What's Included
|
|
|
120
129
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
121
130
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
122
131
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
123
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
133
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
134
|
+
fields
|
|
135
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
136
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
137
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
138
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
139
|
+
the build)
|
|
127
140
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
128
141
|
``save_patches()`` / ``load_patches()``
|
|
129
142
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
{circuitpython_synthtools-0.5 → circuitpython_synthtools-0.5.1}/examples/synthtools_bassline_demo.py
RENAMED
|
@@ -62,27 +62,26 @@ patch = Patch(
|
|
|
62
62
|
amp_level=0.75, # un-accented level, so accents have room to be louder
|
|
63
63
|
slide_time=0.09,
|
|
64
64
|
transpose=0,
|
|
65
|
+
# --- BasslineSynth's own effects chain, if this build has
|
|
66
|
+
# audiofilters. A synthio.Note holds ONE Biquad, so the voice alone
|
|
67
|
+
# is 12 dB/octave; one extra stage makes 24, where the squelch really
|
|
68
|
+
# lives. The stage tracks synth.filter's cutoff AND resonance on its
|
|
69
|
+
# own, so it follows the sweep and the accent with nothing to keep in
|
|
70
|
+
# sync by hand -- see fx_filter_stages in bassline_synth.py.
|
|
71
|
+
fx_filter_stages=1,
|
|
65
72
|
)
|
|
66
73
|
|
|
67
74
|
synth = BasslineSynth(engine, patch)
|
|
68
75
|
|
|
69
|
-
# --- extra filter slope, if this build has audiofilters -----------------
|
|
70
|
-
# A synthio.Note holds ONE Biquad, so the voice alone is 12 dB/octave.
|
|
71
|
-
# One extra stage makes 24, which is where the squelch really lives. The
|
|
72
|
-
# stage tracks synth.cutoff_block, so it follows the sweep and the accent
|
|
73
|
-
# with nothing to keep in sync by hand.
|
|
74
76
|
try:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
fx = EffectsChain(synth, stages=1)
|
|
78
|
-
mixer.voice[0].play(fx.output) # replaces synth_setup's direct hookup
|
|
77
|
+
mixer.voice[0].play(synth.output) # replaces synth_setup's direct hookup
|
|
79
78
|
print("filter: 24 dB/octave (1 extra stage)")
|
|
80
79
|
except ImportError:
|
|
81
80
|
print("no audiofilters in this build -- 12 dB/octave, voice filter only")
|
|
81
|
+
mixer.voice[0].play(synth.synthio)
|
|
82
82
|
|
|
83
83
|
# --- the pattern --------------------------------------------------------
|
|
84
|
-
# (midi_note, slide, accent), or None for a rest.
|
|
85
|
-
# what turn a scale into a bassline, so this leans on both.
|
|
84
|
+
# (midi_note, slide, accent), or None for a rest.
|
|
86
85
|
PATTERN = (
|
|
87
86
|
(36, False, True),
|
|
88
87
|
(36, False, False),
|
|
@@ -13,7 +13,7 @@ requires = [
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "circuitpython-synthtools"
|
|
15
15
|
description = "CircuitPython helper library to do help doing synthio"
|
|
16
|
-
version = "0.5"
|
|
16
|
+
version = "0.5.1"
|
|
17
17
|
readme = "README.rst"
|
|
18
18
|
authors = [
|
|
19
19
|
{name = "Tod Kurt", email = "tod@todbot.com"}
|
|
@@ -25,7 +25,7 @@ Implementation Notes
|
|
|
25
25
|
|
|
26
26
|
# imports
|
|
27
27
|
|
|
28
|
-
from .audio_fx import EffectsChain
|
|
28
|
+
from .audio_fx import EffectsChain, set_drive, sync_delay, tracking_filter
|
|
29
29
|
from .bassline_synth import BasslineSynth
|
|
30
30
|
from .patch import Patch, load_patches, save_patches
|
|
31
31
|
from .subtractive_synth import SubtractiveSynth
|
|
@@ -41,5 +41,5 @@ try:
|
|
|
41
41
|
except ImportError:
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
|
-
__version__ = "0.5"
|
|
44
|
+
__version__ = "0.5.1"
|
|
45
45
|
__repo__ = "https://github.com/todbot/CircuitPython_SynthTools.git"
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Tod Kurt
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
# audio_fx.py - a generic post-synth effects chain, plus a factory for the
|
|
5
|
+
# one effect that needs synth-specific knowledge: extra filter stages that
|
|
6
|
+
# track the synth's own cutoff and resonance.
|
|
7
|
+
#
|
|
8
|
+
# EffectsChain itself never imports audiofilters/audiodelays -- it just
|
|
9
|
+
# calls `.play(source)` on whatever effect objects it's handed, so it loads
|
|
10
|
+
# fine even where those modules don't exist. Only tracking_filter() (and
|
|
11
|
+
# whatever Distortion/Echo objects a caller constructs) needs them.
|
|
12
|
+
#
|
|
13
|
+
# A synthio.Note takes ONE Biquad, so 12 dB/octave is all a voice can do.
|
|
14
|
+
# Steeper means cascading more biquads downstream, and those need
|
|
15
|
+
# somewhere stable to point their `frequency`: Synth rebuilds the voice's
|
|
16
|
+
# cutoff node at every note-on, and it freezes once the Note is freed.
|
|
17
|
+
# BasslineSynth is mono, so it keeps ONE Biquad over one stable cutoff
|
|
18
|
+
# node -- copy `synth.filter`'s frequency and you track it for good.
|
|
19
|
+
#
|
|
20
|
+
# Slope: every Biquad is 12 dB/oct, so `stages` extra ones plus the
|
|
21
|
+
# synth's own give 12*(stages+1). (A 303 is usually called ~18 dB/oct,
|
|
22
|
+
# which cascaded 2-pole sections cannot make at all.)
|
|
23
|
+
#
|
|
24
|
+
# Q tracks too, on every stage, matching the synth this was ported from
|
|
25
|
+
# (its `resonance` setter pushes the same Q into the voice filter and both
|
|
26
|
+
# extra ones by hand). Here it is automatic: `src.Q` is the synth's live
|
|
27
|
+
# `filt_q` block, the same object the voice's own Biquad reads, so wiring
|
|
28
|
+
# an extra stage's Q to it needs no propagation code at all -- one knob
|
|
29
|
+
# turn reaches every stage the instant it reaches the voice.
|
|
30
|
+
#
|
|
31
|
+
# Identical resonant sections stacked like this do pile up gain at the
|
|
32
|
+
# cutoff faster than one section alone -- each stage adds its own peak on
|
|
33
|
+
# top of the last. At a squelchy filt_q that is real headroom to watch for;
|
|
34
|
+
# it is also most of why a cascaded resonant filter reads as more
|
|
35
|
+
# aggressive than a single one, which is the point here.
|
|
36
|
+
|
|
37
|
+
import synthio
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import audiofilters
|
|
41
|
+
except ImportError: # not in every CircuitPython build
|
|
42
|
+
audiofilters = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EffectsChain:
|
|
46
|
+
"""A synth's output, plus an ordered chain of playback effects it runs
|
|
47
|
+
through. Add, insert, or remove any ``audiofilters``/``audiodelays``
|
|
48
|
+
effect object; the chain keeps every effect's ``.play(source)`` pointed
|
|
49
|
+
at whatever now precedes it::
|
|
50
|
+
|
|
51
|
+
fx = EffectsChain(synth)
|
|
52
|
+
fx.add(tracking_filter(synth, stages=1))
|
|
53
|
+
fx.add(audiofilters.Distortion(mix=0.0, drive=0.5, ...))
|
|
54
|
+
mixer.voice[0].play(fx.output)
|
|
55
|
+
|
|
56
|
+
``output`` is always the tail -- the synth itself when the chain is
|
|
57
|
+
empty. Building an ``EffectsChain`` touches no effects module at all,
|
|
58
|
+
so it loads fine even where ``audiofilters`` doesn't exist; only the
|
|
59
|
+
effects you add to it need it.
|
|
60
|
+
|
|
61
|
+
A mixer voice holds whatever object ``output`` *was* at the time you
|
|
62
|
+
called ``play()`` -- it has no way to notice ``output`` changing
|
|
63
|
+
identity later. Any ``add``/``insert``/``remove`` that changes the
|
|
64
|
+
tail (an ``add``, an ``insert`` at the end, or removing the current
|
|
65
|
+
tail) needs ``mixer.voice[0].play(fx.output)`` called again to reach
|
|
66
|
+
the speaker; an insert/remove in the *middle* rewires in place and
|
|
67
|
+
needs nothing further, since the tail object itself doesn't change.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, synth):
|
|
71
|
+
self.synth = synth
|
|
72
|
+
self._effects = []
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def effects(self):
|
|
76
|
+
"""The chain in order, outermost effect last. Read-only -- go
|
|
77
|
+
through :meth:`add`/:meth:`insert`/:meth:`remove` so an effect's
|
|
78
|
+
``.play()`` source can never drift out of sync with this list."""
|
|
79
|
+
return tuple(self._effects)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def output(self):
|
|
83
|
+
"""The tail of the chain: what a mixer voice should play."""
|
|
84
|
+
return self._effects[-1] if self._effects else self.synth.synthio
|
|
85
|
+
|
|
86
|
+
def add(self, effect):
|
|
87
|
+
"""Append ``effect`` to the end of the chain and return it."""
|
|
88
|
+
effect.play(self.output)
|
|
89
|
+
self._effects.append(effect)
|
|
90
|
+
return effect
|
|
91
|
+
|
|
92
|
+
def insert(self, index, effect):
|
|
93
|
+
"""Insert ``effect`` at ``index`` (Python list semantics, negative
|
|
94
|
+
included), rewiring everything from there on."""
|
|
95
|
+
self._effects.insert(index, effect)
|
|
96
|
+
self._rewire(self._effects.index(effect))
|
|
97
|
+
return effect
|
|
98
|
+
|
|
99
|
+
def remove(self, effect):
|
|
100
|
+
"""Take ``effect`` out of the chain and rewire around the gap."""
|
|
101
|
+
index = self._effects.index(effect)
|
|
102
|
+
del self._effects[index]
|
|
103
|
+
self._rewire(index)
|
|
104
|
+
|
|
105
|
+
def _rewire(self, from_index):
|
|
106
|
+
src = self._effects[from_index - 1] if from_index > 0 else self.synth.synthio
|
|
107
|
+
for stage in self._effects[from_index:]:
|
|
108
|
+
stage.play(src)
|
|
109
|
+
src = stage
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def tracking_filter(synth, stages=1, buffer_size=1024, mix=1.0):
|
|
113
|
+
"""Build one ``audiofilters.Filter`` holding ``stages`` extra Biquads
|
|
114
|
+
that track ``synth.filter``'s cutoff AND resonance -- see the module
|
|
115
|
+
comment for why that's automatic once they share the live blocks.
|
|
116
|
+
``stages`` extra 12 dB/octave sections plus the synth's own give
|
|
117
|
+
``12*(stages+1)`` overall.
|
|
118
|
+
|
|
119
|
+
Raises ``ImportError`` if this build has no ``audiofilters``,
|
|
120
|
+
``ValueError`` if ``stages < 1`` or ``synth`` has no filter to track
|
|
121
|
+
(either a poly synth, which has no ``.filter`` at all, or a mono one
|
|
122
|
+
built with ``filt_type=None``).
|
|
123
|
+
"""
|
|
124
|
+
if audiofilters is None:
|
|
125
|
+
raise ImportError("audiofilters is not in this CircuitPython build")
|
|
126
|
+
if stages < 1:
|
|
127
|
+
raise ValueError("stages must be >= 1")
|
|
128
|
+
src = getattr(synth, "filter", None)
|
|
129
|
+
if src is None:
|
|
130
|
+
raise ValueError("synth has no filter to track (mono-only, and filt_type must be set)")
|
|
131
|
+
synthesizer = synth.synthio
|
|
132
|
+
# Copies sharing src's frequency AND Q blocks -- both live, so both
|
|
133
|
+
# track the synth (sweep, accent, and a filt_q knob turn) with nothing
|
|
134
|
+
# to keep in sync by hand. One Filter holding a tuple, not a Filter
|
|
135
|
+
# each: `filter` runs the sample through them in order, saving a
|
|
136
|
+
# buffer and a pass per stage.
|
|
137
|
+
biquads = tuple(
|
|
138
|
+
synthio.Biquad(src.mode, frequency=src.frequency, Q=src.Q) for _ in range(stages)
|
|
139
|
+
)
|
|
140
|
+
return audiofilters.Filter(
|
|
141
|
+
filter=biquads,
|
|
142
|
+
mix=mix,
|
|
143
|
+
sample_rate=synthesizer.sample_rate,
|
|
144
|
+
channel_count=synthesizer.channel_count,
|
|
145
|
+
buffer_size=buffer_size,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def set_drive(distortion, amount):
|
|
150
|
+
"""Set a ``Distortion`` effect's drive, 0..1, through ``pre_gain`` and
|
|
151
|
+
``post_gain`` -- LOFI mode's own ``drive`` parameter does not do what
|
|
152
|
+
its name suggests; ``post_gain`` pulls back the level ``pre_gain``
|
|
153
|
+
adds."""
|
|
154
|
+
distortion.pre_gain = amount * 50.0
|
|
155
|
+
distortion.post_gain = amount * -25.0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def sync_delay(echo, bpm, steps=4, steps_per_beat=4):
|
|
159
|
+
"""Set an ``Echo`` effect's ``delay_ms`` to ``steps`` sequencer steps
|
|
160
|
+
at ``bpm`` -- a tempo-synced delay is most of what makes a repeating
|
|
161
|
+
line sit right."""
|
|
162
|
+
echo.delay_ms = (60_000.0 / bpm / steps_per_beat) * steps
|