circuitpython-synthtools 0.8.0__tar.gz → 0.9.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.

Potentially problematic release.


This version of circuitpython-synthtools might be problematic. Click here for more details.

Files changed (133) hide show
  1. {circuitpython_synthtools-0.8.0/circuitpython_synthtools.egg-info → circuitpython_synthtools-0.9.0}/PKG-INFO +4 -1
  2. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/README.rst +2 -0
  3. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0/circuitpython_synthtools.egg-info}/PKG-INFO +4 -1
  4. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/circuitpython_synthtools.egg-info/SOURCES.txt +2 -0
  5. circuitpython_synthtools-0.9.0/circuitpython_synthtools.egg-info/requires.txt +4 -0
  6. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/conf.py +1 -0
  7. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/pyproject.toml +1 -1
  8. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/requirements.txt +1 -0
  9. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/__init__.py +1 -1
  10. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/arpeggiator.py +12 -14
  11. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/bassline_synth.py +3 -2
  12. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/step_sequencer.py +44 -35
  13. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/trig_sequencer.py +12 -14
  14. circuitpython_synthtools-0.9.0/tests/lib/adafruit_ticks.py +141 -0
  15. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/run_tests.sh +2 -0
  16. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_arpeggiator.py +25 -3
  17. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_mono.py +14 -0
  18. circuitpython_synthtools-0.9.0/tests/test_step_sequencer.py +154 -0
  19. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_trig_sequencer.py +36 -4
  20. circuitpython_synthtools-0.8.0/circuitpython_synthtools.egg-info/requires.txt +0 -3
  21. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.gitattributes +0 -0
  22. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  23. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.github/workflows/build.yml +0 -0
  24. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.github/workflows/failure-help-text.yml +0 -0
  25. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.github/workflows/release_gh.yml +0 -0
  26. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.github/workflows/release_pypi.yml +0 -0
  27. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.gitignore +0 -0
  28. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.pre-commit-config.yaml +0 -0
  29. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.pylintrc +0 -0
  30. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/.readthedocs.yaml +0 -0
  31. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/CODE_OF_CONDUCT.md +0 -0
  32. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/LICENSE +0 -0
  33. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/LICENSES/CC-BY-4.0.txt +0 -0
  34. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/LICENSES/MIT.txt +0 -0
  35. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/LICENSES/Unlicense.txt +0 -0
  36. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/README.rst.license +0 -0
  37. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/circuitpython_synthtools.egg-info/dependency_links.txt +0 -0
  38. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/circuitpython_synthtools.egg-info/top_level.txt +0 -0
  39. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/conftest.py +0 -0
  40. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/_static/custom.css +0 -0
  41. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/_static/favicon.ico +0 -0
  42. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/_static/favicon.ico.license +0 -0
  43. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/api.rst +0 -0
  44. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/api.rst.license +0 -0
  45. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/examples.rst +0 -0
  46. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/examples.rst.license +0 -0
  47. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/index.rst +0 -0
  48. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/index.rst.license +0 -0
  49. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/docs/requirements.txt +0 -0
  50. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/REUSE.toml +0 -0
  51. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/arpeggiator_demo.py +0 -0
  52. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synth_setup.py +0 -0
  53. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthinst_code1.py +0 -0
  54. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthinst_fenv_demo.py +0 -0
  55. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthinst_pad_demo.py +0 -0
  56. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthinst_pitch_demo.py +0 -0
  57. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthinst_wave_demo.py +0 -0
  58. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_ahr_envelope_bend.py +0 -0
  59. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_bassline_accent_demo.py +0 -0
  60. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_bassline_filter_demo.py +0 -0
  61. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_bassline_fx_demo.py +0 -0
  62. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_fm_demo.py +0 -0
  63. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_harmony_demo.py +0 -0
  64. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_midi_demo.py +0 -0
  65. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_simpletest.py +0 -0
  66. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_stepsequencer_demo.py +0 -0
  67. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_swarm_demo.py +0 -0
  68. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_wavetable_chords.py +0 -0
  69. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/synthtools_wavetable_simple.py +0 -0
  70. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/trig_sequencer_demo.py +0 -0
  71. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/ui/synthtools_gaugecluster_test.py +0 -0
  72. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/ui/synthtools_paramscaler_demo.py +0 -0
  73. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavetables/BRAIDS02.WAV +0 -0
  74. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavetables/PLAITS02.WAV +0 -0
  75. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/00_909kick4.wav +0 -0
  76. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/01_909snare2.wav +0 -0
  77. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/02_909hatclosed2a.wav +0 -0
  78. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/03_909hatopen5.wav +0 -0
  79. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/04_909clap1.wav +0 -0
  80. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/05_909tommed.wav +0 -0
  81. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/06_909blip.wav +0 -0
  82. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/07_909cym2.wav +0 -0
  83. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/examples/wavs/kit0_909/readme.txt +0 -0
  84. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/optional_requirements.txt +0 -0
  85. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/ruff.toml +0 -0
  86. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/setup.cfg +0 -0
  87. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/ahr_envelope.py +0 -0
  88. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/audio_fx.py +0 -0
  89. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/blocks.py +0 -0
  90. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/fm_synth.py +0 -0
  91. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/gauge_cluster.py +0 -0
  92. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/harmony.py +0 -0
  93. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/param_scaler.py +0 -0
  94. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/paramset.py +0 -0
  95. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/patch.py +0 -0
  96. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/pitch_glider.py +0 -0
  97. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/subtractive_synth.py +0 -0
  98. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/swarm_synth.py +0 -0
  99. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/synth.py +0 -0
  100. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/utils.py +0 -0
  101. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/waves.py +0 -0
  102. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/wavetable.py +0 -0
  103. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/synthtools/wavetable_synth.py +0 -0
  104. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/README.md +0 -0
  105. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/hw/run_on_device.py +0 -0
  106. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/hw/test_device.py +0 -0
  107. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/hw/test_display_cost.py +0 -0
  108. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/hw/test_trig_sequencer_timing.py +0 -0
  109. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/hw/test_wavetable_cost.py +0 -0
  110. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/plot_env.py +0 -0
  111. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/FINDINGS.md +0 -0
  112. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/README.md +0 -0
  113. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/analyze_renders.py +0 -0
  114. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/check_wavetable.py +0 -0
  115. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/check_wavetable_synth.py +0 -0
  116. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/render_chords.py +0 -0
  117. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/run.sh +0 -0
  118. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/render/wavhdr.py +0 -0
  119. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/stubs/synthio.py +0 -0
  120. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/stubs/ulab/__init__.py +0 -0
  121. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/stubs/ulab/numpy.py +0 -0
  122. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/stubs_fx/audiodelays.py +0 -0
  123. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/stubs_fx/audiofilters.py +0 -0
  124. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_audio_fx.py +0 -0
  125. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_env_shapes.py +0 -0
  126. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_fm_synth.py +0 -0
  127. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_harmony.py +0 -0
  128. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_param_scaler.py +0 -0
  129. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_paramset.py +0 -0
  130. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_patch.py +0 -0
  131. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_pytest_smoke.py +0 -0
  132. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_swarm_synth.py +0 -0
  133. {circuitpython_synthtools-0.8.0 → circuitpython_synthtools-0.9.0}/tests/test_wiring.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-synthtools
