circuitpython-tmidi 1.0.5__tar.gz → 3.0.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 (71) hide show
  1. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.github/workflows/release_gh.yml +1 -4
  2. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.gitignore +4 -0
  3. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.pre-commit-config.yaml +3 -3
  4. circuitpython_tmidi-3.0.0/LATENCY_RESULTS.md +77 -0
  5. {circuitpython_tmidi-1.0.5/circuitpython_tmidi.egg-info → circuitpython_tmidi-3.0.0}/PKG-INFO +101 -24
  6. circuitpython_tmidi-3.0.0/README.rst +194 -0
  7. circuitpython_tmidi-3.0.0/REUSE.toml +6 -0
  8. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0/circuitpython_tmidi.egg-info}/PKG-INFO +101 -24
  9. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/SOURCES.txt +26 -2
  10. circuitpython_tmidi-3.0.0/docs/examples.rst +47 -0
  11. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/index.rst +1 -4
  12. circuitpython_tmidi-3.0.0/examples/tmidi_adafruit_midi_comparison.py +160 -0
  13. circuitpython_tmidi-3.0.0/examples/tmidi_latency_device.py +348 -0
  14. circuitpython_tmidi-3.0.0/examples/tmidi_latency_host.py +110 -0
  15. circuitpython_tmidi-3.0.0/examples/tmidi_simple_arpeggiator.py +69 -0
  16. circuitpython_tmidi-3.0.0/examples/tmidi_simple_poll.py +44 -0
  17. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/examples/tmidi_simple_receiver.py +7 -3
  18. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/examples/tmidi_simpletest.py +2 -2
  19. circuitpython_tmidi-3.0.0/examples/tmidi_sysex.py +17 -0
  20. circuitpython_tmidi-3.0.0/examples/tmidi_test_device.py +168 -0
  21. circuitpython_tmidi-3.0.0/examples/tmidi_test_host.py +428 -0
  22. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/pyproject.toml +14 -7
  23. circuitpython_tmidi-3.0.0/src/Makefile +19 -0
  24. circuitpython_tmidi-3.0.0/src/tmidi_turbo.py +131 -0
  25. circuitpython_tmidi-3.0.0/tests/__init__.py +2 -0
  26. circuitpython_tmidi-3.0.0/tests/conftest.py +8 -0
  27. circuitpython_tmidi-3.0.0/tests/data/rk006_capture.txt +518 -0
  28. circuitpython_tmidi-3.0.0/tests/test_kernel_model.py +70 -0
  29. circuitpython_tmidi-3.0.0/tests/test_tmidi_callbacks.py +196 -0
  30. circuitpython_tmidi-3.0.0/tests/test_tmidi_capture.py +65 -0
  31. circuitpython_tmidi-3.0.0/tests/test_tmidi_messages.py +77 -0
  32. circuitpython_tmidi-3.0.0/tests/test_tmidi_one.py +154 -0
  33. circuitpython_tmidi-3.0.0/tests/test_tmidi_sysex.py +132 -0
  34. circuitpython_tmidi-3.0.0/tmidi/__init__.py +643 -0
  35. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.armv6m.mpy +0 -0
  36. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.armv7emsp.mpy +0 -0
  37. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.rv32imc.mpy +0 -0
  38. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.xtensawin.mpy +0 -0
  39. circuitpython_tmidi-1.0.5/README.rst +0 -116
  40. circuitpython_tmidi-1.0.5/docs/examples.rst +0 -18
  41. circuitpython_tmidi-1.0.5/tests/test_tmidi_one.py +0 -64
  42. circuitpython_tmidi-1.0.5/tmidi.py +0 -365
  43. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  44. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.github/workflows/build.yml +0 -0
  45. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.github/workflows/failure-help-text.yml +0 -0
  46. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.github/workflows/release_pypi.yml +0 -0
  47. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.pylintrc +0 -0
  48. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/.readthedocs.yaml +0 -0
  49. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/CODE_OF_CONDUCT.md +0 -0
  50. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/LICENSE +0 -0
  51. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/LICENSES/CC-BY-4.0.txt +0 -0
  52. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/LICENSES/MIT.txt +0 -0
  53. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/LICENSES/Unlicense.txt +0 -0
  54. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/README.rst.license +0 -0
  55. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/dependency_links.txt +0 -0
  56. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/requires.txt +0 -0
  57. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/top_level.txt +0 -0
  58. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/_static/favicon.ico +0 -0
  59. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/_static/favicon.ico.license +0 -0
  60. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/api.rst +0 -0
  61. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/api.rst.license +0 -0
  62. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/conf.py +0 -0
  63. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/examples.rst.license +0 -0
  64. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/index.rst.license +0 -0
  65. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/docs/requirements.txt +0 -0
  66. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/examples/tmidi_midiforwarder.py +0 -0
  67. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/examples/tmidi_multiple_receiver.py +0 -0
  68. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/optional_requirements.txt +0 -0
  69. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/requirements.txt +0 -0
  70. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/setup.cfg +0 -0
  71. {circuitpython_tmidi-1.0.5 → circuitpython_tmidi-3.0.0}/tests/test_one.py +0 -0
