circuitpython-synthtools 0.5.1__tar.gz → 0.7.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.1/circuitpython_synthtools.egg-info → circuitpython_synthtools-0.7.0}/PKG-INFO +13 -1
  2. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/README.rst +12 -0
  3. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0/circuitpython_synthtools.egg-info}/PKG-INFO +13 -1
  4. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/circuitpython_synthtools.egg-info/SOURCES.txt +17 -3
  5. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/conftest.py +4 -5
  6. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/api.rst +30 -0
  7. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/conf.py +11 -2
  8. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/examples.rst +19 -0
  9. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synth_setup.py +1 -1
  10. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthinst_code1.py +2 -2
  11. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthinst_fenv_demo.py +24 -24
  12. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthinst_pad_demo.py +16 -16
  13. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthinst_pitch_demo.py +26 -26
  14. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthinst_wave_demo.py +5 -5
  15. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthtools_ahr_envelope_bend.py +11 -15
  16. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthtools_bassline_demo.py +17 -17
  17. circuitpython_synthtools-0.7.0/examples/synthtools_fm_demo.py +64 -0
  18. circuitpython_synthtools-0.7.0/examples/synthtools_harmony_demo.py +71 -0
  19. circuitpython_synthtools-0.7.0/examples/synthtools_midi_demo.py +112 -0
  20. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/synthtools_simpletest.py +1 -1
  21. circuitpython_synthtools-0.7.0/examples/synthtools_swarm_demo.py +133 -0
  22. circuitpython_synthtools-0.7.0/examples/synthtools_wavetable_simple.py +29 -0
  23. circuitpython_synthtools-0.7.0/examples/ui/synthtools_gaugecluster_test.py +20 -0
  24. circuitpython_synthtools-0.7.0/examples/ui/synthtools_paramscaler_demo.py +205 -0
  25. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/pyproject.toml +1 -1
  26. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/ruff.toml +8 -8
  27. circuitpython_synthtools-0.7.0/synthtools/__init__.py +90 -0
  28. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/ahr_envelope.py +32 -45
  29. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/audio_fx.py +30 -37
  30. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/bassline_synth.py +85 -100
  31. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/blocks.py +4 -4
  32. circuitpython_synthtools-0.7.0/synthtools/fm_synth.py +150 -0
  33. {circuitpython_synthtools-0.5.1/synthtools/ui → circuitpython_synthtools-0.7.0/synthtools}/gauge_cluster.py +4 -4
  34. circuitpython_synthtools-0.7.0/synthtools/harmony.py +198 -0
  35. circuitpython_synthtools-0.7.0/synthtools/param_scaler.py +144 -0
  36. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/paramset.py +90 -30
  37. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/patch.py +26 -13
  38. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/subtractive_synth.py +9 -11
  39. circuitpython_synthtools-0.7.0/synthtools/swarm_synth.py +253 -0
  40. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/synth.py +211 -114
  41. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/utils.py +1 -1
  42. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/waves.py +132 -60
  43. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/wavetable.py +9 -10
  44. circuitpython_synthtools-0.7.0/synthtools/wavetable_synth.py +303 -0
  45. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/README.md +52 -15
  46. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/hw/run_on_device.py +2 -2
  47. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/hw/test_device.py +17 -6
  48. circuitpython_synthtools-0.7.0/tests/hw/test_display_cost.py +319 -0
  49. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/plot_env.py +3 -3
  50. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/run_tests.sh +11 -1
  51. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/stubs/synthio.py +2 -2
  52. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/stubs/ulab/__init__.py +1 -1
  53. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/stubs/ulab/numpy.py +2 -2
  54. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/stubs_fx/audiodelays.py +1 -1
  55. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/stubs_fx/audiofilters.py +1 -1
  56. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_arpeggiator.py +4 -4
  57. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_audio_fx.py +19 -19
  58. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_env_shapes.py +6 -6
  59. circuitpython_synthtools-0.7.0/tests/test_fm_synth.py +153 -0
  60. circuitpython_synthtools-0.7.0/tests/test_harmony.py +139 -0
  61. circuitpython_synthtools-0.7.0/tests/test_mono.py +494 -0
  62. circuitpython_synthtools-0.7.0/tests/test_param_scaler.py +154 -0
  63. circuitpython_synthtools-0.7.0/tests/test_paramset.py +167 -0
  64. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_patch.py +18 -5
  65. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_pytest_smoke.py +3 -3
  66. circuitpython_synthtools-0.7.0/tests/test_swarm_synth.py +260 -0
  67. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/tests/test_wiring.py +126 -28
  68. circuitpython_synthtools-0.5.1/examples/synthtools_wavetable_simple.py +0 -31
  69. circuitpython_synthtools-0.5.1/synthtools/__init__.py +0 -45
  70. circuitpython_synthtools-0.5.1/synthtools/ui/param.py +0 -108
  71. circuitpython_synthtools-0.5.1/synthtools/ui/param_scaler.py +0 -83
  72. circuitpython_synthtools-0.5.1/synthtools/wavetable_synth.py +0 -80
  73. circuitpython_synthtools-0.5.1/tests/test_mono.py +0 -260
  74. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.gitattributes +0 -0
  75. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  76. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.github/workflows/build.yml +0 -0
  77. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.github/workflows/failure-help-text.yml +0 -0
  78. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.github/workflows/release_gh.yml +0 -0
  79. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.github/workflows/release_pypi.yml +0 -0
  80. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.gitignore +0 -0
  81. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.pre-commit-config.yaml +0 -0
  82. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.pylintrc +0 -0
  83. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/.readthedocs.yaml +0 -0
  84. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  85. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/LICENSE +0 -0
  86. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/LICENSES/CC-BY-4.0.txt +0 -0
  87. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/LICENSES/MIT.txt +0 -0
  88. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/LICENSES/Unlicense.txt +0 -0
  89. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/README.rst.license +0 -0
  90. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/circuitpython_synthtools.egg-info/dependency_links.txt +0 -0
  91. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/circuitpython_synthtools.egg-info/requires.txt +0 -0
  92. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/circuitpython_synthtools.egg-info/top_level.txt +0 -0
  93. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/_static/custom.css +0 -0
  94. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/_static/favicon.ico +0 -0
  95. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/_static/favicon.ico.license +0 -0
  96. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/api.rst.license +0 -0
  97. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/examples.rst.license +0 -0
  98. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/index.rst +0 -0
  99. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/index.rst.license +0 -0
  100. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/docs/requirements.txt +0 -0
  101. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/REUSE.toml +0 -0
  102. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/arpeggiator_demo.py +0 -0
  103. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/step_sequencer_bassdemo.py +0 -0
  104. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/step_sequencer_demo.py +0 -0
  105. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/trig_sequencer_demo.py +0 -0
  106. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavetables/BRAIDS02.WAV +0 -0
  107. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavetables/PLAITS02.WAV +0 -0
  108. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/00_909kick4.wav +0 -0
  109. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/01_909snare2.wav +0 -0
  110. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/02_909hatclosed2a.wav +0 -0
  111. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/03_909hatopen5.wav +0 -0
  112. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/04_909clap1.wav +0 -0
  113. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/05_909tommed.wav +0 -0
  114. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/06_909blip.wav +0 -0
  115. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/07_909cym2.wav +0 -0
  116. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/examples/wavs/kit0_909/readme.txt +0 -0
  117. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/optional_requirements.txt +0 -0
  118. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/requirements.txt +0 -0
  119. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/setup.cfg +0 -0
  120. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/arpeggiator.py +0 -0
  121. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/pitch_glider.py +0 -0
  122. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.0}/synthtools/step_sequencer.py +0 -0
  123. {circuitpython_synthtools-0.5.1 → circuitpython_synthtools-0.7.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.1
3
+ Version: 0.7.0
4
4
  Summary: CircuitPython helper library to do help doing synthio
5
5
  Author-email: Tod Kurt <tod@todbot.com>
6
6
  License: MIT
@@ -50,6 +50,7 @@ Concepts pulled from these projects and others:
50
50
 
51
51
  - `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
52
52
  - `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
53
+ - `synthiota <https://github.com/todbot/synthiota>`_
53
54
  - `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
54
55
  - `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
55
56
  - `picostepseq <https://github.com/todbot/picostepseq>`_
@@ -127,6 +128,12 @@ What's Included
127
128
  * ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
128
129
  with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
129
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.)
130
137
  * ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
