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.
Files changed (123) hide show
  1. {circuitpython_synthtools-0.5/circuitpython_synthtools.egg-info → circuitpython_synthtools-0.6.0}/PKG-INFO +28 -5
  2. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/README.rst +27 -4
  3. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0/circuitpython_synthtools.egg-info}/PKG-INFO +28 -5
  4. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/SOURCES.txt +14 -3
  5. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/conftest.py +4 -5
  6. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/api.rst +24 -0
  7. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/conf.py +11 -2
  8. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/examples.rst +9 -0
  9. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synth_setup.py +1 -1
  10. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_code1.py +2 -2
  11. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_fenv_demo.py +24 -24
  12. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_pad_demo.py +16 -16
  13. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_pitch_demo.py +26 -26
  14. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthinst_wave_demo.py +5 -5
  15. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_ahr_envelope_bend.py +11 -15
  16. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_bassline_demo.py +26 -27
  17. circuitpython_synthtools-0.6.0/examples/synthtools_fm_demo.py +64 -0
  18. circuitpython_synthtools-0.6.0/examples/synthtools_midi_demo.py +112 -0
  19. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/synthtools_simpletest.py +1 -1
  20. circuitpython_synthtools-0.6.0/examples/synthtools_swarm_demo.py +133 -0
  21. circuitpython_synthtools-0.6.0/examples/synthtools_wavetable_simple.py +29 -0
  22. circuitpython_synthtools-0.6.0/examples/ui/synthtools_gaugecluster_test.py +20 -0
  23. circuitpython_synthtools-0.6.0/examples/ui/synthtools_paramscaler_demo.py +205 -0
  24. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/pyproject.toml +1 -1
  25. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/ruff.toml +8 -8
  26. circuitpython_synthtools-0.6.0/synthtools/__init__.py +84 -0
  27. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/ahr_envelope.py +32 -45
  28. circuitpython_synthtools-0.6.0/synthtools/audio_fx.py +155 -0
  29. circuitpython_synthtools-0.6.0/synthtools/bassline_synth.py +696 -0
  30. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/blocks.py +4 -4
  31. circuitpython_synthtools-0.6.0/synthtools/fm_synth.py +150 -0
  32. {circuitpython_synthtools-0.5/synthtools/ui → circuitpython_synthtools-0.6.0/synthtools}/gauge_cluster.py +4 -4
  33. circuitpython_synthtools-0.6.0/synthtools/param_scaler.py +144 -0
  34. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/paramset.py +90 -30
  35. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/patch.py +26 -13
  36. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/subtractive_synth.py +9 -11
  37. circuitpython_synthtools-0.6.0/synthtools/swarm_synth.py +253 -0
  38. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/synth.py +211 -114
  39. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/utils.py +1 -1
  40. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/waves.py +132 -60
  41. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/wavetable.py +9 -10
  42. circuitpython_synthtools-0.6.0/synthtools/wavetable_synth.py +303 -0
  43. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/README.md +52 -15
  44. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/hw/run_on_device.py +2 -2
  45. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/hw/test_device.py +17 -6
  46. circuitpython_synthtools-0.6.0/tests/hw/test_display_cost.py +319 -0
  47. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/plot_env.py +3 -3
  48. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/run_tests.sh +9 -1
  49. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/synthio.py +2 -2
  50. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/ulab/__init__.py +1 -1
  51. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs/ulab/numpy.py +2 -2
  52. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs_fx/audiodelays.py +1 -1
  53. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/stubs_fx/audiofilters.py +1 -1
  54. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_arpeggiator.py +4 -4
  55. circuitpython_synthtools-0.6.0/tests/test_audio_fx.py +399 -0
  56. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_env_shapes.py +6 -6
  57. circuitpython_synthtools-0.6.0/tests/test_fm_synth.py +153 -0
  58. circuitpython_synthtools-0.6.0/tests/test_mono.py +494 -0
  59. circuitpython_synthtools-0.6.0/tests/test_param_scaler.py +154 -0
  60. circuitpython_synthtools-0.6.0/tests/test_paramset.py +167 -0
  61. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_patch.py +18 -5
  62. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_pytest_smoke.py +3 -3
  63. circuitpython_synthtools-0.6.0/tests/test_swarm_synth.py +260 -0
  64. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/tests/test_wiring.py +126 -28
  65. circuitpython_synthtools-0.5/examples/synthtools_wavetable_simple.py +0 -31
  66. circuitpython_synthtools-0.5/synthtools/__init__.py +0 -45
  67. circuitpython_synthtools-0.5/synthtools/audio_fx.py +0 -180
  68. circuitpython_synthtools-0.5/synthtools/bassline_synth.py +0 -410
  69. circuitpython_synthtools-0.5/synthtools/ui/param.py +0 -108
  70. circuitpython_synthtools-0.5/synthtools/ui/param_scaler.py +0 -83
  71. circuitpython_synthtools-0.5/synthtools/wavetable_synth.py +0 -80
  72. circuitpython_synthtools-0.5/tests/test_audio_fx.py +0 -189
  73. circuitpython_synthtools-0.5/tests/test_mono.py +0 -260
  74. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.gitattributes +0 -0
  75. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  76. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/build.yml +0 -0
  77. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/failure-help-text.yml +0 -0
  78. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/release_gh.yml +0 -0
  79. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.github/workflows/release_pypi.yml +0 -0
  80. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.gitignore +0 -0
  81. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.pre-commit-config.yaml +0 -0
  82. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.pylintrc +0 -0
  83. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/.readthedocs.yaml +0 -0
  84. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  85. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSE +0 -0
  86. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/CC-BY-4.0.txt +0 -0
  87. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/MIT.txt +0 -0
  88. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/LICENSES/Unlicense.txt +0 -0
  89. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/README.rst.license +0 -0
  90. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/dependency_links.txt +0 -0
  91. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/requires.txt +0 -0
  92. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/circuitpython_synthtools.egg-info/top_level.txt +0 -0
  93. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/custom.css +0 -0
  94. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/favicon.ico +0 -0
  95. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/_static/favicon.ico.license +0 -0
  96. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/api.rst.license +0 -0
  97. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/examples.rst.license +0 -0
  98. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/index.rst +0 -0
  99. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/index.rst.license +0 -0
  100. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/docs/requirements.txt +0 -0
  101. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/REUSE.toml +0 -0
  102. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/arpeggiator_demo.py +0 -0
  103. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/step_sequencer_bassdemo.py +0 -0
  104. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/step_sequencer_demo.py +0 -0
  105. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/trig_sequencer_demo.py +0 -0
  106. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavetables/BRAIDS02.WAV +0 -0
  107. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavetables/PLAITS02.WAV +0 -0
  108. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/00_909kick4.wav +0 -0
  109. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/01_909snare2.wav +0 -0
  110. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/02_909hatclosed2a.wav +0 -0
  111. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/03_909hatopen5.wav +0 -0
  112. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/04_909clap1.wav +0 -0
  113. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/05_909tommed.wav +0 -0
  114. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/06_909blip.wav +0 -0
  115. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/07_909cym2.wav +0 -0
  116. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/examples/wavs/kit0_909/readme.txt +0 -0
  117. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/optional_requirements.txt +0 -0
  118. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/requirements.txt +0 -0
  119. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/setup.cfg +0 -0
  120. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/arpeggiator.py +0 -0
  121. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/pitch_glider.py +0 -0
  122. {circuitpython_synthtools-0.5 → circuitpython_synthtools-0.6.0}/synthtools/step_sequencer.py +0 -0
  123. {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.5
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
- * ``EffectsChain`` -- post-synth audio effects: extra filter stages that
125
- track the synth's cutoff for a steeper slope, plus optional distortion
126
- and tempo-synced echo (needs ``audiofilters`` in the build)
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
- * ``EffectsChain`` -- post-synth audio effects: extra filter stages that
105
- track the synth's cutoff for a steeper slope, plus optional distortion
106
- and tempo-synced echo (needs ``audiofilters`` in the build)
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.5
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
- * ``EffectsChain`` -- post-synth audio effects: extra filter stages that
125
- track the synth's cutoff for a steeper slope, plus optional distortion
126
- and tempo-synced echo (needs ``audiofilters`` in the build)
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 -- see tests/README.md. The real tests
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 -- so without this file, pytest recursively discovers and
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 (see CLAUDE.md's examples note),
15
- # not run from this tree.
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 -- neither exists off-device, so autodoc
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
- autodoc_mock_imports = ["synthio", "ulab", "adafruit_wave"]
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
 
@@ -12,7 +12,7 @@ import board
12
12
  import keypad
13
13
  import synthio
14
14
 
15
- SAMPLE_RATE = 44100
15
+ SAMPLE_RATE = 22050
16
16
  CHANNEL_COUNT = 2
17
17
  BUFFER_SIZE = 2048
18
18
 
@@ -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 -- a jittery pot otherwise writes on every single frame.
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` -- knob turns are live state
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:
@@ -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 -- keep it low and let the envelope supply the brightness.
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 -- it is the one field
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 -- you hear a click,
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 -- negative means hard playing closes the filter.
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 -- a short amp release silently cuts
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 -- a 0.10s note cannot show off a 0.25s attack, 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 -- envelope off, static filter")
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 -- sweeps 350 -> 4350 Hz and back")
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 -- longer than the attack.
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 -- slow rise, a swell not a pluck")
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 -- the filter envelope stops dead
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 -- long tail (amp release raised to match)")
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 -- snappy rise, decaying tail on release")
115
+ play(1, "fenv_curve=2 (snappy rise, decaying tail on release")
116
116
  synth.fenv_curve = 3
117
- play(1, "fenv_curve=3 -- more so: nearly a pluck")
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 -- filt_f is the floor and it opens 0..amount
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 -- 4 octaves
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 -- envelope off, LFO alone: 120..1920 Hz")
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 -- that is the additive Hz bus being
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 -- the two summing on one cutoff")
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 -- soft notes darker (every 4th is soft)")
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 -- inverted: hard notes darker")
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 -- soft notes sweep less far")
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:
@@ -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 -- where the
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 -- and with a 2.6s release, chords here
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 -- two slightly
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 -- see the polyphony note above
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 -- at filt_q 0.7 a slow sweep just sounds like a
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 -- static filter, dull and unmoving", secs=2)
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 -- slow swell up to 5180 Hz, then holds")
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 -- a very slow open, held 7s", secs=6.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 -- listen past the key release, it closes down",
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 -- same times, front-loaded: opens early",
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 -- one slow sweep up & back, no envelope", secs=12.0)
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 -- they SUM on one cutoff, so the envelope's
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 -- envelope opens it, LFO keeps it breathing",
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 -- one chord, four velocities, four sweeps",
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 -- each voice sweeps on its own clock")
174
+ stagger("staggered entry : each voice sweeps on its own clock")
175
175
 
176
176
  print()
177
177
  print("=== a whole progression, everything on ===")