3
- Version: 0.8.0
3
+ Version: 0.9.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
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3
15
15
  Description-Content-Type: text/x-rst
16
16
  License-File: LICENSE
17
17
  Requires-Dist: Adafruit-Blinka
18
+ Requires-Dist: adafruit-circuitpython-ticks
18
19
  Provides-Extra: optional
19
20
  Dynamic: license-file
20
21
 
@@ -60,6 +61,8 @@ Dependencies
60
61
  This driver depends on:
61
62
 
62
63
  * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
64
+ * `Adafruit CircuitPython Ticks <https://github.com/adafruit/Adafruit_CircuitPython_Ticks>`_
65
+ (``StepSequencer``)
63
66
 
64
67
  Please ensure all dependencies are available on the CircuitPython filesystem.
65
68
  This is easily achieved by downloading
@@ -40,6 +40,8 @@ Dependencies
40
40
  This driver depends on:
41
41
 
42
42
  * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
43
+ * `Adafruit CircuitPython Ticks <https://github.com/adafruit/Adafruit_CircuitPython_Ticks>`_
44
+ (``StepSequencer``)
43
45
 
44
46
  Please ensure all dependencies are available on the CircuitPython filesystem.
45
47
  This is easily achieved by downloading
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-synthtools
3
- Version: 0.8.0
3
+ Version: 0.9.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
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3
15
15
  Description-Content-Type: text/x-rst