131
138
  * ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
132
139
  oscillator, a decay-only filter sweep, per-step slide and accent. Can
@@ -148,10 +155,15 @@ What's Included
148
155
  sequencers with on/off callbacks
149
156
  * ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
150
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
151
161
  * ``Glider`` -- a standalone pitch-slide block for hand-built
152
162
  ``synthio.Note`` graphs (the engines above have their own portamento,
153
163
  via ``mono`` + ``glide_time``)
154
164
  * ``RollingAverage`` -- moving-average smoothing for noisy knob reads
165
+ * ``Scale`` / ``chord`` -- scales and diatonic/chromatic chords for
166
+ mapping pads and MIDI onto pitch
155
167
 
156
168
  Documentation
157
169
  =============
@@ -30,6 +30,7 @@ Concepts pulled from these projects and others:
30
30
 
31
31
  - `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
32
32
  - `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
33
+ - `synthiota <https://github.com/todbot/synthiota>`_
33
34
  - `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
34
35
  - `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
35
36
  - `picostepseq <https://github.com/todbot/picostepseq>`_
@@ -107,6 +108,12 @@ What's Included
107
108
  * ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
108
109
  with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
109
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.)
110
117
  * ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
111
118
  * ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
112
119
  oscillator, a decay-only filter sweep, per-step slide and accent. Can
