circuitpython-synthtools 0.5__tar.gz → 0.5.1__tar.gz

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