@@ -17,7 +17,4 @@ jobs:
17
17
  with:
18
18
  github-token: ${{ secrets.GITHUB_TOKEN }}
19
19
  upload-url: ${{ github.event.release.upload_url }}
20
- # TODO: If you're creating a package (library is a folder), add this
21
- # argument along with the prefix (or full name) of the package folder
22
- # so the MPY bundles are built correctly:s
23
- # package-prefix: tmidi
20
+ package-prefix: tmidi
@@ -24,6 +24,7 @@
24
24
 
25
25
  # CircuitPython-specific files
26
26
  *.mpy
27
+ !tmidi/tmidi_kernel.*.mpy
27
28
 
28
29
  # Python-specific files
29
30
  __pycache__
@@ -46,3 +47,6 @@ _build
46
47
  .idea
47
48
  .vscode
48
49
  *~
50
+
51
+ .claude
52
+ CLAUDE.md
@@ -4,18 +4,18 @@
4
4
 
5
5
  repos:
6
6
  - repo: https://github.com/pre-commit/pre-commit-hooks
7
- rev: v4.5.0
7
+ rev: v6.0.0
8
8
  hooks:
9
9
  - id: check-yaml
10
10
  - id: end-of-file-fixer
11
11
  - id: trailing-whitespace
12
12
  - repo: https://github.com/astral-sh/ruff-pre-commit
13
- rev: v0.3.4
13
+ rev: v0.15.12
14
14
  hooks:
15
15
  - id: ruff-format
16
16
  - id: ruff
17
17
  args: ["--fix"]
18
18
  - repo: https://github.com/fsfe/reuse-tool
19
- rev: v3.0.1
19
+ rev: v6.2.0
20
20
  hooks:
21
21
  - id: reuse