@@ -128,10 +135,15 @@ What's Included
128
135
  sequencers with on/off callbacks
129
136
  * ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
130
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
131
141
  * ``Glider`` -- a standalone pitch-slide block for hand-built
132
142
  ``synthio.Note`` graphs (the engines above have their own portamento,
133
143
  via ``mono`` + ``glide_time``)
134
144
  * ``RollingAverage`` -- moving-average smoothing for noisy knob reads
145
+ * ``Scale`` / ``chord`` -- scales and diatonic/chromatic chords for
146
+ mapping pads and MIDI onto pitch
135
147
 
136
148
  Documentation
137
149
  =============
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-synthtools
3
- Version: 0.5.1
3
+ Version: 0.7.0
4
4
  Summary: CircuitPython helper library to do help doing synthio
5
5
  Author-email: Tod Kurt <tod@todbot.com>
6
6
  License: MIT
@@ -50,6 +50,7 @@ Concepts pulled from these projects and others:
50
50
 
51
51
  - `CircuitPython Synthio Tutorial <https://todbot.github.io/CircuitPython_Synthio_Tutorial/>`_
52
52
  - `circuitpython synthio tricks <https://github.com/todbot/circuitpython-synthio-tricks>`_
53
+ - `synthiota <https://github.com/todbot/synthiota>`_
53
54
  - `pico_test_synth <https://github.com/todbot/pico_test_synth>`_
54
55
  - `picotouch_synth <https://github.com/todbot/picotouch_synth>`_
55
56
  - `picostepseq <https://github.com/todbot/picostepseq>`_
@@ -127,6 +128,12 @@ What's Included
127
128
  * ``Synth`` -- synth engine base: shared voice, patch, and modulation handling,
128
129
  with ``mono`` mode for a single-voice synth with ``glide_time`` portamento
129
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.)
130
137
  * ``WavetableSynth`` -- wavetable-playback with adjustable wave_pos
131
138
  * ``BasslineSynth`` -- TB-303-style acid bassline: monophonic, one
132
139
  oscillator, a decay-only filter sweep, per-step slide and accent. Can
@@ -148,10 +155,15 @@ What's Included
148
155
  sequencers with on/off callbacks
149
156
  * ``Param`` / ``ParamSet`` -- knob-pickup and scaling for UIs with fewer
150
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
151
161
  * ``Glider`` -- a standalone pitch-slide block for hand-built
152
162
  ``synthio.Note`` graphs (the engines above have their own portamento,
153
163
  via ``mono`` + ``glide_time``)
