circuitpython-tmidi 2.0.0__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-2.0.0 → circuitpython_tmidi-3.0.0}/.github/workflows/release_gh.yml +1 -4
  2. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.gitignore +4 -0
  3. {circuitpython_tmidi-2.0.0 → 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-2.0.0/circuitpython_tmidi.egg-info → circuitpython_tmidi-3.0.0}/PKG-INFO +97 -27
  6. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/README.rst +95 -24
  7. circuitpython_tmidi-3.0.0/REUSE.toml +6 -0
  8. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0/circuitpython_tmidi.egg-info}/PKG-INFO +97 -27
  9. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/SOURCES.txt +23 -2
  10. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/examples.rst +10 -0
  11. {circuitpython_tmidi-2.0.0 → 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-3.0.0/examples/tmidi_simple_receiver.py +21 -0
  18. circuitpython_tmidi-3.0.0/examples/tmidi_sysex.py +17 -0
  19. circuitpython_tmidi-3.0.0/examples/tmidi_test_device.py +168 -0
  20. circuitpython_tmidi-3.0.0/examples/tmidi_test_host.py +428 -0
  21. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/pyproject.toml +14 -7
  22. circuitpython_tmidi-3.0.0/src/Makefile +19 -0
  23. circuitpython_tmidi-3.0.0/src/tmidi_turbo.py +131 -0
  24. circuitpython_tmidi-3.0.0/tests/conftest.py +8 -0
  25. circuitpython_tmidi-3.0.0/tests/data/rk006_capture.txt +518 -0
  26. circuitpython_tmidi-3.0.0/tests/test_kernel_model.py +70 -0
  27. circuitpython_tmidi-3.0.0/tests/test_tmidi_callbacks.py +196 -0
  28. circuitpython_tmidi-3.0.0/tests/test_tmidi_capture.py +65 -0
  29. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/tests/test_tmidi_messages.py +32 -1
  30. circuitpython_tmidi-3.0.0/tests/test_tmidi_one.py +154 -0
  31. circuitpython_tmidi-3.0.0/tests/test_tmidi_sysex.py +132 -0
  32. circuitpython_tmidi-3.0.0/tmidi/__init__.py +643 -0
  33. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.armv6m.mpy +0 -0
  34. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.armv7emsp.mpy +0 -0
  35. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.rv32imc.mpy +0 -0
  36. circuitpython_tmidi-3.0.0/tmidi/tmidi_kernel.xtensawin.mpy +0 -0
  37. circuitpython_tmidi-2.0.0/examples/tmidi_simple_arpeggiator.py +0 -58
  38. circuitpython_tmidi-2.0.0/examples/tmidi_simple_receiver.py +0 -31
  39. circuitpython_tmidi-2.0.0/tests/test_tmidi_one.py +0 -64
  40. circuitpython_tmidi-2.0.0/tmidi.py +0 -423
  41. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  42. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.github/workflows/build.yml +0 -0
  43. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.github/workflows/failure-help-text.yml +0 -0
  44. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.github/workflows/release_pypi.yml +0 -0
  45. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.pylintrc +0 -0
  46. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/.readthedocs.yaml +0 -0
  47. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/CODE_OF_CONDUCT.md +0 -0
  48. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/LICENSE +0 -0
  49. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/LICENSES/CC-BY-4.0.txt +0 -0
  50. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/LICENSES/MIT.txt +0 -0
  51. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/LICENSES/Unlicense.txt +0 -0
  52. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/README.rst.license +0 -0
  53. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/dependency_links.txt +0 -0
  54. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/requires.txt +0 -0
  55. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/circuitpython_tmidi.egg-info/top_level.txt +0 -0
  56. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/_static/favicon.ico +0 -0
  57. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/_static/favicon.ico.license +0 -0
  58. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/api.rst +0 -0
  59. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/api.rst.license +0 -0
  60. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/conf.py +0 -0
  61. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/examples.rst.license +0 -0
  62. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/index.rst.license +0 -0
  63. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/docs/requirements.txt +0 -0
  64. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/examples/tmidi_midiforwarder.py +0 -0
  65. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/examples/tmidi_multiple_receiver.py +0 -0
  66. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/examples/tmidi_simpletest.py +0 -0
  67. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/optional_requirements.txt +0 -0
  68. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/requirements.txt +0 -0
  69. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/setup.cfg +0 -0
  70. {circuitpython_tmidi-2.0.0 → circuitpython_tmidi-3.0.0}/tests/__init__.py +0 -0
  71. {circuitpython_tmidi-2.0.0 → 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: 2.0.0
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,28 +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(midi_channel=1):
113
- ch = midi_channel-1
114
- msg = tmidi.Message(tmidi.NOTE_OFF, channel=ch) # channels are 0-indexed
115
- for notenum in range(128):
116
- msg.note = notenum
117
- msg.velocity = 0
118
- midi_usb.send(msg)
119
- time.sleep(0.01)
120
- }
121
-
122
- send_midi_panic()
123
-
124
195
 
125
196
  Documentation
126
197
  =============
@@ -132,8 +203,7 @@ For information on building library documentation, please check out
132
203
  Testing
133
204
  =======
134
205
 
135
- Install ``pytest`` with ``pip3 install pytest --upgrade`` and run ``pytest -v``
136
- To build docs:
206
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
137
207
 
138
208
  Contributing
139
209
  ============
@@ -17,6 +17,100 @@ Introduction
17
17
 
18
18
  MIDI library for CircuitPython
19
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
+
20
114
 
21
115
  Dependencies
22
116
  =============
@@ -79,28 +173,6 @@ Or the following command to update an existing version:
79
173
 
80
174
  circup update
81
175
 
82
- Usage Example
83
- =============
84
-
85
- .. code-block:: python
86
-
87
- import usb_midi
88
- import tmidi
89
-
90
- midi_usb = tmidi.MIDI(midi_in=usb_midi.ports[0], midi_out=usb_midi.ports[1])
91
-
92
- def send_midi_panic(midi_channel=1):
93
- ch = midi_channel-1
94
- msg = tmidi.Message(tmidi.NOTE_OFF, channel=ch) # channels are 0-indexed
95
- for notenum in range(128):
96
- msg.note = notenum
97
- msg.velocity = 0
98
- midi_usb.send(msg)
99
- time.sleep(0.01)
100
- }
101
-
102
- send_midi_panic()
103
-
104
176
 