@@ -0,0 +1,77 @@
1
+ # tmidi latency results
2
+
3
+ Time from a MIDI message on the wire to CircuitPython code acting on it,
4
+ from `examples/tmidi_latency_device.py` (and `tmidi_latency_host.py` for
5
+ USB).
6
+
7
+ - Raspberry Pi Pico (RP2040) @ 125 MHz, CircuitPython 11.0.0-alpha.2
8
+ - tmidi with the pure Python parser
9
+ - Cells are median (p90) in ms
10
+ - "idle": the main loop only reads MIDI
11
+ - "busy": the main loop also does 5 ms of other work per pass
12
+ - Clock jitter: how far handled clocks land from their 20.8 ms spacing
13
+ (120 BPM)
14
+
15
+ ## UART
16
+
17
+ Absolute, via a GP20 -> GP21 loopback. A 3-byte message takes 0.96 ms on
18
+ the wire, which is included. The Pico also does the sending here, so CC
19
+ storm losses are pessimistic.
20
+
21
+ Idle loop:
22
+
23
+ | Scenario | tmidi receive() | tmidi poll() | adafruit_midi |
24
+ |-----------------|------------------|--------------|------------------|
25
+ | note | 2.05 (2.17) | 1.89 (1.95) | 3.84 (4.49) |
26
+ | chord, 8 notes | 4.95 (5.39) | 1.85 (2.20) | 16.19 (18.17) |
27
+ | clock | 1.68 (1.80) | 1.43 (1.56) | 3.57 (4.21) |
28
+ | clock jitter | 0.14 (0.48) | 0.13 (0.39) | 0.62 (2.60) |
29
+ | CC storm | 68.5, 1403 lost | 1.88 (2.14) | 139, 1982 lost |
30
+
31
+ Busy loop:
32
+
33
+ | Scenario | tmidi receive() | tmidi poll() | adafruit_midi |
34
+ |-----------------|------------------|--------------|------------------|
35
+ | note | 5.52 (10.71) | 4.03 (6.44) | 71.72 (80.99) |
36
+ | chord, 8 notes | 23.72 (40.16) | 5.07 (7.20) | 70.67 (94.29) |
37
+ | clock | 6.80 (14.65) | 3.97 (6.62) | 57.71 (91.13) |
38
+ | clock jitter | 3.55 (8.90) | 2.40 (5.01) | 8.59 (14.15) |
39
+ | CC storm | 288, 2563 lost | 5.79 (7.37) | 285, 2702 lost |
40
+
41
+ CC storm: 3000 CCs back to back at wire speed.
42
+
43
+ ## USB
44
+
45
+ Relative: the Mac and the Pico share no clock, so these are the delay over
46
+ the fastest message seen in each run. No USB run lost messages; "behind"
47
+ means it had not caught up with the storm when the run ended.
48
+
49
+ Idle loop:
50
+
51
+ | Scenario | tmidi receive() | tmidi poll() | adafruit_midi |
52
+ |-----------------|------------------|--------------|------------------|
53
+ | note | 0.15 (0.22) | 0.14 (0.21) | 0.46 (0.77) |
54
+ | chord, 8 notes | 2.94 (5.13) | 2.31 (4.20) | 6.99 (12.31) |
55
+ | clock | 0.11 (0.17) | 0.11 (0.18) | 0.34 (0.55) |
56
+ | clock jitter | 0.05 (0.13) | 0.04 (0.13) | 0.14 (0.42) |
57
+ | CC storm | 8.20 (14.25) | 0.92 (1.07) | behind |
58
+
59
+ Busy loop:
60
+
61
+ | Scenario | tmidi receive() | tmidi poll() | adafruit_midi |
62
+ |-----------------|------------------|--------------|------------------|
63
+ | note | 4.32 (7.31) | 2.80 (5.12) | 17.03 (33.75) |
64
+ | chord, 8 notes | 23.65 (43.10) | 4.21 (6.99) | 27.35 (50.38) |
65
+ | clock | 4.59 (10.62) | 2.55 (4.61) | 7.52 (21.69) |
66
+ | clock jitter | 3.37 (5.17) | 2.27 (3.74) | 5.27 (12.32) |
67
+ | CC storm | behind | 2.97 (6.37) | behind |
68
+
69
+ ## Takeaways
70
+
71
+ - Single notes on an idle loop: all three are within about 2 ms.
72
+ - receive() (tmidi or adafruit_midi) handles one message per pass, so
73
+ chords, clock and storms back up behind a busy loop. poll() handles
74
+ everything waiting each pass and stays close to idle-loop numbers.
75
+ - For steady MIDI clock, use poll() and keep the main loop's passes short.
76
+ - The viper parser made idle-loop medians 0.1-0.7 ms faster and
77
+ otherwise changed little.
@@ -1,16 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-tmidi
3
- Version: 1.0.5
3
+ Version: 3.0.0
4
4
  Summary: MIDI library for CircuitPython
5
5
  Author-email: Tod Kurt <tod@todbot.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/todbot/CircuitPython_TMIDI
8
8
  Keywords: adafruit,blinka,circuitpython,micropython,tmidi,midi
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: Topic :: Software Development :: Libraries
11
11
  Classifier: Topic :: Software Development :: Embedded Systems
12
12
  Classifier: Topic :: System :: Hardware
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Description-Content-Type: text/x-rst
16
15
  License-File: LICENSE
@@ -37,6 +36,100 @@ Introduction
37
36
 
38
37
  MIDI library for CircuitPython
39
38
 
