Repository navigation
midiports: arrival times for every incoming message - #211
Merged
Merged
Conversation
… port take_timestamp() on every input port pops when the next message arrived: whole microseconds on midiports.ticks_us() (time.ticks_us() on MicroPython, time.monotonic_ns() // 1000 on CPython, performance.now() in Pyodide), one per message in order, realtime bytes included. Running status stamps each message at its first data byte, a SysEx at its 0xF0, a split message at its first byte. The stamps live in a fixed ring, so stamping allocates nothing. USB device input is stamped in the event callback; UART, Linux, Windows and the usbif shim at the read that finds the bytes; Web MIDI keeps the browser's own times, now in microseconds like the rest. WholeMessages now holds a realtime byte that arrives inside a SysEx until the SysEx ends, since adafruit_midi loses both otherwise. The contract test checks the pairing on every port across running status, a clock mid-message, SysEx and a split read, and a planted fault (a clock left unstamped) fails it on every port.
…ges over USB and usbif The MIDI spec lets clock, start, stop and the other real-time bytes land inside a SysEx. usb_device now holds each one, with its arrival time, until the SysEx ends, as WholeMessages does for byte streams; a SysEx another message cuts short is ended with an 0xF7 first. The usbif shim's input goes through WholeMessages for the same reason. The contract test covers it on usb_device, WholeMessages, the usbif shim and Web MIDI, and the planted unstamped clock fails each case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every
midiportsinput port now remembers when each message arrived, so a looper, a recorder or a quantiser can place notes where they were played rather than where your loop got round to them. Playing stays as it was: read a message and act on it at once.This builds on #208 and uses the same
take_timestamp()it added for Web MIDI, so there's one contract. Until #208 merges, its commit shows here too; the change for review is the last commit.The contract
Documented once, in the package docstring:
midiports.ticks_us(). That'stime.ticks_us()on MicroPython, which wraps, so compare withmidiports.ticks_diff(). It'stime.monotonic_ns() // 1000on CPython, and the same on CircuitPython except that it wraps at 2**30 on a board. In Pyodide it'sperformance.now(), the clock Web MIDI stamps events with.take_timestamp()returnsNonewhen no stamp is waiting.adafruit_midi, take one for every objectreceive()returns, of any type. Leavein_channelat its default, because a message thatadafruit_midifilters out is consumed without being returned.Where each port stamps
usb_deviceon_midi_event, as the USB stack hands the event overwebmiditimeStamp)uart,linux(viaWholeMessages)windowsusbif_shimWeb MIDI stamps change from float milliseconds to integer microseconds, to match the rest. A relayed message without a
timeStampis now stamped when it reaches the worker, where before it gotNone.Real-time bytes (clock, start, stop and the rest) that arrive inside a SysEx, as the MIDI spec allows, now come out just after the SysEx ends. Each one is its own message with its own arrival time, in order.
WholeMessages(UART, Linux) andusb_devicehold up to 8 of them, and the usbif shim's input now goes throughWholeMessagestoo. Before, they went out in place, andadafruit_mididropped both the clock and the SysEx. A tempo-synced instrument would have drifted during a SysEx dump. A SysEx that another message cuts short is ended with an 0xF7 first. Web MIDI already delivers these as separate events. Windows input carries no SysEx.Tests
tests/midiports_contract.pygains a stamp check for every port:WholeMessages,uart,usb_device,usbif_shim,windowsandwebmidi. A fake clock moves on at each read, so each message's stamp names the read that found it. The checks cover running status, a clock in the middle of a note, a SysEx (and a clock inside one), a message split across two reads, and a full USB ring dropping messages, and clock and start bytes inside a SysEx on every port that can carry one. On MicroPython the checks also confirm that the ring and a burst throughWholeMessagesallocate nothing.bin/micropython) and a current unix build: 33 passed each.tests/test_midiports.py: 23 passed.--plant=skip-clock-stampleaves a clock byte unstamped. It fails the stamp checks on all six ports, the SysEx cases included, under both interpreters, andtest_midiports.pyasserts that it does.Not covered:
adafruit_midiitself drops a SysEx when it only sees part of it in one buffer, so a long SysEx that arrives over several reads is lost whatever the port does.