16
16
  License-File: LICENSE
17
17
  Requires-Dist: Adafruit-Blinka
18
+ Requires-Dist: adafruit-circuitpython-ticks
18
19
  Provides-Extra: optional
19
20
  Dynamic: license-file
20
21
 
@@ -60,6 +61,8 @@ Dependencies
60
61
  This driver depends on:
61
62
 
62
63
  * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
64
+ * `Adafruit CircuitPython Ticks <https://github.com/adafruit/Adafruit_CircuitPython_Ticks>`_
65
+ (``StepSequencer``)
63
66
 
64
67
  Please ensure all dependencies are available on the CircuitPython filesystem.
65
68
  This is easily achieved by downloading
@@ -105,6 +105,7 @@ tests/test_param_scaler.py
105
105
  tests/test_paramset.py
106
106
  tests/test_patch.py
107
107
  tests/test_pytest_smoke.py
108
+ tests/test_step_sequencer.py
108
109
  tests/test_swarm_synth.py
109
110
  tests/test_trig_sequencer.py
110
111
  tests/test_wiring.py
@@ -113,6 +114,7 @@ tests/hw/test_device.py
113
114
  tests/hw/test_display_cost.py
114
115
  tests/hw/test_trig_sequencer_timing.py
115
116
  tests/hw/test_wavetable_cost.py
117
+ tests/lib/adafruit_ticks.py
116
118
  tests/render/FINDINGS.md
117
119
  tests/render/README.md
118
120
  tests/render/analyze_renders.py