39
+ TMIDI is a MIDI library for CircuitPython / MicroPython. It attempts to
40
+ be a provide a simpler API and perform faster, especially for MIDI parsing,
41
+ compared to ``adafruit_midi`` (`adafruit_midi github <https://github.com/adafruit/Adafruit_CircuitPython_MIDI/>`_)
42
+ TMIDI is derived from ``winterbloom_smolmidi``
43
+ (`smolmidi github <https://github.com/wntrblm/Winterbloom_SmolMIDI>`_)
44
+ by Thea Flowers for Winterbloom.
45
+
46
+ Like ``adafruit_midi``, TMIDI works on CircuitPython's ``usb_midi`` and ``busio.UART``,
47
+ or any stream-like object that supports ``.readinto()`` and ``.write()``
48
+
49
+ Usage Example
50
+ =============
51
+
52
+ Sending MIDI over USB. Channels are 0-indexed, so ``channel=0`` is
53
+ MIDI channel 1:
54
+
55
+ .. code-block:: python
56
+
57
+ import time
58
+ import usb_midi
59
+ import tmidi
60
+
61
+ midi = tmidi.MIDI(midi_out=usb_midi.ports[1])
62
+
63
+ while True:
64
+ midi.send(tmidi.Message(tmidi.NOTE_ON, 60, 100, channel=0))
65
+ time.sleep(0.5)
66
+ midi.send(tmidi.Message(tmidi.NOTE_OFF, 60, 0, channel=0))
67
+ time.sleep(0.5)
68
+
69
+ Receiving MIDI over USB. ``receive()`` returns ``None`` when nothing
70
+ has arrived:
71
+
72
+ .. code-block:: python
73
+
74
+ import usb_midi
75
+ import tmidi
76
+
77
+ midi = tmidi.MIDI(midi_in=usb_midi.ports[0])
78
+
79
+ while True:
80
+ if msg := midi.receive():
81
+ if msg.type == tmidi.NOTE_ON:
82
+ print("note on:", msg.note, msg.velocity, msg.channel)
83
+ else:
84
+ print(msg)
85
+
86
+
87
+ Callbacks
88
+ =========
89
+
90
+ Instead of ``receive()``, register functions for the message types you want
91
+ and call ``poll()``. No ``Message`` objects are allocated.
92
+
93
+ .. code-block:: python
94
+
95
+ def note_on(channel, note, velocity):
96
+ print("note on:", channel, note, velocity)
97
+
98
+ midi.on(tmidi.NOTE_ON, note_on)
99
+ while True:
100
+ midi.poll()
101
+
102
+ See ``examples/tmidi_simple_poll.py`` for a full example.
103
+
104
+ Turbo parser
105
+ ============
106
+
107
+ TMIDI runs entirely in Python, but uses a faster ``@micropython.viper``
108
+ parser when your firmware can load native code. The parsers ship inside the
109
+ ``tmidi/`` library folder as ``tmidi_kernel.<arch>.mpy``, and CircuitPython
110
+ picks the one for your board. ``tmidi.ENGINE`` says which parser is in use.
111
+
112
+ TMIDI 3.0 is a folder, ``lib/tmidi/``, instead of a single ``lib/tmidi.mpy``.
113
+ Delete an old ``lib/tmidi.mpy`` when upgrading.
114
+
115
+ ====================== =====================================
116
+ ``tmidi_kernel`` file Boards
117
+ ====================== =====================================
118
+ ``armv6m`` RP2040
119
+ ``armv7emsp`` SAMD51, nRF52840, RP2350, STM32F4
120
+ ``xtensawin`` ESP32-S2, ESP32-S3
121
+ ``rv32imc`` ESP32-C3, C5, C6
122
+ ====================== =====================================
123
+
124
+ To rebuild them from ``src/tmidi_turbo.py``, use an ``mpy-cross`` from the
125
+ CircuitPython version they will run on. The files land in ``tmidi/``:
126
+
127
+ .. code-block:: shell
128
+
129
+ cd src
130
+ make MPY_CROSS=path/to/circuitpython/mpy-cross/build/mpy-cross
131
+
132
+
40
133
 
41
134
  Dependencies
42
135
  =============
@@ -99,27 +192,6 @@ Or the following command to update an existing version:
99
192
 
100
193
  circup update
101
194
 