154
164
  * ``RollingAverage`` -- moving-average smoothing for noisy knob reads
165
+ * ``Scale`` / ``chord`` -- scales and diatonic/chromatic chords for
166
+ mapping pads and MIDI onto pitch
155
167
 
156
168
  Documentation
157
169
  =============
@@ -48,9 +48,15 @@ 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_harmony_demo.py
53
+ examples/synthtools_midi_demo.py
51
54
  examples/synthtools_simpletest.py
55
+ examples/synthtools_swarm_demo.py
52
56
  examples/synthtools_wavetable_simple.py
53
57
  examples/trig_sequencer_demo.py
58
+ examples/ui/synthtools_gaugecluster_test.py
59
+ examples/ui/synthtools_paramscaler_demo.py
54
60
  examples/wavetables/BRAIDS02.WAV
55
61
  examples/wavetables/PLAITS02.WAV
56
62
  examples/wavs/kit0_909/00_909kick4.wav
@@ -68,32 +74,40 @@ synthtools/arpeggiator.py
68
74
  synthtools/audio_fx.py
69
75
  synthtools/bassline_synth.py
70
76
  synthtools/blocks.py
77
+ synthtools/fm_synth.py
78
+ synthtools/gauge_cluster.py
79
+ synthtools/harmony.py
80
+ synthtools/param_scaler.py
71
81
  synthtools/paramset.py
72
82
  synthtools/patch.py
73
83
  synthtools/pitch_glider.py
74
84
  synthtools/step_sequencer.py
75
85
  synthtools/subtractive_synth.py
86
+ synthtools/swarm_synth.py
76
87
  synthtools/synth.py
77
88
  synthtools/trig_sequencer.py
78
89
  synthtools/utils.py
79
90
  synthtools/waves.py
80
91
  synthtools/wavetable.py
81
92
  synthtools/wavetable_synth.py
82
- synthtools/ui/gauge_cluster.py
83
- synthtools/ui/param.py
84
- synthtools/ui/param_scaler.py
85
93
  tests/README.md
86
94
  tests/plot_env.py
87
95
  tests/run_tests.sh
88
96
  tests/test_arpeggiator.py
89
97
  tests/test_audio_fx.py
90
98
  tests/test_env_shapes.py
99
+ tests/test_fm_synth.py
100
+ tests/test_harmony.py
91
101
  tests/test_mono.py
102
+ tests/test_param_scaler.py
103
+ tests/test_paramset.py
92
104
  tests/test_patch.py
93
105
  tests/test_pytest_smoke.py
106
+ tests/test_swarm_synth.py
94
107
  tests/test_wiring.py
95
108
  tests/hw/run_on_device.py
96
109
  tests/hw/test_device.py
110
+ tests/hw/test_display_cost.py
97
111
  tests/stubs/synthio.py
98
112
  tests/stubs/ulab/__init__.py
99
113
  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
 
@@ -70,6 +82,12 @@ Waves
70
82
  .. automodule:: synthtools.waves
71
83
  :members:
72
84
 
85
+ Harmony
86
+ =======
87
+
88
+ .. automodule:: synthtools.harmony
89
+ :members:
90
+
73
91
  Arpeggiator
74
92
  ===========
75
93
 
@@ -105,3 +123,15 @@ Utils
105
123
 
106
124
  .. automodule:: synthtools.utils
107
125
  :members:
126
+
127
+ Param Scaler
128
+ ============
129
+
130
+ .. automodule:: synthtools.param_scaler
131
+ :members:
132
+
133
+ Gauge Cluster
134
+ =============
135
+
136
+ .. automodule:: synthtools.gauge_cluster
137
+ :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,25 @@ 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
+
56
+ Harmony demo
57
+ ------------
58
+
59
+ Walk scales and play diatonic chords with harmony.Scale, cycling through a
60
+ short chord progression
61
+
62
+ .. literalinclude:: ../examples/synthtools_harmony_demo.py
63
+ :caption: examples/synthtools_harmony_demo.py
64
+ :linenos:
65
+
47
66
 
48
67
 
49
68
 
@@ -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 ===")