105
177
  Documentation
106
178
  =============
@@ -112,8 +184,7 @@ For information on building library documentation, please check out
112
184
  Testing
113
185
  =======
114
186
 
115
- Install ``pytest`` with ``pip3 install pytest --upgrade`` and run ``pytest -v``
116
- To build docs:
187
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
117
188
 
118
189
  Contributing
119
190
  ============
@@ -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: 2.0.0
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,28 +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(midi_channel=1):
113
- ch = midi_channel-1
114
- msg = tmidi.Message(tmidi.NOTE_OFF, channel=ch) # channels are 0-indexed
115
- for notenum in range(128):
116
- msg.note = notenum
117
- msg.velocity = 0
118
- midi_usb.send(msg)
119
- time.sleep(0.01)
120
- }
121
-
122
- send_midi_panic()
123
-
124
195
 
125
196
  Documentation
126
197
  =============
@@ -132,8 +203,7 @@ For information on building library documentation, please check out
132
203
  Testing
133
204
  =======
134
205
 
135
- Install ``pytest`` with ``pip3 install pytest --upgrade`` and run ``pytest -v``
136
- To build docs:
206
+ Install ``pytest`` with ``pip3 install pytest circuitpython-mocks --upgrade`` and run ``pytest -v``
137
207
 
138
208
  Contributing
139
209
  ============
@@ -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,12 +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
38
42
  examples/tmidi_simple_arpeggiator.py
43
+ examples/tmidi_simple_poll.py
39
44
  examples/tmidi_simple_receiver.py
40
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
41
51
  tests/__init__.py
52
+ tests/conftest.py
53
+ tests/test_kernel_model.py
42
54
  tests/test_one.py
55
+ tests/test_tmidi_callbacks.py
56
+ tests/test_tmidi_capture.py
43
57
  tests/test_tmidi_messages.py
44
- tests/test_tmidi_one.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
@@ -18,6 +18,16 @@ Show to to receive MIDI messages.
18
18
  :linenos:
19
19
 
20
20
 
21
+ Simple poll
22
+ -----------
23
+
24
+ Show how to receive MIDI messages with callbacks and ``poll()``.
25
+
26
+ .. literalinclude:: ../examples/tmidi_simple_poll.py
27
+ :caption: examples/tmidi_simple_poll.py
28
+ :linenos:
29
+
30
+
21
31
  Multiple receivers
22
32
  ------------------
23
33
 
@@ -36,10 +36,7 @@ Table of Contents
36
36
  Download Library Bundle <https://circuitpython.org/libraries>
37
37
  CircuitPython Reference Documentation <https://docs.circuitpython.org>
38
38
  CircuitPython Support Forum <https://forums.adafruit.com/viewforum.php?f=60>
39
- Discord Chat <https://adafru.it/discord>
40
- Adafruit Learning System <https://learn.adafruit.com>
41
- Adafruit Blog <https://blog.adafruit.com>
42
- Adafruit Store <https://www.adafruit.com>
39
+
43
40
 
44
41
  Indices and tables
45
42
  ==================