102
- Usage Example
103
- =============
104
-
105
- .. code-block:: python
106
-
107
- import usb_midi
108
- import tmidi
109
-
110
- midi_usb = tmidi.MIDI(midi_in=usb_midi.ports[0], midi_out=usb_midi.ports[1])
111
-
112
- def send_midi_panic(channel=1):
113
- msg = tmidi.Message(tmidi.NOTE_OFF, channel-1) # channels are 0-indexed
114
- for notenum in range(128):
115
- msg.note = notenum
116
- msg.velocity = 0
117
- midi_usb.send(msg)
118
- time.sleep(0.01)
119
- }
120
-
121
- send_midi_panic()
122
-
123
195
 
124
196
  Documentation
125
197
  =============
@@ -128,6 +200,11 @@ API documentation for this library can be found on `Read the Docs <https://circu
128
200
  For information on building library documentation, please check out
129
201
  `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
130
202
 
203
+ Testing
204
+ =======
205
+
206
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
207
+
131
208
  Contributing
132
209
  ============
133
210
 
@@ -0,0 +1,194 @@
1
+ Introduction
2
+ ============
3
+
4
+
5
+ .. image:: https://readthedocs.org/projects/circuitpython-tmidi/badge/?version=latest
6
+ :target: https://circuitpython-tmidi.readthedocs.io/
7
+ :alt: Documentation Status
8
+ .. image:: https://img.shields.io/discord/327254708534116352.svg
9
+ :target: https://adafru.it/discord
10
+ :alt: Discord
11
+ .. image:: https://github.com/todbot/CircuitPython_TMIDI/workflows/Build%20CI/badge.svg
12
+ :target: https://github.com/todbot/CircuitPython_TMIDI/actions
13
+ :alt: Build Status
14
+ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
15
+ :target: https://github.com/psf/black
16
+ :alt: Code Style: Black
17
+
18
+ MIDI library for CircuitPython
19
+
20
+ TMIDI is a MIDI library for CircuitPython / MicroPython. It attempts to
21
+ be a provide a simpler API and perform faster, especially for MIDI parsing,
22
+ compared to ``adafruit_midi`` (`adafruit_midi github <https://github.com/adafruit/Adafruit_CircuitPython_MIDI/>`_)
23
+ TMIDI is derived from ``winterbloom_smolmidi``
24
+ (`smolmidi github <https://github.com/wntrblm/Winterbloom_SmolMIDI>`_)
25
+ by Thea Flowers for Winterbloom.
26
+
27
+ Like ``adafruit_midi``, TMIDI works on CircuitPython's ``usb_midi`` and ``busio.UART``,
28
+ or any stream-like object that supports ``.readinto()`` and ``.write()``
29
+
30
+ Usage Example
31
+ =============
32
+
33
+ Sending MIDI over USB. Channels are 0-indexed, so ``channel=0`` is
34
+ MIDI channel 1:
35
+
36
+ .. code-block:: python
37
+
38
+ import time
39
+ import usb_midi
40
+ import tmidi
41
+
42
+ midi = tmidi.MIDI(midi_out=usb_midi.ports[1])
43
+
44
+ while True:
45
+ midi.send(tmidi.Message(tmidi.NOTE_ON, 60, 100, channel=0))
46
+ time.sleep(0.5)
47
+ midi.send(tmidi.Message(tmidi.NOTE_OFF, 60, 0, channel=0))
48
+ time.sleep(0.5)
49
+
50
+ Receiving MIDI over USB. ``receive()`` returns ``None`` when nothing
51
+ has arrived:
52
+
53
+ .. code-block:: python
54
+
55
+ import usb_midi
56
+ import tmidi
57
+
58
+ midi = tmidi.MIDI(midi_in=usb_midi.ports[0])
59
+
60
+ while True:
61
+ if msg := midi.receive():
62
+ if msg.type == tmidi.NOTE_ON:
63
+ print("note on:", msg.note, msg.velocity, msg.channel)
64
+ else:
65
+ print(msg)
66
+
67
+
68
+ Callbacks
69
+ =========
70
+
71
+ Instead of ``receive()``, register functions for the message types you want
72
+ and call ``poll()``. No ``Message`` objects are allocated.
73
+
74
+ .. code-block:: python
75
+
76
+ def note_on(channel, note, velocity):
77
+ print("note on:", channel, note, velocity)
78
+
79
+ midi.on(tmidi.NOTE_ON, note_on)
80
+ while True:
81
+ midi.poll()
82
+
83
+ See ``examples/tmidi_simple_poll.py`` for a full example.
84
+
85
+ Turbo parser
86
+ ============
87
+
88
+ TMIDI runs entirely in Python, but uses a faster ``@micropython.viper``
89
+ parser when your firmware can load native code. The parsers ship inside the
90
+ ``tmidi/`` library folder as ``tmidi_kernel.<arch>.mpy``, and CircuitPython
91
+ picks the one for your board. ``tmidi.ENGINE`` says which parser is in use.
92
+
93
+ TMIDI 3.0 is a folder, ``lib/tmidi/``, instead of a single ``lib/tmidi.mpy``.
94
+ Delete an old ``lib/tmidi.mpy`` when upgrading.
95
+
96
+ ====================== =====================================
97
+ ``tmidi_kernel`` file Boards
98
+ ====================== =====================================
99
+ ``armv6m`` RP2040
100
+ ``armv7emsp`` SAMD51, nRF52840, RP2350, STM32F4
101
+ ``xtensawin`` ESP32-S2, ESP32-S3
102
+ ``rv32imc`` ESP32-C3, C5, C6
103
+ ====================== =====================================
104
+
105
+ To rebuild them from ``src/tmidi_turbo.py``, use an ``mpy-cross`` from the
106
+ CircuitPython version they will run on. The files land in ``tmidi/``:
107
+
108
+ .. code-block:: shell
109
+
110
+ cd src
111
+ make MPY_CROSS=path/to/circuitpython/mpy-cross/build/mpy-cross
112
+
113
+
114
+
115
+ Dependencies
116
+ =============
117
+ This library depends on:
118
+
119
+ * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
120
+
121
+ Please ensure all dependencies are available on the CircuitPython filesystem.
122
+ This is easily achieved by downloading
123
+ `the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
124
+ or individual libraries can be installed using
125
+ `circup <https://github.com/adafruit/circup>`_.
126
+
127
+ Installing from PyPI
128
+ =====================
129
+
130
+ On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
131
+ PyPI <https://pypi.org/project/circuitpython-tmidi/>`_.
132
+ To install for current user:
133
+
134
+ .. code-block:: shell
135
+
136
+ pip3 install circuitpython-tmidi
137
+
138
+ To install system-wide (this may be required in some cases):
139
+
140
+ .. code-block:: shell
141
+
142
+ sudo pip3 install circuitpython-tmidi
143
+
144
+ To install in a virtual environment in your current project:
145
+
146
+ .. code-block:: shell
147
+
148
+ mkdir project-name && cd project-name
149
+ python3 -m venv .venv
150
+ source .env/bin/activate
151
+ pip3 install circuitpython-tmidi
152
+
153
+ Installing to a Connected CircuitPython Device with Circup
154
+ ==========================================================
155
+
156
+ Make sure that you have ``circup`` installed in your Python environment.
157
+ Install it with the following command if necessary:
158
+
159
+ .. code-block:: shell
160
+
161
+ pip3 install circup
162
+
163
+ With ``circup`` installed and your CircuitPython device connected use the
164
+ following command to install:
165
+
166
+ .. code-block:: shell
167
+
168
+ circup install tmidi
169
+
170
+ Or the following command to update an existing version:
171
+
172
+ .. code-block:: shell
173
+
174
+ circup update
175
+
176
+
177
+ Documentation
178
+ =============
179
+ API documentation for this library can be found on `Read the Docs <https://circuitpython-tmidi.readthedocs.io/>`_.
180
+
181
+ For information on building library documentation, please check out
182
+ `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
183
+
184
+ Testing
185
+ =======
186
+
187
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
188
+
189
+ Contributing
190
+ ============
191
+
192
+ Contributions are welcome! Please read our `Code of Conduct
193
+ <https://github.com/todbot/CircuitPython_TMIDI/blob/HEAD/CODE_OF_CONDUCT.md>`_
194
+ before contributing to help this project stay welcoming.
@@ -0,0 +1,6 @@
1
+ version = 1
2
+
3
+ [[annotations]]
4
+ path = ["LATENCY_RESULTS.md", "tests/data/**", "tmidi/tmidi_kernel.*.mpy"]
5
+ SPDX-FileCopyrightText = "Copyright (c) 2026 Tod Kurt"
6
+ SPDX-License-Identifier = "MIT"
@@ -1,16 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circuitpython-tmidi
3
- Version: 1.0.5
3
+ Version: 3.0.0
4
4
  Summary: MIDI library for CircuitPython