@@ -0,0 +1,4 @@
1
+ Adafruit-Blinka
2
+ adafruit-circuitpython-ticks
3
+
4
+ [optional]
@@ -33,6 +33,7 @@ autodoc_mock_imports = [
33
33
  "synthio",
34
34
  "ulab",
35
35
  "adafruit_wave",
36
+ "adafruit_ticks",
36
37
  "displayio",
37
38
  "vectorio",
38
39
  "micropython",
@@ -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.8.0"
16
+ version = "0.9.0"
17
17
  readme = "README.rst"
18
18
  authors = [
19
19
  {name = "Tod Kurt", email = "tod@todbot.com"}
@@ -4,3 +4,4 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  Adafruit-Blinka
7
+ adafruit-circuitpython-ticks
@@ -36,7 +36,7 @@ Anything not listed below is still importable by its module path::
36
36
 
37
37
  import sys
38
38
 
39
- __version__ = "0.8.0"
39
+ __version__ = "0.9.0"
40
40
  __repo__ = "https://github.com/todbot/CircuitPython_SynthTools.git"
41
41
 
42
42
  #: Public name -> the submodule that defines it. Every entry is loaded on
@@ -11,16 +11,7 @@ Part of synthtools.
11
11
 
12
12
  """
13
13
 
14
- import time
15
-
16
- try:
17
- from supervisor import ticks_ms
18
- except ImportError:
19
-
20
- def ticks_ms():
21
- """stand-in for supervisor.ticks_ms"""
22
- return time.monotonic_ns() // 1_000_000
23
-
14
+ from adafruit_ticks import ticks_add, ticks_diff, ticks_ms
24
15
 
25
16
  patterns = (
26
17
  (0, 4, 7, 12),
@@ -68,6 +59,7 @@ class Arpeggiator:
68
59
  self.held_note = None
69
60
  self.next_millis = 0
70
61
  self.held_millis = 0
62
+ self._frac_millis = 0.0 # step_millis's fraction, carried between steps
71
63
 
72
64
  def set_bpm(self, bpm, rate=None):
73
65
  """Set BPM and optionally rate"""
@@ -90,6 +82,7 @@ class Arpeggiator:
90
82
  def start(self):
91
83
  """Start the arpeggiator running"""
92
84
  self.next_millis = ticks_ms()
85
+ self._frac_millis = 0.0
93
86
  self.on = True
94
87
 
95
88
  def stop(self):
@@ -105,12 +98,12 @@ class Arpeggiator:
105
98
  now = ticks_ms()
106
99
 
107
100
  # trigger note-off after gate time
108
- if self.held_note and now - self.held_millis > 0:
101
+ if self.held_note and ticks_diff(now, self.held_millis) > 0:
109
102
  self.off_func(self.held_note)
110
103
  self.held_note = None
111
104
 
112
105
  # trigger note-on on if time to do so
113
- delta_millis = now - self.next_millis
106
+ delta_millis = ticks_diff(now, self.next_millis)
114
107
  if delta_millis >= 0 and len(self.notes) > 0: # time for new note
115
108
  note = self.notes[self.i] + self.oct_distance * self.octave
116
109
  # print("\t\t\t\t\t", "delta:",delta_millis)
@@ -120,8 +113,13 @@ class Arpeggiator:
120
113
  self.held_note = note # save for note-off
121
114
 
122
115
  # set up when note off and next note happens
123
- self.held_millis = now + (self.step_millis * self.gate)
124
- self.next_millis = now + self.step_millis - delta_millis // 2
116
+ # int ticks: they wrap at 2**29, first ~65 s after power-on, and a
117
+ # 30-bit float can't hold one that big to the ms
118
+ self._frac_millis += self.step_millis
119
+ step = int(self._frac_millis)
120
+ self._frac_millis -= step
121
+ self.held_millis = ticks_add(now, int(self.step_millis * self.gate))
122
+ self.next_millis = ticks_add(now, step - delta_millis // 2)
125
123
 
126
124
  # go to next note
127
125
  self.i = (self.i + 1) % len(self.notes)
@@ -306,8 +306,8 @@ class BasslineSynth(Synth):
306
306
  self._accent_sweep_decay = getattr(p, "accent_sweep_decay", 0.55)
307
307
  self._accent_sweep_max = getattr(p, "accent_sweep_max", 1.6)
308
308
  self._slide_time = getattr(p, "slide_time", 0.06)
309
- self._decay = p.fenv_attack # super() already wrote it into the block
310
- self._fall = p.fenv_attack # ...and that is what the block holds
309
+ self._decay = getattr(p, "decay", p.fenv_attack)
310
+ self._fall = p.fenv_attack # what super() wrote into the block
311
311
  get_wave(self._wave_name) # warm the cache; note-on only reads it
312
312
  self._accent_on = False
313
313
  self._accent_sweep = 0.0
@@ -368,6 +368,7 @@ class BasslineSynth(Synth):
368
368
  peak, rest = self._env_endpoints() # un-accented: no sweep widening
369
369
  p.fenv_amount = rest - peak
370
370
  p.fenv_attack = self._decay
371
+ p.decay = self._decay
371
372
  p.fx_filter_stages = self._fx_filter_stages
372
373
  p.fx_filter_mix = self._fx_filter_mix
373
374
  p.fx_hpf_f = self._fx_hpf_f
@@ -11,15 +11,7 @@ Part of synthtools.
11
11
 
12
12
  """
13
13
 
14
- import time
15
-
16
- try:
17
- from supervisor import ticks_ms
18
- except ImportError:
19
-
20
- def ticks_ms():
21
- """stand-in for supervisor.ticks_ms"""
22
- return time.monotonic_ns() // 1_000_000
14
+ from adafruit_ticks import ticks_add, ticks_diff, ticks_ms
23
15
 
24
16
 
25
17
  class StepSequencer:
@@ -46,8 +38,10 @@ class StepSequencer:
46
38
  self.held_note = None # the current note playing
47
39
  self.transpose = 0
48
40
  self.playing = False # is sequence running or not (but use .start()/.stop())
41
+ self.external_clock = False # owner calls trigger(), update() only releases gates
42
+ self.catch_up_steps = 0 # how far behind to still play every step, late, not resync
49
43
  self.next_millis = 0
50
- self.error_millis = 0
44
+ self._frac_millis = 0.0 # step_millis's fraction, carried between steps
51
45
 
52
46
  @property
53
47
  def bpm(self):
@@ -68,46 +62,61 @@ class StepSequencer:
68
62
  def start(self):
69
63
  """Start sequencer going"""
70
64
  self.next_millis = ticks_ms()
65
+ self._frac_millis = 0.0
71
66
  self.playing = True
72
- self.error_millis = 0
73
67
 
74
- def stop(self):
75
- """Stop sequencer, turning off any currently-sounding note"""
68
+ def pause(self):
69
+ """Pause sequencer where it is, turning off any currently-sounding note"""
76
70
  if self.held_note:
77
71
  self.off_func(*self.held_note)
72
+ self.held_note = None
78
73
  self.playing = False
74
+
75
+ def stop(self):
76
+ """Stop sequencer and rewind, turning off any currently-sounding note"""
77
+ self.pause()
79
78
  self.i = 0
80
79
 
80
+ def trigger(self, now=None):
81
+ """Play the current step and advance, for an owner keeping its own clock"""
82
+ if now is None:
83
+ now = ticks_ms()
84
+ if self.i >= self.step_count: # step_count shrank under us
85
+ self.i = 0
86
+ (note, vel, gate, on) = self.steps[self.i] # get new note to play
87
+ note += self.transpose # adjust for transpose
88
+ self.held_note = (note, vel, gate, on) # save it for when we note_off it
89
+
90
+ # trigger new note
91
+ self.on_func(*self.held_note) # held_note = (note,vel,gate,on)
92
+
93
+ # prep for next step in sequence
94
+ self.i = (self.i + 1) % self.step_count
95
+
96
+ # int ticks: they wrap at 2**29, first ~65 s after power-on, and a
97
+ # 30-bit float can't hold one that big to the ms
98
+ self._frac_millis += self.step_millis
99
+ step = int(self._frac_millis)
100
+ self._frac_millis -= step
101
+ self.next_millis = ticks_add(self.next_millis, step)
102
+ # a stall put us too far behind
103
+ if ticks_diff(self.next_millis, now) < -self.catch_up_steps * step:
104
+ self.next_millis = ticks_add(now, step)
105
+ # next note off is some percentage smaller
106
+ gate = int(self.step_millis * self.held_note[2])
107
+ self.gate_off_millis = ticks_add(now, gate)
108
+
81
109
  def update(self):
82
110
  """Update the sequencer. Call as frequently as possible"""
83
111
  if not self.playing:
84
112
  return
85
113
 
86
114
  now = ticks_ms()
87
- delta_millis = now - self.next_millis
88
115
 
89
116
  # trigger note-off after gate time
90
- if now - self.gate_off_millis >= 0 and self.held_note:
117
+ if self.held_note and ticks_diff(now, self.gate_off_millis) >= 0:
91
118
  self.off_func(*self.held_note)
92
119
  self.held_note = None
93
120
 
94
- if delta_millis >= 0: # if zero or great, time for next step
95
- # print(" delta_millis:", delta_millis, self.error_millis)
96
- self.error_millis += delta_millis
97
- (note, vel, gate, on) = self.steps[self.i] # get new note to play
98
- note += self.transpose # adjust for transpose
99
- self.held_note = (note, vel, gate, on) # save it for when we note_off it
100
-
101
- # trigger new note
102
- self.on_func(*self.held_note) # held_note = (note,vel,gate,on)
103
-
104
- # prep for next step in sequence
105
- self.i = (self.i + 1) % self.step_count
106
-
107
- # set up when next note on is, with some error correction
108
- self.next_millis = now + self.step_millis
109
- if self.error_millis > 1:
110
- self.next_millis -= self.error_millis
111
- self.error_millis = 0
112
- # next note off is some percentage smaller
113
- self.gate_off_millis = now + self.step_millis * self.held_note[2]
121
+ if not self.external_clock and ticks_diff(now, self.next_millis) >= 0:
122
+ self.trigger(now)
@@ -11,15 +11,7 @@ Part of synthtools.
11
11
 
12
12
  """
13
13
 
14
- import time
15
-
16
- try:
17
- from supervisor import ticks_ms
18
- except ImportError:
19
-
20
- def ticks_ms():
21
- """stand-in for supervisor.ticks_ms"""
22
- return time.monotonic_ns() // 1_000_000
14
+ from adafruit_ticks import ticks_add, ticks_diff, ticks_ms
23
15
 
24
16
 
25
17
  class TrigSequencer:
@@ -45,6 +37,7 @@ class TrigSequencer:
45
37
  self.drum_map = [0] * trig_count
46
38
  self.step_millis = 0 # real value once bpm is set; update()/start() need it to exist
47
39
  self.next_millis = 0
40
+ self._frac_millis = 0.0 # step_millis's fraction, carried between steps
48
41
 
49
42
  @property
50
43
  def bpm(self):
@@ -58,6 +51,7 @@ class TrigSequencer:
58
51
  def start(self):
59
52
  """Start sequencer going"""
60
53
  self.next_millis = ticks_ms()
54
+ self._frac_millis = 0.0
61
55
  self.playing = True
62
56
 
63
57
  def stop(self):
@@ -78,15 +72,19 @@ class TrigSequencer:
78
72
  return
79
73
 
80
74
  now = ticks_ms()
81
- delta_millis = now - self.next_millis
82
75
 
83
- if delta_millis >= 0: # time to play
76
+ if ticks_diff(now, self.next_millis) >= 0: # time to play
84
77
  for t in range(self.trig_count):
85
78
  if self.trigs[t][self.i] == 1:
86
79
  self.on_func(t, self.drum_map[t])
87
80
 
88
81
  # prep for next step in sequence
89
82
  self.i = (self.i + 1) % self.step_count
90
- self.next_millis += self.step_millis
91
- if self.next_millis < now: # a stall put us a whole step behind: resync
92
- self.next_millis = now + self.step_millis
83
+ # int ticks: they wrap at 2**29, first ~65 s after power-on, and a
84
+ # 30-bit float can't hold one that big to the ms
85
+ self._frac_millis += self.step_millis
86
+ step = int(self._frac_millis)
87
+ self._frac_millis -= step
88
+ self.next_millis = ticks_add(self.next_millis, step)
89
+ if ticks_diff(self.next_millis, now) < 0: # a stall put us a whole step behind
90
+ self.next_millis = ticks_add(now, step)
@@ -0,0 +1,141 @@
1
+ # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
2
+ # SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
3
+ #
4
+ # SPDX-License-Identifier: MIT
5
+ """
6
+ `adafruit_ticks`
7
+ ================================================================================
8
+
9
+ Work with intervals and deadlines in milliseconds
10
+
11
+
12
+ * Author(s): Jeff Epler
13
+
14
+ Implementation Notes
15
+ --------------------
16
+
17
+ **Software and Dependencies:**
18
+
19
+ * Adafruit CircuitPython firmware for the supported boards:
20
+ https://github.com/adafruit/circuitpython/releases
21
+
22
+ """
23
+
24
+ # imports
25
+ from micropython import const
26
+
27
+ __version__ = "1.1.7"
28
+ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ticks.git"
29
+
30
+ _TICKS_PERIOD = const(1 << 29)
31
+ _TICKS_MAX = const(_TICKS_PERIOD - 1)
32
+ _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2)
33
+
34
+ # Get the correct implementation of ticks_ms. There are three possibilities:
35
+ #
36
+ # - supervisor.ticks_ms is present. This will be the case starting in CP7.0
37
+ #
38
+ # - time.ticks_ms is present. This is the case for MicroPython & for the "unix
39
+ # port" of CircuitPython, used for some automated testing.
40
+ #
41
+ # - time.monotonic_ns is present, and works. This is the case on most
42
+ # Express boards in CP6.x, and most host computer versions of Python.
43
+ #
44
+ # - Otherwise, time.monotonic is assumed to be present. This is the case
45
+ # on most non-express boards in CP6.x, and some old host computer versions
46
+ # of Python.
47
+ #
48
+ # Note that on microcontrollers, this time source becomes increasingly
49
+ # inaccurate when the board has not been reset in a long time, losing the
50
+ # ability to measure 1ms intervals after about 1 hour, and losing the
51
+ # ability to meausre 128ms intervals after 6 days. The only solution is to
52
+ # either upgrade to a version with supervisor.ticks_ms, or to switch to a
53
+ # board with time.monotonic_ns.
54
+
55
+ try:
56
+ from supervisor import ticks_ms
57
+ except (ImportError, NameError):
58
+ import time
59
+
60
+ if _ticks_ms := getattr(time, "ticks_ms", None):
61
+
62
+ def ticks_ms() -> int:
63
+ """Return the time in milliseconds since an unspecified moment,
64
+ wrapping after 2**29ms.
65
+
66
+ The wrap value was chosen so that it is always possible to add or
67
+ subtract two `ticks_ms` values without overflow on a board without
68
+ long ints (or without allocating any long integer objects, on
69
+ boards with long ints).
70
+
71
+ This ticks value comes from a low-accuracy clock internal to the
72
+ microcontroller, just like `time.monotonic`. Due to its low
73
+ accuracy and the fact that it "wraps around" every few days, it is
74
+ intended for working with short term events like advancing an LED
75
+ animation, not for long term events like counting down the time
76
+ until a holiday."""
77
+ return _ticks_ms() & _TICKS_MAX
78
+
79
+ else:
80
+ try:
81
+ from time import monotonic_ns as _monotonic_ns
82
+
83
+ _monotonic_ns() # Check that monotonic_ns is usable
84
+
85
+ def ticks_ms() -> int:
86
+ """Return the time in milliseconds since an unspecified moment,
87
+ wrapping after 2**29ms.
88
+
89
+ The wrap value was chosen so that it is always possible to add or
90
+ subtract two `ticks_ms` values without overflow on a board without
91
+ long ints (or without allocating any long integer objects, on
92
+ boards with long ints).
93
+
94
+ This ticks value comes from a low-accuracy clock internal to the
95
+ microcontroller, just like `time.monotonic`. Due to its low
96
+ accuracy and the fact that it "wraps around" every few days, it is
97
+ intended for working with short term events like advancing an LED
98
+ animation, not for long term events like counting down the time
99
+ until a holiday."""
100
+ return (_monotonic_ns() // 1_000_000) & _TICKS_MAX
101
+
102
+ except (ImportError, NameError, NotImplementedError):
103
+ from time import monotonic as _monotonic
104
+
105
+ def ticks_ms() -> int:
106
+ """Return the time in milliseconds since an unspecified moment,
107
+ wrapping after 2**29ms.
108
+
109
+ The wrap value was chosen so that it is always possible to add or
110
+ subtract two `ticks_ms` values without overflow on a board without
111
+ long ints (or without allocating any long integer objects, on
112
+ boards with long ints).
113
+
114
+ This ticks value comes from a low-accuracy clock internal to the
115
+ microcontroller, just like `time.monotonic`. Due to its low
116
+ accuracy and the fact that it "wraps around" every few days, it is
117
+ intended for working with short term events like advancing an LED
118
+ animation, not for long term events like counting down the time
119
+ until a holiday."""
120
+ return int(_monotonic() * 1000) & _TICKS_MAX
121
+
122
+
123
+ def ticks_add(ticks: int, delta: int) -> int:
124
+ "Add a delta to a base number of ticks, performing wraparound at 2**29ms."
125
+ if -_TICKS_HALFPERIOD < delta < _TICKS_HALFPERIOD:
126
+ return (ticks + delta) % _TICKS_PERIOD
127
+ raise OverflowError("ticks interval overflow")
128
+
129
+
130
+ def ticks_diff(ticks1: int, ticks2: int) -> int:
131
+ """Compute the signed difference between two ticks values,
132
+ assuming that they are within 2**28 ticks"""
133
+ diff = (ticks1 - ticks2) & _TICKS_MAX
134
+ diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
135
+ return diff
136
+
137
+
138
+ def ticks_less(ticks1: int, ticks2: int) -> bool:
139
+ """Return true if ticks1 is before ticks2 and false otherwise,
140
+ assuming that they are within 2**28 ticks"""
141
+ return ticks_diff(ticks1, ticks2) < 0
@@ -40,6 +40,7 @@ if command -v python3 >/dev/null 2>&1; then
40
40
  run python3 tests/test_audio_fx.py
41
41
  run python3 tests/test_arpeggiator.py
42
42
  run python3 tests/test_trig_sequencer.py
43
+ run python3 tests/test_step_sequencer.py
43
44
  run python3 tests/test_paramset.py
44
45
  run python3 tests/test_param_scaler.py
45
46
  run python3 tests/test_harmony.py
@@ -58,6 +59,7 @@ if command -v micropython >/dev/null 2>&1; then
58
59
  run micropython tests/test_audio_fx.py
59
60
  run micropython tests/test_arpeggiator.py
60
61
  run micropython tests/test_trig_sequencer.py
62
+ run micropython tests/test_step_sequencer.py
61
63
  run micropython tests/test_paramset.py
62
64
  run micropython tests/test_param_scaler.py
63
65
  run micropython tests/test_harmony.py
@@ -5,8 +5,8 @@ for the constructor bug fixed alongside this file: Arpeggiator.__init__
5
5
  called `self.set_bpm(120)` with no rate, which overwrote self.rate with
6
6
  set_bpm's own default (None) and raised TypeError on every construction.
7
7
 
8
- arpeggiator.py imports only `time` (with a `supervisor.ticks_ms` shim), so
9
- like test_patch.py this runs on a bare interpreter: no stubs needed.
8
+ arpeggiator.py needs only adafruit_ticks, vendored unchanged in tests/lib/,
9
+ so like test_patch.py this runs on a bare interpreter: no stubs needed.
10
10
  ticks_ms is monkeypatched to a fake, settable clock so the timing tests are
11
11
  exact and instant instead of racing real wall-clock sleeps.
12
12
 
@@ -18,6 +18,7 @@ import sys
18
18
 
19
19
  _D = __file__.rsplit("/", 1)[0] if "/" in __file__ else "."
20
20
  sys.path.insert(0, _D + "/../synthtools") # direct: skips synthtools/__init__
21
+ sys.path.insert(0, _D + "/lib") # the real adafruit_ticks, vendored
21
22
 
22
23
  import arpeggiator # noqa: E402
23
24
  from arpeggiator import Arpeggiator # noqa: E402
@@ -33,8 +34,11 @@ def ck(cond, msg):
33
34
  _now = [0]
34
35
 
35
36
 
37
+ WRAP = 1 << 29
38
+
39
+
36
40
  def fake_ticks_ms():
37
- return _now[0]
41
+ return _now[0] % WRAP # wraps like supervisor.ticks_ms()
38
42
 
39
43
 
40
44
  def set_time(ms):
@@ -162,6 +166,24 @@ ck(advance(375) == [("on", 60 + 12)],
162
166
  "next pass must transpose by oct_distance * octave")
163
167
  ck(arp4.i == 1, "i must advance normally within the transposed pass")
164
168
 
169
+ # --- across the ticks_ms() wrap: no stop, no hiccup -----------------------
170
+ # supervisor.ticks_ms() first wraps ~65 s after power-on
171
+ ons, offs = [], []
172
+ arp5 = Arpeggiator(4, lambda n: ons.append(_now[0]), lambda n: offs.append(_now[0]))
173
+ arp5.set_bpm(120, 4)
174
+ arp5.notes = [60]
175
+ set_time(WRAP - 600)
176
+ arp5.start()
177
+ while _now[0] < WRAP + 1000:
178
+ arp5.update()
179
+ _now[0] += 1
180
+ gaps = [b - a for a, b in zip(ons, ons[1:])]
181
+ ck(len(ons) == 13 and min(gaps) == max(gaps) == 125,
182
+ "notes must keep coming every 125 ms across the wrap, got gaps %r" % gaps)
183
+ ck(len(offs) == len(ons), "gates must keep releasing across the wrap, got %d" % len(offs))
184
+ ck(isinstance(arp5.next_millis, int) and isinstance(arp5.held_millis, int),
185
+ "tick math must stay int; a 30-bit float can't hold a tick near 2**29")
186
+
165
187
  if fails:
166
188
  print("FAILURES (%d):" % len(fails))
167
189
  for f in fails:
@@ -674,6 +674,20 @@ ck(
674
674
  "outlive the sweep, which is what makes the sweep audible",
675
675
  )
676
676
 
677
+ # `decay` is the 303-facing patch field; fenv_attack is the fallback for
678
+ # patches written before it was read
679
+ dk = make(decay=0.09)
680
+ ck(dk.decay == 0.09, "a patch's decay must load, got %r" % dk.decay)
681
+ ck(dk._fenv.attack == 0.09, "...and reach the block, got %r" % dk._fenv.attack)
682
+ ck(make(fenv_attack=0.2).decay == 0.2, "no decay field must fall back to fenv_attack")
683
+ dk.decay = 0.12
684
+ dk.save_patch()
685
+ ck(
686
+ dk.patch.decay == 0.12 and dk.patch.fenv_attack == 0.12,
687
+ "decay must save as both fields, got decay=%r fenv_attack=%r"
688
+ % (dk.patch.decay, dk.patch.fenv_attack),
689
+ )
690
+
677
691
  # --- keyboard tracking on the ONE shared mono cutoff node ----------------
678
692
  # BasslineSynth reuses a single cutoff node across notes, re-aiming its
679
693
  # spare .b/.c inputs. That makes a STALE slot the real hazard: turning