circuitpython-synthtools 0.5__tar.gz → 0.6.0__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.egg-info → circuitpython_synthtools-0.6.0}/PKG-INFO +28 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/README.rst +27 -4
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0/circuitpython_synthtools.egg-info}/PKG-INFO +28 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/SOURCES.txt +14 -3
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/conftest.py +4 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/api.rst +24 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/conf.py +11 -2
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/examples.rst +9 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synth_setup.py +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_code1.py +2 -2
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_fenv_demo.py +24 -24
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_pad_demo.py +16 -16
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_pitch_demo.py +26 -26
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_wave_demo.py +5 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_ahr_envelope_bend.py +11 -15
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_bassline_demo.py +26 -27
- circuitpython_synthtools-0.6.0/examples/synthtools_fm_demo.py +64 -0
- circuitpython_synthtools-0.6.0/examples/synthtools_midi_demo.py +112 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_simpletest.py +1 -1
- circuitpython_synthtools-0.6.0/examples/synthtools_swarm_demo.py +133 -0
- circuitpython_synthtools-0.6.0/examples/synthtools_wavetable_simple.py +29 -0
- circuitpython_synthtools-0.6.0/examples/ui/synthtools_gaugecluster_test.py +20 -0
- circuitpython_synthtools-0.6.0/examples/ui/synthtools_paramscaler_demo.py +205 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/pyproject.toml +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/ruff.toml +8 -8
- circuitpython_synthtools-0.6.0/synthtools/__init__.py +84 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/ahr_envelope.py +32 -45
- circuitpython_synthtools-0.6.0/synthtools/audio_fx.py +155 -0
- circuitpython_synthtools-0.6.0/synthtools/bassline_synth.py +696 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/blocks.py +4 -4
- circuitpython_synthtools-0.6.0/synthtools/fm_synth.py +150 -0
- {circuitpython_synthtools-0.5/synthtools/ui → circuitpython_synthtools-0.6.0/synthtools}/gauge_cluster.py +4 -4
- circuitpython_synthtools-0.6.0/synthtools/param_scaler.py +144 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/paramset.py +90 -30
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/patch.py +26 -13
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/subtractive_synth.py +9 -11
- circuitpython_synthtools-0.6.0/synthtools/swarm_synth.py +253 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/synth.py +211 -114
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/utils.py +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/waves.py +132 -60
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/wavetable.py +9 -10
- circuitpython_synthtools-0.6.0/synthtools/wavetable_synth.py +303 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/README.md +52 -15
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/hw/run_on_device.py +2 -2
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/hw/test_device.py +17 -6
- circuitpython_synthtools-0.6.0/tests/hw/test_display_cost.py +319 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/plot_env.py +3 -3
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/run_tests.sh +9 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/synthio.py +2 -2
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/ulab/__init__.py +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/ulab/numpy.py +2 -2
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs_fx/audiodelays.py +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs_fx/audiofilters.py +1 -1
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_arpeggiator.py +4 -4
- circuitpython_synthtools-0.6.0/tests/test_audio_fx.py +399 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_env_shapes.py +6 -6
- circuitpython_synthtools-0.6.0/tests/test_fm_synth.py +153 -0
- circuitpython_synthtools-0.6.0/tests/test_mono.py +494 -0
- circuitpython_synthtools-0.6.0/tests/test_param_scaler.py +154 -0
- circuitpython_synthtools-0.6.0/tests/test_paramset.py +167 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_patch.py +18 -5
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_pytest_smoke.py +3 -3
- circuitpython_synthtools-0.6.0/tests/test_swarm_synth.py +260 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_wiring.py +126 -28
- circuitpython_synthtools-0.5/examples/synthtools_wavetable_simple.py +0 -31
- circuitpython_synthtools-0.5/synthtools/__init__.py +0 -45
- circuitpython_synthtools-0.5/synthtools/audio_fx.py +0 -180
- circuitpython_synthtools-0.5/synthtools/bassline_synth.py +0 -410
- circuitpython_synthtools-0.5/synthtools/ui/param.py +0 -108
- circuitpython_synthtools-0.5/synthtools/ui/param_scaler.py +0 -83
- circuitpython_synthtools-0.5/synthtools/wavetable_synth.py +0 -80
- circuitpython_synthtools-0.5/tests/test_audio_fx.py +0 -189
- circuitpython_synthtools-0.5/tests/test_mono.py +0 -260
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.gitattributes +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/build.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/failure-help-text.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/release_gh.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/release_pypi.yml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.gitignore +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.pre-commit-config.yaml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.pylintrc +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.readthedocs.yaml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/CODE_OF_CONDUCT.md +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSE +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/MIT.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/Unlicense.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/README.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/dependency_links.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/requires.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/top_level.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/custom.css +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/favicon.ico +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/favicon.ico.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/api.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/examples.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/index.rst +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/index.rst.license +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/REUSE.toml +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/arpeggiator_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/step_sequencer_bassdemo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/step_sequencer_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/trig_sequencer_demo.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavetables/BRAIDS02.WAV +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavetables/PLAITS02.WAV +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/00_909kick4.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/01_909snare2.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/02_909hatclosed2a.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/03_909hatopen5.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/04_909clap1.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/05_909tommed.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/06_909blip.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/07_909cym2.wav +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/readme.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/optional_requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/requirements.txt +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/setup.cfg +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/arpeggiator.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/pitch_glider.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/step_sequencer.py +0 -0
- {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/trig_sequencer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-synthtools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -44,6 +44,16 @@ 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
|
+
- `synthiota <https://github.com/todbot/synthiota>`_
|
|
54
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
55
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
56
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
47
57
|
|
|
48
58
|
Dependencies
|
|
49
59
|
=============
|
|
@@ -118,12 +128,22 @@ What's Included
|
|
|
118
128
|
* ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
|
|
119
129
|
with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
|
|
120
130
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
131
|
+
* ``FMSynth`` -- two-operator phase-modulation voice: a carrier waveform
|
|
132
|
+
pre-rendered from ``sin(theta + fm_index*sin(fm_ratio*theta))``.
|
|
133
|
+
``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.)
|
|
121
137
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
122
138
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
123
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
140
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
141
|
+
fields
|
|
142
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
143
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
144
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
145
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
146
|
+
the build)
|
|
127
147
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
128
148
|
``save_patches()`` / ``load_patches()``
|
|
129
149
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
|
@@ -135,6 +155,9 @@ What's Included
|
|
|
135
155
|
sequencers with on/off callbacks
|
|
136
156
|
* ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
|
|
137
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
|
|
138
161
|
* ``Glider`` -- a standalone pitch-slide block for hand-built
|
|
139
162
|
``synthio.Note`` graphs (the engines above have their own portamento,
|
|
140
163
|
via ``mono`` + ``glide_time``)
|
|
@@ -24,6 +24,16 @@ 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
|
+
- `synthiota <https://github.com/todbot/synthiota>`_
|
|
34
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
35
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
36
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
27
37
|
|
|
28
38
|
Dependencies
|
|
29
39
|
=============
|
|
@@ -98,12 +108,22 @@ What's Included
|
|
|
98
108
|
* ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
|
|
99
109
|
with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
|
|
100
110
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
111
|
+
* ``FMSynth`` -- two-operator phase-modulation voice: a carrier waveform
|
|
112
|
+
pre-rendered from ``sin(theta + fm_index*sin(fm_ratio*theta))``.
|
|
113
|
+
``fm_ratio`` must be an integer; ``fm_index`` is PM depth in radians,
|
|
114
|
+
0 = plain single-oscillator. (Not a live audio-rate bend modulator --
|
|
115
|
+
synthio's Math/LFO blocks only update every 256 samples, too slow for
|
|
116
|
+
that; see the module docstring for why.)
|
|
101
117
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
102
118
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
103
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
120
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
121
|
+
fields
|
|
122
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
123
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
124
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
125
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
126
|
+
the build)
|
|
107
127
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
108
128
|
``save_patches()`` / ``load_patches()``
|
|
109
129
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
|
@@ -115,6 +135,9 @@ What's Included
|
|
|
115
135
|
sequencers with on/off callbacks
|
|
116
136
|
* ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
|
|
117
137
|
knobs than parameters
|
|
138
|
+
* ``ParamScaler`` -- proportional ("scale") knob takeover for a single
|
|
139
|
+
control, when you are not using ``ParamSet``
|
|
140
|
+
* ``GaugeCluster`` -- a bar-graph display of a parameter page
|
|
118
141
|
* ``Glider`` -- a standalone pitch-slide block for hand-built
|
|
119
142
|
``synthio.Note`` graphs (the engines above have their own portamento,
|
|
120
143
|
via ``mono`` + ``glide_time``)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-synthtools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -44,6 +44,16 @@ 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
|
+
- `synthiota <https://github.com/todbot/synthiota>`_
|
|
54
|
+
- `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
|
|
55
|
+
- `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
|
|
56
|
+
- `picostepseq <https://github.com/todbot/picostepseq>`_
|
|
47
57
|
|
|
48
58
|
Dependencies
|
|
49
59
|
=============
|
|
@@ -118,12 +128,22 @@ What's Included
|
|
|
118
128
|
* ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
|
|
119
129
|
with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
|
|
120
130
|
* ``SubtractiveSynth`` -- subtractive two-oscillator synth w/ detune
|
|
131
|
+
* ``FMSynth`` -- two-operator phase-modulation voice: a carrier waveform
|
|
132
|
+
pre-rendered from ``sin(theta + fm_index*sin(fm_ratio*theta))``.
|
|
133
|
+
``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.)
|
|
121
137
|
* ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
|
|
122
138
|
* ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
|
|
123
|
-
oscillator, a decay-only filter sweep, per-step slide and accent
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
oscillator, a decay-only filter sweep, per-step slide and accent. Can
|
|
140
|
+
own its own filter/distortion/echo effects chain via ``fx_*`` patch
|
|
141
|
+
fields
|
|
142
|
+
* ``EffectsChain`` -- a generic post-synth effects chain: add, insert, or
|
|
143
|
+
remove any ``audiofilters``/``audiodelays`` effect and it stays wired.
|
|
144
|
+
``tracking_filter()`` builds extra filter stages that follow the synth's
|
|
145
|
+
own cutoff and resonance for a steeper slope (needs ``audiofilters`` in
|
|
146
|
+
the build)
|
|
127
147
|
* ``Patch`` -- inert, JSON-able patch data; save/load with
|
|
128
148
|
``save_patches()`` / ``load_patches()``
|
|
129
149
|
* ``Wavetable`` -- loads a wavetable WAV file and lerps between frames
|
|
@@ -135,6 +155,9 @@ What's Included
|
|
|
135
155
|
sequencers with on/off callbacks
|
|
136
156
|
* ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
|
|
137
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
|
|
138
161
|
* ``Glider`` -- a standalone pitch-slide block for hand-built
|
|
139
162
|
``synthio.Note`` graphs (the engines above have their own portamento,
|
|
140
163
|
via ``mono`` + ``glide_time``)
|
|
@@ -48,9 +48,14 @@ examples/synthinst_pitch_demo.py
|
|
|
48
48
|
examples/synthinst_wave_demo.py
|
|
49
49
|
examples/synthtools_ahr_envelope_bend.py
|
|
50
50
|
examples/synthtools_bassline_demo.py
|
|
51
|
+
examples/synthtools_fm_demo.py
|
|
52
|
+
examples/synthtools_midi_demo.py
|
|
51
53
|
examples/synthtools_simpletest.py
|
|
54
|
+
examples/synthtools_swarm_demo.py
|
|
52
55
|
examples/synthtools_wavetable_simple.py
|
|
53
56
|
examples/trig_sequencer_demo.py
|
|
57
|
+
examples/ui/synthtools_gaugecluster_test.py
|
|
58
|
+
examples/ui/synthtools_paramscaler_demo.py
|
|
54
59
|
examples/wavetables/BRAIDS02.WAV
|
|
55
60
|
examples/wavetables/PLAITS02.WAV
|
|
56
61
|
examples/wavs/kit0_909/00_909kick4.wav
|
|
@@ -68,32 +73,38 @@ synthtools/arpeggiator.py
|
|
|
68
73
|
synthtools/audio_fx.py
|
|
69
74
|
synthtools/bassline_synth.py
|
|
70
75
|
synthtools/blocks.py
|
|
76
|
+
synthtools/fm_synth.py
|
|
77
|
+
synthtools/gauge_cluster.py
|
|
78
|
+
synthtools/param_scaler.py
|
|
71
79
|
synthtools/paramset.py
|
|
72
80
|
synthtools/patch.py
|
|
73
81
|
synthtools/pitch_glider.py
|
|
74
82
|
synthtools/step_sequencer.py
|
|
75
83
|
synthtools/subtractive_synth.py
|
|
84
|
+
synthtools/swarm_synth.py
|
|
76
85
|
synthtools/synth.py
|
|
77
86
|
synthtools/trig_sequencer.py
|
|
78
87
|
synthtools/utils.py
|
|
79
88
|
synthtools/waves.py
|
|
80
89
|
synthtools/wavetable.py
|
|
81
90
|
synthtools/wavetable_synth.py
|
|
82
|
-
synthtools/ui/gauge_cluster.py
|
|
83
|
-
synthtools/ui/param.py
|
|
84
|
-
synthtools/ui/param_scaler.py
|
|
85
91
|
tests/README.md
|
|
86
92
|
tests/plot_env.py
|
|
87
93
|
tests/run_tests.sh
|
|
88
94
|
tests/test_arpeggiator.py
|
|
89
95
|
tests/test_audio_fx.py
|
|
90
96
|
tests/test_env_shapes.py
|
|
97
|
+
tests/test_fm_synth.py
|
|
91
98
|
tests/test_mono.py
|
|
99
|
+
tests/test_param_scaler.py
|
|
100
|
+
tests/test_paramset.py
|
|
92
101
|
tests/test_patch.py
|
|
93
102
|
tests/test_pytest_smoke.py
|
|
103
|
+
tests/test_swarm_synth.py
|
|
94
104
|
tests/test_wiring.py
|
|
95
105
|
tests/hw/run_on_device.py
|
|
96
106
|
tests/hw/test_device.py
|
|
107
|
+
tests/hw/test_display_cost.py
|
|
97
108
|
tests/stubs/synthio.py
|
|
98
109
|
tests/stubs/ulab/__init__.py
|
|
99
110
|
tests/stubs/ulab/numpy.py
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: Copyright (c) 2026 Tod Kurt
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
#
|
|
4
|
-
# This project has no pytest suite
|
|
4
|
+
# This project has no pytest suite; see tests/README.md. The real tests
|
|
5
5
|
# are plain scripts run via `sh tests/run_tests.sh`, deliberately runnable
|
|
6
6
|
# under a bare MicroPython too, which is incompatible with how pytest
|
|
7
7
|
# collects and executes test modules.
|
|
8
8
|
#
|
|
9
9
|
# The shared Adafruit CI build action runs bare `python -m pytest`
|
|
10
10
|
# whenever a tests/ directory exists at all, with no way to opt out or
|
|
11
|
-
# scope it
|
|
11
|
+
# scope it, so without this file, pytest recursively discovers and
|
|
12
12
|
# tries to *import* every test_*.py / *_test.py file in the whole repo:
|
|
13
13
|
# - examples/*_test.py expect to be copied flat onto a CIRCUITPY drive
|
|
14
|
-
# alongside their sibling modules
|
|
15
|
-
#
|
|
16
|
-
# - tests/hw/test_device.py needs a real synthio -- it only runs on
|
|
14
|
+
# alongside their sibling modules, not run from this tree.
|
|
15
|
+
# - tests/hw/test_device.py needs a real synthio: it only runs on
|
|
17
16
|
# actual hardware (see tests/README.md).
|
|
18
17
|
collect_ignore_glob = ["examples/*", "tests/hw/*"]
|
|
@@ -28,6 +28,18 @@ Subtractive Synth
|
|
|
28
28
|
.. automodule:: synthtools.subtractive_synth
|
|
29
29
|
:members:
|
|
30
30
|
|
|
31
|
+
FM Synth
|
|
32
|
+
========
|
|
33
|
+
|
|
34
|
+
.. automodule:: synthtools.fm_synth
|
|
35
|
+
:members:
|
|
36
|
+
|
|
37
|
+
Swarm Synth
|
|
38
|
+
===========
|
|
39
|
+
|
|
40
|
+
.. automodule:: synthtools.swarm_synth
|
|
41
|
+
:members:
|
|
42
|
+
|
|
31
43
|
Bassline Synth
|
|
32
44
|
==============
|
|
33
45
|
|
|
@@ -105,3 +117,15 @@ Utils
|
|
|
105
117
|
|
|
106
118
|
.. automodule:: synthtools.utils
|
|
107
119
|
:members:
|
|
120
|
+
|
|
121
|
+
Param Scaler
|
|
122
|
+
============
|
|
123
|
+
|
|
124
|
+
.. automodule:: synthtools.param_scaler
|
|
125
|
+
:members:
|
|
126
|
+
|
|
127
|
+
Gauge Cluster
|
|
128
|
+
=============
|
|
129
|
+
|
|
130
|
+
.. automodule:: synthtools.gauge_cluster
|
|
131
|
+
:members:
|
|
@@ -22,12 +22,21 @@ extensions = [
|
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
# synthtools/__init__.py now imports the engine (synth.py etc.), which pulls
|
|
25
|
-
# in synthio and ulab at import time
|
|
25
|
+
# in synthio and ulab at import time: neither exists off-device, so autodoc
|
|
26
26
|
# needs mocks or it fails to import the package at all. adafruit_wave is
|
|
27
27
|
# optional at runtime (synthtools/__init__.py already guards it with
|
|
28
28
|
# try/except ImportError) but still needs mocking here since the docs build
|
|
29
29
|
# has no real one installed either.
|
|
30
|
-
|
|
30
|
+
# displayio/vectorio come with synthtools.gauge_cluster; micropython (for
|
|
31
|
+
# const) with synthtools.param_scaler. None ships on desktop CPython.
|
|
32
|
+
autodoc_mock_imports = [
|
|
33
|
+
"synthio",
|
|
34
|
+
"ulab",
|
|
35
|
+
"adafruit_wave",
|
|
36
|
+
"displayio",
|
|
37
|
+
"vectorio",
|
|
38
|
+
"micropython",
|
|
39
|
+
]
|
|
31
40
|
|
|
32
41
|
autodoc_preserve_defaults = True
|
|
33
42
|
|
|
@@ -44,6 +44,15 @@ Show how to use Synth's pitch modulation features
|
|
|
44
44
|
:caption: examples/synthtools_pitch_demo.py
|
|
45
45
|
:linenos:
|
|
46
46
|
|
|
47
|
+
FM synth demo
|
|
48
|
+
-------------
|
|
49
|
+
|
|
50
|
+
A bright DX-style FM bell using FMSynth, with a live fm_ratio / fm_index sweep
|
|
51
|
+
|
|
52
|
+
.. literalinclude:: ../examples/synthtools_fm_demo.py
|
|
53
|
+
:caption: examples/synthtools_fm_demo.py
|
|
54
|
+
:linenos:
|
|
55
|
+
|
|
47
56
|
|
|
48
57
|
|
|
49
58
|
|
|
@@ -45,7 +45,7 @@ while True:
|
|
|
45
45
|
i += 1
|
|
46
46
|
|
|
47
47
|
# live filter sweep: one write into a shared block, O(1) in polyphony.
|
|
48
|
-
# deadband it
|
|
48
|
+
# deadband it: a jittery pot otherwise writes on every single frame.
|
|
49
49
|
sweep = (sweep + 7) % 100
|
|
50
50
|
new_f = 400 + 30 * sweep
|
|
51
51
|
if abs(new_f - synth.filt_f) > 5:
|
|
@@ -62,7 +62,7 @@ while True:
|
|
|
62
62
|
# synth.filt_vel = -1500 # hard playing CLOSES the filter
|
|
63
63
|
# synth.pitch_bend(0.05)
|
|
64
64
|
|
|
65
|
-
# NOTE none of the above touched `patch
|
|
65
|
+
# NOTE none of the above touched `patch`; knob turns are live state
|
|
66
66
|
# only. To keep them, snapshot first:
|
|
67
67
|
# synth.save_patch().save("/my_patch.json")
|
|
68
68
|
# and reloading the old patch is therefore a revert:
|
{circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_fenv_demo.py
RENAMED
|
@@ -20,16 +20,16 @@ patch = Patch(
|
|
|
20
20
|
detune=1.004,
|
|
21
21
|
# A filter has to exist before an envelope can sweep it. filt_f is the
|
|
22
22
|
# resting cutoff, i.e. the FLOOR the envelope rises from and falls back
|
|
23
|
-
# to
|
|
23
|
+
# to: keep it low and let the envelope supply the brightness.
|
|
24
24
|
filt_type="LPF",
|
|
25
25
|
filt_f=350,
|
|
26
26
|
filt_q=1.6,
|
|
27
27
|
# The envelope itself. fenv_amount is Hz ABOVE filt_f, and it defaults
|
|
28
|
-
# to 0, which switches the envelope off entirely
|
|
28
|
+
# to 0, which switches the envelope off entirely: it is the one field
|
|
29
29
|
# you cannot leave out.
|
|
30
30
|
fenv_amount=4000,
|
|
31
31
|
# 0.05s, not 0.02s. synthio updates blocks every 256 samples (~5.8ms at
|
|
32
|
-
# 44.1kHz), so a 0.02s attack is barely THREE steps
|
|
32
|
+
# 44.1kHz), so a 0.02s attack is barely THREE steps: you hear a click,
|
|
33
33
|
# not a sweep. Anything you want to hear as movement needs to span a few
|
|
34
34
|
# dozen updates.
|
|
35
35
|
fenv_attack=0.05,
|
|
@@ -40,11 +40,11 @@ patch = Patch(
|
|
|
40
40
|
filt_lfo_rate=0.4,
|
|
41
41
|
filt_lfo_amount=0,
|
|
42
42
|
# Velocity: both default to 0, meaning "velocity changes nothing".
|
|
43
|
-
# filt_vel is signed
|
|
43
|
+
# filt_vel is signed: negative means hard playing closes the filter.
|
|
44
44
|
filt_vel=0,
|
|
45
45
|
fenv_vel=0.0,
|
|
46
46
|
# The amp release must OUTLAST fenv_release, because the filter envelope
|
|
47
|
-
# only runs while the note is alive
|
|
47
|
+
# only runs while the note is alive: a short amp release silently cuts
|
|
48
48
|
# the sweep short. But not by much: a long amp tail piles voices on top
|
|
49
49
|
# of each other, and four overlapping sweeps at different points average
|
|
50
50
|
# out into a constant brightness. 0.22 against fenv_release 0.22 keeps
|
|
@@ -60,7 +60,7 @@ riff = (36, 36, 48, 36, 43, 36, 46, 36)
|
|
|
60
60
|
# any of this is audible:
|
|
61
61
|
# 1. AHR holds at PEAK for as long as the key is down, so the only movement
|
|
62
62
|
# during the loud part of a note is the attack. The note has to be long
|
|
63
|
-
# enough to contain it
|
|
63
|
+
# enough to contain it: a 0.10s note cannot show off a 0.25s attack, it
|
|
64
64
|
# just sounds duller because the sweep never finishes.
|
|
65
65
|
# 2. note lifetime / BEAT = how many voices overlap. At BEAT 0.16 with a 0.5s
|
|
66
66
|
# amp release that was 3.7 voices, each at a different point in its sweep,
|
|
@@ -85,40 +85,40 @@ def play(bars, label, velocity=110):
|
|
|
85
85
|
while True:
|
|
86
86
|
# 1. the envelope switched off: flat, dull, no movement at all
|
|
87
87
|
synth.fenv_amount = 0
|
|
88
|
-
play(1, "fenv_amount=0
|
|
88
|
+
play(1, "fenv_amount=0 ; envelope off, static filter")
|
|
89
89
|
|
|
90
90
|
# 2. switched on: the classic filter pluck
|
|
91
91
|
synth.fenv_amount = 4000
|
|
92
|
-
play(1, "fenv_amount=4000
|
|
92
|
+
play(1, "fenv_amount=4000 ; sweeps 350 -> 4350 Hz and back")
|
|
93
93
|
|
|
94
94
|
# 3. slower attack: the sweep becomes a swell rather than a pluck. This
|
|
95
|
-
# only reads because the key is held 0.28s
|
|
95
|
+
# only reads because the key is held 0.28s: longer than the attack.
|
|
96
96
|
# Ask for a 0.25s swell on a 0.10s note and you just get a duller note.
|
|
97
97
|
synth.fenv_attack = 0.25
|
|
98
|
-
play(1, "fenv_attack=0.25
|
|
98
|
+
play(1, "fenv_attack=0.25 : slow rise, a swell not a pluck")
|
|
99
99
|
synth.fenv_attack = 0.05
|
|
100
100
|
|
|
101
101
|
# 4. longer release: the tail rings on after the key is up. The amp
|
|
102
|
-
# release has to be raised WITH it
|
|
102
|
+
# release has to be raised WITH it: the filter envelope stops dead
|
|
103
103
|
# when the voice is freed, so a 0.45s sweep under a 0.22s amp release
|
|
104
104
|
# is silently truncated to 0.22s and sounds like nothing changed.
|
|
105
105
|
synth.release_time = 0.5
|
|
106
106
|
synth.fenv_release = 0.45
|
|
107
|
-
play(1, "fenv_release=0.45
|
|
107
|
+
play(1, "fenv_release=0.45: long tail (amp release raised to match)")
|
|
108
108
|
synth.fenv_release = 0.22
|
|
109
109
|
synth.release_time = 0.22
|
|
110
110
|
|
|
111
|
-
# 5. curve: 1 is a straight line up and down. 2 is the analog feel
|
|
111
|
+
# 5. curve: 1 is a straight line up and down. 2 is the analog feel:
|
|
112
112
|
# the rise snaps up and eases into the peak, and the release drops
|
|
113
113
|
# fast then tails off (15% of its height by halfway, vs 50% linear).
|
|
114
114
|
synth.fenv_curve = 2
|
|
115
|
-
play(1, "fenv_curve=2
|
|
115
|
+
play(1, "fenv_curve=2 (snappy rise, decaying tail on release")
|
|
116
116
|
synth.fenv_curve = 3
|
|
117
|
-
play(1, "fenv_curve=3
|
|
117
|
+
play(1, "fenv_curve=3 ) more so: nearly a pluck")
|
|
118
118
|
synth.fenv_curve = 1
|
|
119
119
|
|
|
120
120
|
# 6. the cyclic LFO: the fourth modulation on the same cutoff. It SUMS
|
|
121
|
-
# with the envelope, which is exactly why it needs its own moment
|
|
121
|
+
# with the envelope, which is exactly why it needs its own moment:
|
|
122
122
|
# with fenv_amount at 4000 the cutoff sits at 4-5kHz, and a 900Hz
|
|
123
123
|
# wobble up there is 0.39 of an octave on a 65Hz saw that has almost
|
|
124
124
|
# no energy left above 4kHz. Inaudible, despite being a big number.
|
|
@@ -126,26 +126,26 @@ while True:
|
|
|
126
126
|
# harmonics to remove.
|
|
127
127
|
#
|
|
128
128
|
# So: envelope off, and put the LFO somewhere it can be heard. The
|
|
129
|
-
# LFO is ADDITIVE
|
|
129
|
+
# LFO is ADDITIVE: filt_f is the floor and it opens 0..amount
|
|
130
130
|
# upward, it does not swing either side of filt_f. So to get a wide
|
|
131
131
|
# sweep, put the floor LOW and let the amount do the work.
|
|
132
132
|
# Note the rate too: 0.4Hz is a 2.5s cycle, slower than one note, so
|
|
133
133
|
# it drifts across the phrase instead of wobbling within a note.
|
|
134
134
|
synth.fenv_amount = 0
|
|
135
135
|
synth.filt_f = 120
|
|
136
|
-
synth.filt_lfo_amount = 1800 # 120..1920 Hz
|
|
136
|
+
synth.filt_lfo_amount = 1800 # 120..1920 Hz (4 octaves
|
|
137
137
|
synth.filt_lfo_rate = 3.0 # ~1.5 cycles per note
|
|
138
|
-
play(1, "filt_lfo 1800 @ 3Hz
|
|
138
|
+
play(1, "filt_lfo 1800 @ 3Hz) envelope off, LFO alone: 120..1920 Hz")
|
|
139
139
|
|
|
140
140
|
# ...and now both at once, with the envelope kept small enough that
|
|
141
141
|
# the LFO still reads on top of it. It reads less at the envelope's
|
|
142
|
-
# peak than between notes
|
|
142
|
+
# peak than between notes: that is the additive Hz bus being
|
|
143
143
|
# honest, not a bug: the same Hz depth is fewer octaves higher up.
|
|
144
144
|
synth.filt_f = 250
|
|
145
145
|
synth.fenv_amount = 200
|
|
146
146
|
synth.filt_lfo_amount = 1800
|
|
147
147
|
synth.filt_lfo_rate = 5.0
|
|
148
|
-
play(1, "filt_lfo + fenv
|
|
148
|
+
play(1, "filt_lfo + fenv : the two summing on one cutoff")
|
|
149
149
|
synth.filt_lfo_amount = 0
|
|
150
150
|
synth.filt_lfo_rate = 0.4
|
|
151
151
|
synth.filt_f = 350
|
|
@@ -155,14 +155,14 @@ while True:
|
|
|
155
155
|
# NEW notes (at 0 no per-voice node is built at all), but once a voice
|
|
156
156
|
# has one, the knob keeps reaching it.
|
|
157
157
|
synth.filt_vel = 2500
|
|
158
|
-
play(1, "filt_vel=2500
|
|
158
|
+
play(1, "filt_vel=2500 (soft notes darker (every 4th is soft)")
|
|
159
159
|
synth.filt_vel = -2500
|
|
160
|
-
play(1, "filt_vel=-2500
|
|
160
|
+
play(1, "filt_vel=-2500 ) inverted: hard notes darker")
|
|
161
161
|
synth.filt_vel = 0
|
|
162
162
|
|
|
163
163
|
# 8. velocity scales how far the envelope sweeps
|
|
164
164
|
synth.fenv_vel = 1.0
|
|
165
|
-
play(1, "fenv_vel=1.0
|
|
165
|
+
play(1, "fenv_vel=1.0 : soft notes sweep less far")
|
|
166
166
|
synth.fenv_vel = 0.0
|
|
167
167
|
|
|
168
168
|
# Nothing above was written to `patch`. To keep the current sound:
|
{circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_pad_demo.py
RENAMED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
# The third of the three: synthinst_fenv_demo.py is the filter as a plucky
|
|
12
12
|
# attack gesture, synthinst_pitch_demo.py is everything reaching note.bend,
|
|
13
|
-
# and this one is the filter moving SLOWLY under held chords
|
|
13
|
+
# and this one is the filter moving SLOWLY under held chords; where the
|
|
14
14
|
# envelope's shape and the filter LFO actually become audible as motion
|
|
15
15
|
# rather than as a transient.
|
|
16
16
|
#
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
# synth.max_polyphony is 24 on rp2040 (measured; the synthio stub docstring
|
|
19
19
|
# still claims 12 and is out of date). SubtractiveSynth spends TWO Notes
|
|
20
20
|
# per key whenever detune != 1.0, and releasing notes hold their slots
|
|
21
|
-
# until the amp envelope finishes
|
|
21
|
+
# until the amp envelope finishes, and with a 2.6s release, chords here
|
|
22
22
|
# DO overlap. So:
|
|
23
23
|
#
|
|
24
24
|
# 4-note chord, detune on -> 8 Notes, 16 while two chords overlap
|
|
25
25
|
# 5-note chord, detune on -> 10 Notes, 20 while two chords overlap
|
|
26
26
|
#
|
|
27
|
-
# Both fit. Detune stays on, which matters on a pad
|
|
27
|
+
# Both fit. Detune stays on, which matters on a pad: two slightly
|
|
28
28
|
# mistuned saws per key is most of what makes it sound wide. Go past
|
|
29
29
|
# 5-note chords and the arithmetic is worth redoing.
|
|
30
30
|
#
|
|
@@ -45,10 +45,10 @@ from synthtools import Patch, SubtractiveSynth
|
|
|
45
45
|
patch = Patch(
|
|
46
46
|
name="slowpad",
|
|
47
47
|
wave="SAW",
|
|
48
|
-
detune=1.004, # two Notes per key
|
|
48
|
+
detune=1.004, # two Notes per key: see the polyphony note above
|
|
49
49
|
filt_type="LPF",
|
|
50
50
|
# A low floor and real resonance. Resonance is what makes a slow sweep
|
|
51
|
-
# audible as MOVEMENT
|
|
51
|
+
# audible as MOVEMENT: at filt_q 0.7 a slow sweep just sounds like a
|
|
52
52
|
# tone control being turned, with no character to track.
|
|
53
53
|
filt_f=180,
|
|
54
54
|
filt_q=1.9,
|
|
@@ -59,7 +59,7 @@ patch = Patch(
|
|
|
59
59
|
filt_lfo_rate=0.5,
|
|
60
60
|
filt_lfo_amount=0,
|
|
61
61
|
# Pad amp envelope. The release (2.6) must OUTLAST fenv_release, or the
|
|
62
|
-
# voice is freed part-way down and the filter sweep is cut short
|
|
62
|
+
# voice is freed part-way down and the filter sweep is cut short:
|
|
63
63
|
# it sounds like a broken envelope but is just the note ending.
|
|
64
64
|
amp_env=[0.35, 0.3, 0.85, 2.6],
|
|
65
65
|
)
|
|
@@ -104,31 +104,31 @@ while True:
|
|
|
104
104
|
|
|
105
105
|
# 1. reference: filter parked, nothing moves
|
|
106
106
|
synth.fenv_amount = 0
|
|
107
|
-
chord("fenv_amount=0
|
|
107
|
+
chord("fenv_amount=0 ; static filter, dull and unmoving", secs=2)
|
|
108
108
|
|
|
109
109
|
# 2. the classic slow swell. Held 6s against a 2.5s attack, so you hear
|
|
110
110
|
# the sweep finish and then sit at the top for a while.
|
|
111
111
|
synth.fenv_amount = 5000
|
|
112
112
|
synth.fenv_attack = 2.5
|
|
113
|
-
chord("fenv_attack=2.5
|
|
113
|
+
chord("fenv_attack=2.5 : slow swell up to 5180 Hz, then holds")
|
|
114
114
|
|
|
115
115
|
# 3. slower still, and held longer to contain it
|
|
116
116
|
synth.fenv_attack = 5.0
|
|
117
|
-
chord("fenv_attack=5.0
|
|
117
|
+
chord("fenv_attack=5.0 : a very slow open, held 7s", secs=6.0)
|
|
118
118
|
synth.fenv_attack = 2.5
|
|
119
119
|
|
|
120
120
|
# 4. the release half. fenv_release 2.5 under an amp release of 2.6:
|
|
121
121
|
# the filter closes as the chord fades, which is most of what makes
|
|
122
122
|
# a pad sound like it is being played rather than switched off.
|
|
123
123
|
synth.fenv_release = 2.5
|
|
124
|
-
chord("fenv_release=2.5
|
|
124
|
+
chord("fenv_release=2.5 : listen past the key release, it closes down",
|
|
125
125
|
secs=4.0, tail=3.5)
|
|
126
126
|
|
|
127
127
|
# 5. curve. On a plucky note this is nearly inaudible; over 2.5s it is
|
|
128
128
|
# obvious. 1 is a straight line; 3 snaps open early then eases into
|
|
129
129
|
# the top, and drops away fast on release with a long tail.
|
|
130
130
|
synth.fenv_curve = 3
|
|
131
|
-
chord("fenv_curve=3
|
|
131
|
+
chord("fenv_curve=3 : same times, front-loaded: opens early",
|
|
132
132
|
secs=6.0, tail=3.5)
|
|
133
133
|
synth.fenv_curve = 1
|
|
134
134
|
|
|
@@ -143,15 +143,15 @@ while True:
|
|
|
143
143
|
synth.fenv_amount = 0
|
|
144
144
|
synth.filt_lfo_rate = 0.1
|
|
145
145
|
synth.filt_lfo_amount = 2000
|
|
146
|
-
chord("filt_lfo 2000 @ 0.1Hz
|
|
146
|
+
chord("filt_lfo 2000 @ 0.1Hz: one slow sweep up & back, no envelope", secs=12.0)
|
|
147
147
|
|
|
148
148
|
# 7. both at once. The envelope opens it on the attack, the LFO keeps
|
|
149
|
-
# it moving afterwards
|
|
149
|
+
# it moving afterwards: they SUM on one cutoff, so the envelope's
|
|
150
150
|
# 5000 raises the floor the LFO wobbles around.
|
|
151
151
|
synth.fenv_amount = 3000
|
|
152
152
|
synth.filt_lfo_amount = 2500
|
|
153
153
|
synth.filt_lfo_rate = 0.12
|
|
154
|
-
chord("fenv + slow LFO
|
|
154
|
+
chord("fenv + slow LFO : envelope opens it, LFO keeps it breathing",
|
|
155
155
|
secs=12.0)
|
|
156
156
|
synth.filt_lfo_amount = 0
|
|
157
157
|
synth.fenv_amount = 5000
|
|
@@ -164,14 +164,14 @@ while True:
|
|
|
164
164
|
# different brightness and they pull apart as the chord opens.
|
|
165
165
|
# A single global envelope could not do this.
|
|
166
166
|
synth.fenv_vel = 1.0
|
|
167
|
-
chord("fenv_vel=1.0
|
|
167
|
+
chord("fenv_vel=1.0 : one chord, four velocities, four sweeps",
|
|
168
168
|
vels=(40, 70, 100, 127), secs=8.0)
|
|
169
169
|
synth.fenv_vel = 0.0
|
|
170
170
|
|
|
171
171
|
# 9. The same point in time rather than in depth: notes entering 0.9s
|
|
172
172
|
# apart each start their OWN envelope from zero, so the chord opens
|
|
173
173
|
# as a staircase instead of all at once.
|
|
174
|
-
stagger("staggered entry
|
|
174
|
+
stagger("staggered entry : each voice sweeps on its own clock")
|
|
175
175
|
|
|
176
176
|
print()
|
|
177
177
|
print("=== a whole progression, everything on ===")
|