5
5
  Author-email: Tod Kurt <tod@todbot.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/todbot/CircuitPython_TMIDI
8
8
  Keywords: adafruit,blinka,circuitpython,micropython,tmidi,midi
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: Topic :: Software Development :: Libraries
11
11
  Classifier: Topic :: Software Development :: Embedded Systems
12
12
  Classifier: Topic :: System :: Hardware
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Description-Content-Type: text/x-rst
16
15
  License-File: LICENSE
@@ -37,6 +36,100 @@ Introduction
37
36
 
38
37
  MIDI library for CircuitPython
39
38
 
39
+ TMIDI is a MIDI library for CircuitPython / MicroPython. It attempts to
40
+ be a provide a simpler API and perform faster, especially for MIDI parsing,
41
+ compared to ``adafruit_midi`` (`adafruit_midi github <https://github.com/adafruit/Adafruit_CircuitPython_MIDI/>`_)
42
+ TMIDI is derived from ``winterbloom_smolmidi``
43
+ (`smolmidi github <https://github.com/wntrblm/Winterbloom_SmolMIDI>`_)
44
+ by Thea Flowers for Winterbloom.
45
+
46
+ Like ``adafruit_midi``, TMIDI works on CircuitPython's ``usb_midi`` and ``busio.UART``,
47
+ or any stream-like object that supports ``.readinto()`` and ``.write()``
48
+
49
+ Usage Example
50
+ =============
51
+
52
+ Sending MIDI over USB. Channels are 0-indexed, so ``channel=0`` is
53
+ MIDI channel 1:
54
+
55
+ .. code-block:: python
56
+
57
+ import time
58
+ import usb_midi
59
+ import tmidi
60
+
61
+ midi = tmidi.MIDI(midi_out=usb_midi.ports[1])
62
+
63
+ while True:
64
+ midi.send(tmidi.Message(tmidi.NOTE_ON, 60, 100, channel=0))
65
+ time.sleep(0.5)
66
+ midi.send(tmidi.Message(tmidi.NOTE_OFF, 60, 0, channel=0))
67
+ time.sleep(0.5)
68
+
69
+ Receiving MIDI over USB. ``receive()`` returns ``None`` when nothing
70
+ has arrived:
71
+
72
+ .. code-block:: python
73
+
74
+ import usb_midi
75
+ import tmidi
76
+
77
+ midi = tmidi.MIDI(midi_in=usb_midi.ports[0])
78
+
79
+ while True:
80
+ if msg := midi.receive():
81
+ if msg.type == tmidi.NOTE_ON:
82
+ print("note on:", msg.note, msg.velocity, msg.channel)
83
+ else:
84
+ print(msg)
85
+
86
+
87
+ Callbacks
88
+ =========
89
+
90
+ Instead of ``receive()``, register functions for the message types you want
91
+ and call ``poll()``. No ``Message`` objects are allocated.
92
+
93
+ .. code-block:: python
94
+
95
+ def note_on(channel, note, velocity):
96
+ print("note on:", channel, note, velocity)
97
+
98
+ midi.on(tmidi.NOTE_ON, note_on)
99
+ while True:
100
+ midi.poll()
101
+
102
+ See ``examples/tmidi_simple_poll.py`` for a full example.
103
+
104
+ Turbo parser
105
+ ============
106
+
107
+ TMIDI runs entirely in Python, but uses a faster ``@micropython.viper``
108
+ parser when your firmware can load native code. The parsers ship inside the
109
+ ``tmidi/`` library folder as ``tmidi_kernel.<arch>.mpy``, and CircuitPython
110
+ picks the one for your board. ``tmidi.ENGINE`` says which parser is in use.
111
+
112
+ TMIDI 3.0 is a folder, ``lib/tmidi/``, instead of a single ``lib/tmidi.mpy``.
113
+ Delete an old ``lib/tmidi.mpy`` when upgrading.
114
+
115
+ ====================== =====================================
116
+ ``tmidi_kernel`` file Boards
117
+ ====================== =====================================
118
+ ``armv6m`` RP2040
119
+ ``armv7emsp`` SAMD51, nRF52840, RP2350, STM32F4
120
+ ``xtensawin`` ESP32-S2, ESP32-S3
121
+ ``rv32imc`` ESP32-C3, C5, C6
122
+ ====================== =====================================
123
+
124
+ To rebuild them from ``src/tmidi_turbo.py``, use an ``mpy-cross`` from the
125
+ CircuitPython version they will run on. The files land in ``tmidi/``:
126
+
127
+ .. code-block:: shell
128
+
129
+ cd src
130
+ make MPY_CROSS=path/to/circuitpython/mpy-cross/build/mpy-cross
131
+
132
+
40
133
 
41
134
  Dependencies
42
135
  =============
@@ -99,27 +192,6 @@ Or the following command to update an existing version:
99
192
 
100
193
  circup update
101
194
 
102
- Usage Example
103
- =============
104
-
105
- .. code-block:: python
106
-
107
- import usb_midi
108
- import tmidi
109
-
110
- midi_usb = tmidi.MIDI(midi_in=usb_midi.ports[0], midi_out=usb_midi.ports[1])
111
-
112
- def send_midi_panic(channel=1):
113
- msg = tmidi.Message(tmidi.NOTE_OFF, channel-1) # channels are 0-indexed
114
- for notenum in range(128):
115
- msg.note = notenum
116
- msg.velocity = 0
117
- midi_usb.send(msg)
118
- time.sleep(0.01)
119
- }
120
-
121
- send_midi_panic()
122
-
123
195
 
124
196
  Documentation
125
197
  =============
@@ -128,6 +200,11 @@ API documentation for this library can be found on `Read the Docs <https://circu
128
200
  For information on building library documentation, please check out
129
201
  `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
130
202
 
203
+ Testing
204
+ =======
205
+
206
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
207
+
131
208
  Contributing
132
209
  ============
133
210
 
@@ -3,13 +3,14 @@
3
3
  .pylintrc
4
4
  .readthedocs.yaml
5
5
  CODE_OF_CONDUCT.md
6
+ LATENCY_RESULTS.md
6
7
  LICENSE
7
8
  README.rst
8
9
  README.rst.license
10
+ REUSE.toml
9
11
  optional_requirements.txt
10
12
  pyproject.toml
11
13
  requirements.txt
12
- tmidi.py
13
14
  .github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md
14
15
  .github/workflows/build.yml
15
16
  .github/workflows/failure-help-text.yml
@@ -33,9 +34,32 @@ docs/index.rst.license
33
34
  docs/requirements.txt
34
35
  docs/_static/favicon.ico
35
36
  docs/_static/favicon.ico.license
37
+ examples/tmidi_adafruit_midi_comparison.py
38
+ examples/tmidi_latency_device.py
39
+ examples/tmidi_latency_host.py
36
40
  examples/tmidi_midiforwarder.py
37
41
  examples/tmidi_multiple_receiver.py
42
+ examples/tmidi_simple_arpeggiator.py
43
+ examples/tmidi_simple_poll.py
38
44
  examples/tmidi_simple_receiver.py
39
45
  examples/tmidi_simpletest.py
46
+ examples/tmidi_sysex.py
47
+ examples/tmidi_test_device.py
48
+ examples/tmidi_test_host.py
49
+ src/Makefile
50
+ src/tmidi_turbo.py
51
+ tests/__init__.py
52
+ tests/conftest.py
53
+ tests/test_kernel_model.py
40
54
  tests/test_one.py
41
- tests/test_tmidi_one.py
55
+ tests/test_tmidi_callbacks.py
56
+ tests/test_tmidi_capture.py
57
+ tests/test_tmidi_messages.py
58
+ tests/test_tmidi_one.py
59
+ tests/test_tmidi_sysex.py
60
+ tests/data/rk006_capture.txt
61
+ tmidi/__init__.py
62
+ tmidi/tmidi_kernel.armv6m.mpy
63
+ tmidi/tmidi_kernel.armv7emsp.mpy
64
+ tmidi/tmidi_kernel.rv32imc.mpy
65
+ tmidi/tmidi_kernel.xtensawin.mpy