diff --git a/apps/__init__.py b/apps/__init__.py index 121e297..4f15319 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -1 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + # The apps folder is a package: appkit imports each app in it as apps.. diff --git a/apps/midiengine/midiin.py b/apps/midiengine/midiin.py index 9c8dfea..7e000a7 100644 --- a/apps/midiengine/midiin.py +++ b/apps/midiengine/midiin.py @@ -4,7 +4,7 @@ """What the apps that play say about the MIDI devices in the hub. Instruments and the midiEngine hear every USB MIDI device at once, through -``midiports.usb_host.all_ports()``. :class:`Watch` names the ones open now, +``midiports.usb_host.all_ports()``. `Watch` names the ones open now, for the status line, and notices when one comes or goes. """ diff --git a/apps/ostinato/app/engine/engine.py b/apps/ostinato/app/engine/engine.py index 94afdea..963f81d 100644 --- a/apps/ostinato/app/engine/engine.py +++ b/apps/ostinato/app/engine/engine.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato's sound engine. Each engine worker runs one Engine. Several engine workers render in parallel, each owning some of the tracks @@ -392,7 +396,7 @@ def apply_many(self, ops): def resequence(self): """Forget what was sequenced and not yet played: the song or the - transport changed. :meth:`sequence_ahead` sequences it again.""" + transport changed. `sequence_ahead` sequences it again.""" self.queue = [] self.sequenced_to = None @@ -469,7 +473,7 @@ def retempo(self, at_frame): # -- what plays --------------------------------------------------------- def segments(self, t0, t1): - """What plays between ticks t0 and t1: :func:`songmodel.model.segments`.""" + """What plays between ticks t0 and t1: `songmodel.model.segments`.""" return model.segments(self.project, t0, t1) def _sequence(self, until): diff --git a/apps/ostinato/app/engine/enginehost.py b/apps/ostinato/app/engine/enginehost.py index 0de8507..3a34ad2 100644 --- a/apps/ostinato/app/engine/enginehost.py +++ b/apps/ostinato/app/engine/enginehost.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """One engine, whatever hosts it: a browser worker, a process, or a thread. A host gives an ``EngineHost`` three things and asks one: diff --git a/apps/ostinato/app/engine/main.py b/apps/ostinato/app/engine/main.py index ce7128d..93b9dd2 100644 --- a/apps/ostinato/app/engine/main.py +++ b/apps/ostinato/app/engine/main.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Entry point of an engine worker. Ports from the page (workers/host.js): ``audio`` to the AudioWorklet, diff --git a/apps/ostinato/app/engine/master.py b/apps/ostinato/app/engine/master.py index 46349f0..389bda5 100644 --- a/apps/ostinato/app/engine/master.py +++ b/apps/ostinato/app/engine/master.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Entry point of the master worker: the master rack, on the sum of every track. The track engines don't talk to the AudioWorklet. Each one talks to this diff --git a/apps/ostinato/app/engine/mastercore.py b/apps/ostinato/app/engine/mastercore.py index 8193d36..6dc619f 100644 --- a/apps/ostinato/app/engine/mastercore.py +++ b/apps/ostinato/app/engine/mastercore.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The master track's sound, whatever hosts it: the tracks' blocks summed, through the master rack and volume. diff --git a/apps/ostinato/app/ui/catalog.py b/apps/ostinato/app/ui/catalog.py index f41c589..d5f5ef5 100644 --- a/apps/ostinato/app/ui/catalog.py +++ b/apps/ostinato/app/ui/catalog.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """What instruments and effects exist, from assets/catalog.json. scripts/make_catalog.mjs writes it from the same wheels the engines load, diff --git a/apps/ostinato/app/ui/editor.py b/apps/ostinato/app/ui/editor.py index 05a793e..e196b0a 100644 --- a/apps/ostinato/app/ui/editor.py +++ b/apps/ostinato/app/ui/editor.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The pattern editor: a step grid for drums, a piano roll for everything else. Both edit the selected track's current pattern through the app's history, so diff --git a/apps/ostinato/app/ui/engines.py b/apps/ostinato/app/ui/engines.py index 3ed6d06..ed96631 100644 --- a/apps/ostinato/app/ui/engines.py +++ b/apps/ostinato/app/ui/engines.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The UI's side of the engine workers. The UI owns the project. Every edit is an op, applied here and sent to every diff --git a/apps/ostinato/app/ui/files.py b/apps/ostinato/app/ui/files.py index 116e31f..a9417e3 100644 --- a/apps/ostinato/app/ui/files.py +++ b/apps/ostinato/app/ui/files.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Files: save and open projects, autosave, and export WAV and MIDI. The file pickers belong to the page (index.html in a browser, diff --git a/apps/ostinato/app/ui/fonts.py b/apps/ostinato/app/ui/fonts.py index c603934..019d542 100644 --- a/apps/ostinato/app/ui/fonts.py +++ b/apps/ostinato/app/ui/fonts.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Fonts: Montserrat from lvgl-bindings, Material icons from the app. Text fonts come from lvgl-bindings' ``fonts/`` folder, at the tag pinned for diff --git a/apps/ostinato/app/ui/help.py b/apps/ostinato/app/ui/help.py index 04a41c9..194c28e 100644 --- a/apps/ostinato/app/ui/help.py +++ b/apps/ostinato/app/ui/help.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The guide: how to play Ostinato, inside the app (it never links out). Shown once on the first run, and any time from the ? button. diff --git a/apps/ostinato/app/ui/history.py b/apps/ostinato/app/ui/history.py index 874be99..1496056 100644 --- a/apps/ostinato/app/ui/history.py +++ b/apps/ostinato/app/ui/history.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Undo and redo: every edit is an op with its inverse, grouped into steps. A step is what one undo takes back: one click on the grid, one drag in the diff --git a/apps/ostinato/app/ui/inspector.py b/apps/ostinato/app/ui/inspector.py index a8f68c6..2d5cee5 100644 --- a/apps/ostinato/app/ui/inspector.py +++ b/apps/ostinato/app/ui/inspector.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The inspector: the selected track's instrument, its knobs, and its effects; or, with the master selected, the master effects. diff --git a/apps/ostinato/app/ui/keyboard.py b/apps/ostinato/app/ui/keyboard.py index a9ea7be..09dc3c2 100644 --- a/apps/ostinato/app/ui/keyboard.py +++ b/apps/ostinato/app/ui/keyboard.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The on-screen keyboard: shows what's playing, plays the selected track.""" import lvgl as lv diff --git a/apps/ostinato/app/ui/knob.py b/apps/ostinato/app/ui/knob.py index de4562a..7a107b3 100644 --- a/apps/ostinato/app/ui/knob.py +++ b/apps/ostinato/app/ui/knob.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """A knob: drag up or down to turn it; its name under it, its value inside. Dragging is vertical, as in most music software: up turns it up, one step diff --git a/apps/ostinato/app/ui/layout.py b/apps/ostinato/app/ui/layout.py index 181518f..547753c 100644 --- a/apps/ostinato/app/ui/layout.py +++ b/apps/ostinato/app/ui/layout.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Where each region of the window goes. Every size here comes from the window size through one scale factor, and diff --git a/apps/ostinato/app/ui/main.py b/apps/ostinato/app/ui/main.py index 72d1fec..52a2e9f 100644 --- a/apps/ostinato/app/ui/main.py +++ b/apps/ostinato/app/ui/main.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato's UI: LVGL, the project, and the engines. In a browser this is the UI worker: the page (index.html) runs nothing but diff --git a/apps/ostinato/app/ui/meter.py b/apps/ostinato/app/ui/meter.py index 6f42ff2..ed2c650 100644 --- a/apps/ostinato/app/ui/meter.py +++ b/apps/ostinato/app/ui/meter.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """A stereo segment meter: left and right columns of lit segments. Levels arrive about 30 times a second as peak dB since the last reading. diff --git a/apps/ostinato/app/ui/page.py b/apps/ostinato/app/ui/page.py index 6b9767a..8412f68 100644 --- a/apps/ostinato/app/ui/page.py +++ b/apps/ostinato/app/ui/page.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The UI's line to whatever hosts it: here, the browser page (index.html). The page holds what a worker can't: the file pickers, local storage, the diff --git a/apps/ostinato/app/ui/song.py b/apps/ostinato/app/ui/song.py index cc55423..7c0fe83 100644 --- a/apps/ostinato/app/ui/song.py +++ b/apps/ostinato/app/ui/song.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The song: scenes in order, each a pattern per track and a repeat count. Click a track's letter in a scene to cycle through its patterns (and "-", diff --git a/apps/ostinato/app/ui/status.py b/apps/ostinato/app/ui/status.py index 9c590f1..4523908 100644 --- a/apps/ostinato/app/ui/status.py +++ b/apps/ostinato/app/ui/status.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The status bar: the project's name and whether it's saved, a word when the audio drops out, and on the right either a message or the last MIDI.""" diff --git a/apps/ostinato/app/ui/theme.py b/apps/ostinato/app/ui/theme.py index 1e0b493..2eb83b7 100644 --- a/apps/ostinato/app/ui/theme.py +++ b/apps/ostinato/app/ui/theme.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Colours, spacing and the type scale, defined once, as shared LVGL styles. Every widget takes its look from a handful of shared styles; a pressed, diff --git a/apps/ostinato/app/ui/tooltip.py b/apps/ostinato/app/ui/tooltip.py index 56a1dc6..2169926 100644 --- a/apps/ostinato/app/ui/tooltip.py +++ b/apps/ostinato/app/ui/tooltip.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Tooltips: what an icon-only button does, shown when the mouse rests on it. LVGL hovers a released pointer (HOVER_OVER, HOVER_LEAVE) when its input diff --git a/apps/ostinato/app/ui/tracks.py b/apps/ostinato/app/ui/tracks.py index 882ff31..24c0017 100644 --- a/apps/ostinato/app/ui/tracks.py +++ b/apps/ostinato/app/ui/tracks.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The track list: one row per track, select to play it, mute, solo, volume. Under the tracks, the master: its volume, and select it to see the master diff --git a/apps/ostinato/app/ui/transport.py b/apps/ostinato/app/ui/transport.py index b77fd4b..3a19e66 100644 --- a/apps/ostinato/app/ui/transport.py +++ b/apps/ostinato/app/ui/transport.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The top bar: transport, position, tempo, swing, metronome, patterns.""" import lvgl as lv diff --git a/apps/ostinato/desktop/app.py b/apps/ostinato/desktop/app.py index 538b05e..0309071 100644 --- a/apps/ostinato/desktop/app.py +++ b/apps/ostinato/desktop/app.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato on CPython, Phase 0 spike: the demo song through the desktop runner and mixer, live MIDI from a Windows port, and the shared UI drawing. diff --git a/apps/ostinato/desktop/control.py b/apps/ostinato/desktop/control.py index f2973cb..c22dac9 100644 --- a/apps/ostinato/desktop/control.py +++ b/apps/ostinato/desktop/control.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The control socket: what the checks drive the desktop app through. The browser checks reach the app through Chrome's DevTools: they send the UI diff --git a/apps/ostinato/desktop/device.py b/apps/ostinato/desktop/device.py index 2d9fc08..a8b87e8 100644 --- a/apps/ostinato/desktop/device.py +++ b/apps/ostinato/desktop/device.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The engine process as an hmilink device: it holds the song and makes the sound. python -m ostinato --role device [--port 9100] [--song FILE] diff --git a/apps/ostinato/desktop/dialogs.py b/apps/ostinato/desktop/dialogs.py index d67d734..3a45ba4 100644 --- a/apps/ostinato/desktop/dialogs.py +++ b/apps/ostinato/desktop/dialogs.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Windows' own Open and Save dialogs, through comdlg32 and ctypes. No dependency: ``GetOpenFileNameW`` and ``GetSaveFileNameW`` with the diff --git a/apps/ostinato/desktop/engine_loop.py b/apps/ostinato/desktop/engine_loop.py index 1380951..d509c9c 100644 --- a/apps/ostinato/desktop/engine_loop.py +++ b/apps/ostinato/desktop/engine_loop.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """An engine's life on the desktop, the same in a process and in a thread. The runner hands it ``io`` (how messages arrive and leave) and a ``Ring`` diff --git a/apps/ostinato/desktop/linkrunner.py b/apps/ostinato/desktop/linkrunner.py index e903099..a56be16 100644 --- a/apps/ostinato/desktop/linkrunner.py +++ b/apps/ostinato/desktop/linkrunner.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The UI's runner when the engines are another process's: an hmilink panel. desktop/runners.py's runners host the engines and the mixer in this process diff --git a/apps/ostinato/desktop/midi.py b/apps/ostinato/desktop/midi.py index 287efde..a259d12 100644 --- a/apps/ostinato/desktop/midi.py +++ b/apps/ostinato/desktop/midi.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """MIDI in on the desktop: Windows MIDI ports, read once, relayed to every engine. The browser's page relays Web MIDI to the engine workers; here one thread in diff --git a/apps/ostinato/desktop/midi_send.py b/apps/ostinato/desktop/midi_send.py index b865a2d..595c49c 100644 --- a/apps/ostinato/desktop/midi_send.py +++ b/apps/ostinato/desktop/midi_send.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """A stand-in keyboard for the checks: plays MIDI into a port, on time. python desktop/midi_send.py "Ostinato Loopback" (the control socket starts it) diff --git a/apps/ostinato/desktop/mixer.py b/apps/ostinato/desktop/mixer.py index a96c1a5..f009dcd 100644 --- a/apps/ostinato/desktop/mixer.py +++ b/apps/ostinato/desktop/mixer.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop mixer: the AudioWorklet's job, in Python, on WASAPI. It asks each engine for blocks, sums them through the shared master diff --git a/apps/ostinato/desktop/patch_synthio.py b/apps/ostinato/desktop/patch_synthio.py index 3c9141f..7d06853 100644 --- a/apps/ostinato/desktop/patch_synthio.py +++ b/apps/ostinato/desktop/patch_synthio.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Write patched/synthio.py: audiodsp's synthio.py with its block clock per thread instead of per process (the stress test's --synthio-tls experiment). diff --git a/apps/ostinato/desktop/ring.py b/apps/ostinato/desktop/ring.py index 6f0f8a7..bff8557 100644 --- a/apps/ostinato/desktop/ring.py +++ b/apps/ostinato/desktop/ring.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """One engine's audio on its way to the mixer: a single-producer, single-consumer ring of int16 stereo frames, plus a few shared counters. diff --git a/apps/ostinato/desktop/runners.py b/apps/ostinato/desktop/runners.py index aeb0c07..cb4611b 100644 --- a/apps/ostinato/desktop/runners.py +++ b/apps/ostinato/desktop/runners.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The two ways to run the engines and the mixer: processes or threads. Both give the UI the same object: diff --git a/apps/ostinato/desktop/send.py b/apps/ostinato/desktop/send.py index 6497399..40627ac 100644 --- a/apps/ostinato/desktop/send.py +++ b/apps/ostinato/desktop/send.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Play keys into a Windows MIDI port, stamping each with perf_counter_ns (QueryPerformanceCounter: the same clock in every process on the machine). diff --git a/apps/ostinato/desktop/stress.py b/apps/ostinato/desktop/stress.py index d086f59..2e8bc10 100644 --- a/apps/ostinato/desktop/stress.py +++ b/apps/ostinato/desktop/stress.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Free-threading stress test for _audiodsp and LVGL (Phase 0). python3.15t.exe -I -X gil=0 desktop/stress.py [--threads 16] [--rounds 3] [--blocks 400] diff --git a/apps/ostinato/desktop/ui/engines.py b/apps/ostinato/desktop/ui/engines.py index ff6ca69..a267fa4 100644 --- a/apps/ostinato/desktop/ui/engines.py +++ b/apps/ostinato/desktop/ui/engines.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The UI's side of the engines on the desktop: app/ui/engines.py's job, over a runner (desktop/runners.py) instead of the page's MessagePorts. diff --git a/apps/ostinato/desktop/ui/page.py b/apps/ostinato/desktop/ui/page.py index 37faf91..eccc2d8 100644 --- a/apps/ostinato/desktop/ui/page.py +++ b/apps/ostinato/desktop/ui/page.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop's page: what index.html does for the browser version. The shared UI (app/ui) talks to its page through the same names (``send``, diff --git a/apps/ostinato/desktop/window.py b/apps/ostinato/desktop/window.py index e73f6ee..c6770ad 100644 --- a/apps/ostinato/desktop/window.py +++ b/apps/ostinato/desktop/window.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop app's window: pydevices' WinDisplay, maximised, as the installed PWA is. ``PYDEVICES_MAXIMIZED=1`` makes the desktop board config open WinDisplay diff --git a/apps/ostinato/engine/engine.py b/apps/ostinato/engine/engine.py index f77a3b1..10afbdd 100644 --- a/apps/ostinato/engine/engine.py +++ b/apps/ostinato/engine/engine.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato's sound engine: runs in a worker, owns everything you hear. The page relays MIDI bytes here (``feed``); the AudioWorklet asks for audio diff --git a/apps/ostinato/ostinato/__init__.py b/apps/ostinato/ostinato/__init__.py index cb2c15f..a231eae 100644 --- a/apps/ostinato/ostinato/__init__.py +++ b/apps/ostinato/ostinato/__init__.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato on the desktop: ``python -m ostinato`` from a checkout. The browser version and the desktop app share everything in ``app/`` and studio's ``songmodel``. What diff --git a/apps/ostinato/ostinato/__main__.py b/apps/ostinato/ostinato/__main__.py index 2a41ac7..06c137c 100644 --- a/apps/ostinato/ostinato/__main__.py +++ b/apps/ostinato/ostinato/__main__.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Start Ostinato on the desktop. python -m ostinato [--engines processes|threads] [--midi NAME]... [--fresh] diff --git a/apps/ostinato/scripts/fetch_midi.py b/apps/ostinato/scripts/fetch_midi.py index 542881c..2a55314 100644 --- a/apps/ostinato/scripts/fetch_midi.py +++ b/apps/ostinato/scripts/fetch_midi.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Download midiports and adafruit_midi into vendor/lib/ for the engine, hmilink for the desktop's two processes, and the UI worker's board config into vendor/board/. diff --git a/apps/ostinato/scripts/fetch_wheels.py b/apps/ostinato/scripts/fetch_wheels.py index 0ce4c9e..3e0432e 100644 --- a/apps/ostinato/scripts/fetch_wheels.py +++ b/apps/ostinato/scripts/fetch_wheels.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Download the newest PyDevices wheels Ostinato runs on into vendor/wheels/. Ostinato always takes the latest released stack, so moving to a new release diff --git a/apps/ostinato/scripts/make_app_icon.py b/apps/ostinato/scripts/make_app_icon.py index 288445e..dacb68f 100644 --- a/apps/ostinato/scripts/make_app_icon.py +++ b/apps/ostinato/scripts/make_app_icon.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Draw Ostinato's app icons (icon-192.png, icon-512.png). ~/gh/pydevices/.toolbox/bin/python scripts/make_app_icon.py diff --git a/apps/ostinato/scripts/make_icon_font.py b/apps/ostinato/scripts/make_icon_font.py index 96bcb74..d35b38d 100644 --- a/apps/ostinato/scripts/make_icon_font.py +++ b/apps/ostinato/scripts/make_icon_font.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Build Ostinato's icon fonts from Google's Material Icons. .venv/bin/python scripts/make_icon_font.py [~/material-design-icons] diff --git a/apps/ostinato/scripts/make_soundfont_catalog.py b/apps/ostinato/scripts/make_soundfont_catalog.py index 1a13d9e..bf08813 100644 --- a/apps/ostinato/scripts/make_soundfont_catalog.py +++ b/apps/ostinato/scripts/make_soundfont_catalog.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Add the SoundFont instruments to assets/catalog.json. python scripts/make_soundfont_catalog.py [--bank FILE.sf2] diff --git a/apps/ostinato/scripts/soundhelix_project.py b/apps/ostinato/scripts/soundhelix_project.py index f45630b..29fb2a7 100644 --- a/apps/ostinato/scripts/soundhelix_project.py +++ b/apps/ostinato/scripts/soundhelix_project.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Turn a SoundHelix song (Export.java's JSON) into an Ostinato project. SoundHelix writes seven parts; Ostinato has four tracks, so parts share a diff --git a/apps/ostinato/songs/sketches/make_sketches.py b/apps/ostinato/songs/sketches/make_sketches.py index b581528..1f6e2a2 100644 --- a/apps/ostinato/songs/sketches/make_sketches.py +++ b/apps/ostinato/songs/sketches/make_sketches.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Three classic-rock sketches for Ostinato, written as song files. python songs/sketches/make_sketches.py diff --git a/apps/ostinato/songs/sketches/make_soundfont_check.py b/apps/ostinato/songs/sketches/make_soundfont_check.py index ec6d343..4d8b843 100644 --- a/apps/ostinato/songs/sketches/make_soundfont_check.py +++ b/apps/ostinato/songs/sketches/make_soundfont_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """A short song on the SoundFont instruments, written as a song file. python songs/sketches/make_soundfont_check.py diff --git a/apps/ostinato/spike/kit_ui.py b/apps/ostinato/spike/kit_ui.py index 205d1bc..5daa5a8 100644 --- a/apps/ostinato/spike/kit_ui.py +++ b/apps/ostinato/spike/kit_ui.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """P1 check: workerdisplay + appdev + display_driver + LVGL in a worker, with relayed input.""" import board_config # board_configs/workerdisplay import appdev diff --git a/apps/ostinato/spike/page.py b/apps/ostinato/spike/page.py index 7fd66f7..b9104b9 100644 --- a/apps/ostinato/spike/page.py +++ b/apps/ostinato/spike/page.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """P0 stress screen: full-window LVGL that redraws as hard as it's told to. ``window.__stress``: 0 idle, 1 every widget changes every 16 ms frame, diff --git a/apps/ostinato/spike/ui_worker.py b/apps/ostinato/spike/ui_worker.py index ad108c7..fabde9f 100644 --- a/apps/ostinato/spike/ui_worker.py +++ b/apps/ostinato/spike/ui_worker.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """P0: the stress screen with LVGL in its own worker, drawing to an OffscreenCanvas. Same widgets and the same stress levels as page.py, but nothing here runs on diff --git a/apps/ostinato/tests/test_template.py b/apps/ostinato/tests/test_template.py index ba179ff..0a761f8 100644 --- a/apps/ostinato/tests/test_template.py +++ b/apps/ostinato/tests/test_template.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import json import re import subprocess diff --git a/apps/ostinato/tools/app_play.py b/apps/ostinato/tools/app_play.py index 73468f3..5a4679a 100644 --- a/apps/ostinato/tools/app_play.py +++ b/apps/ostinato/tools/app_play.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Drive the real app in Chrome and record what it plays. .venv/bin/python tools/app_play.py --cdp http://127.0.0.1:9222 \ diff --git a/apps/ostinato/tools/check_all.py b/apps/ostinato/tools/check_all.py index b0faafb..b418dab 100644 --- a/apps/ostinato/tools/check_all.py +++ b/apps/ostinato/tools/check_all.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Every check, in one app session: boot once, run them all, time each. .venv/bin/python tools/check_all.py --cdp http://127.0.0.1:9222 [--long] [--only master,demo] diff --git a/apps/ostinato/tools/check_no_links.py b/apps/ostinato/tools/check_no_links.py index d25091e..550e884 100644 --- a/apps/ostinato/tools/check_no_links.py +++ b/apps/ostinato/tools/check_no_links.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Fail if anything in the app could navigate away from it. .venv/bin/python tools/check_no_links.py diff --git a/apps/ostinato/tools/checkkit.py b/apps/ostinato/tools/checkkit.py index 2ac0ca0..c27b96e 100644 --- a/apps/ostinato/tools/checkkit.py +++ b/apps/ostinato/tools/checkkit.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """One app session for the checks: a server, a page, the app booted once. from checkkit import Session diff --git a/apps/ostinato/tools/click_check.py b/apps/ostinato/tools/click_check.py index 58ffb83..39f6522 100644 --- a/apps/ostinato/tools/click_check.py +++ b/apps/ostinato/tools/click_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check the metronome in a tap: every beat there, on time, downbeats higher. .venv/bin/python tools/click_check.py runs/p2-metronome [--bpm 110] diff --git a/apps/ostinato/tools/demo_check.py b/apps/ostinato/tools/demo_check.py index 43925d0..c4b2dd1 100644 --- a/apps/ostinato/tools/demo_check.py +++ b/apps/ostinato/tools/demo_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The demo song: it loads on the first run and from the menu, its controls work, and it exports with every track heard. diff --git a/apps/ostinato/tools/desktop_demo_check.py b/apps/ostinato/tools/desktop_demo_check.py index d45a00d..8f7e27c 100644 --- a/apps/ostinato/tools/desktop_demo_check.py +++ b/apps/ostinato/tools/desktop_demo_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Phase 2's gate on the desktop: python -m ostinato opens maximised, loads the demo song on its first run and plays it. diff --git a/apps/ostinato/tools/desktop_input_check.py b/apps/ostinato/tools/desktop_input_check.py index fdf9905..9d6e36f 100644 --- a/apps/ostinato/tools/desktop_input_check.py +++ b/apps/ostinato/tools/desktop_input_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop app's keyboard and wheel, as the browser version has them. .venv/bin/python tools/desktop_input_check.py --desktop processes|threads|threads-ft diff --git a/apps/ostinato/tools/desktop_offline_check.py b/apps/ostinato/tools/desktop_offline_check.py index 9552774..75f7a68 100644 --- a/apps/ostinato/tools/desktop_offline_check.py +++ b/apps/ostinato/tools/desktop_offline_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop app starts with no network: the browser's offline check, for python -m ostinato. .venv/bin/python tools/desktop_offline_check.py --desktop processes|threads|threads-ft diff --git a/apps/ostinato/tools/desktop_tap_check.py b/apps/ostinato/tools/desktop_tap_check.py index 7123c9b..a47e9ca 100644 --- a/apps/ostinato/tools/desktop_tap_check.py +++ b/apps/ostinato/tools/desktop_tap_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check a desktop spike run (desktop/app.py + desktop/send.py) the way tap_check.py checks the browser: what the keyboard sent against what the mixer wrote. diff --git a/apps/ostinato/tools/desktopkit.py b/apps/ostinato/tools/desktopkit.py index 475bb3b..ded5b4a 100644 --- a/apps/ostinato/tools/desktopkit.py +++ b/apps/ostinato/tools/desktopkit.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The checks' session against the desktop app (python -m ostinato on Windows). from desktopkit import DesktopSession diff --git a/apps/ostinato/tools/files_check.py b/apps/ostinato/tools/files_check.py index 12e93d5..e7299de 100644 --- a/apps/ostinato/tools/files_check.py +++ b/apps/ostinato/tools/files_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Phase 5 (files): its gate, through the real UI in Chrome. .venv/bin/python tools/files_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/gen_boot.py b/apps/ostinato/tools/gen_boot.py index b40333d..f18e571 100644 --- a/apps/ostinato/tools/gen_boot.py +++ b/apps/ostinato/tools/gen_boot.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Write app/boot_config.js: what each worker loads. .venv/bin/python tools/gen_boot.py diff --git a/apps/ostinato/tools/gen_spike_config.py b/apps/ostinato/tools/gen_spike_config.py index be56e2b..aaf1c14 100644 --- a/apps/ostinato/tools/gen_spike_config.py +++ b/apps/ostinato/tools/gen_spike_config.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Write spike/pyscript.json and spike/spike_config.js from wheels.json and vendor/lib. .venv/bin/python tools/gen_spike_config.py diff --git a/apps/ostinato/tools/groovebox_check.py b/apps/ostinato/tools/groovebox_check.py index 8937d65..22e6b9a 100644 --- a/apps/ostinato/tools/groovebox_check.py +++ b/apps/ostinato/tools/groovebox_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Phase 4 (the groovebox): its gate, through the real UI. .venv/bin/python tools/groovebox_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/instrument_sweep_check.py b/apps/ostinato/tools/instrument_sweep_check.py index c49c15c..cedf4a6 100644 --- a/apps/ostinato/tools/instrument_sweep_check.py +++ b/apps/ostinato/tools/instrument_sweep_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Judge tools/instrument_sweep.mjs's output: pitch within 50 cents for every melodic instrument's notes, a sound for every drum pad.""" import json, math, pathlib, sys diff --git a/apps/ostinato/tools/knob_check.py b/apps/ostinato/tools/knob_check.py index 4ef7819..d20c0fe 100644 --- a/apps/ostinato/tools/knob_check.py +++ b/apps/ostinato/tools/knob_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check the knob scenario: the sound's brightness follows CC 31 (Cutoff). .venv/bin/python tools/knob_check.py runs/p3-knob diff --git a/apps/ostinato/tools/link_check.py b/apps/ostinato/tools/link_check.py index e1c66af..c451cd4 100644 --- a/apps/ostinato/tools/link_check.py +++ b/apps/ostinato/tools/link_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The window and the browser page as two panels of one device agree on the song. ~/gh/pydevices/.toolbox/bin/python tools/link_check.py [--defeat] diff --git a/apps/ostinato/tools/lookahead_check.py b/apps/ostinato/tools/lookahead_check.py index 06541a6..d952824 100644 --- a/apps/ostinato/tools/lookahead_check.py +++ b/apps/ostinato/tools/lookahead_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The engine sequences notes ahead of the render, so a paused sequencer doesn't make a note late. diff --git a/apps/ostinato/tools/master_check.py b/apps/ostinato/tools/master_check.py index 0ef6ae6..92da266 100644 --- a/apps/ostinato/tools/master_check.py +++ b/apps/ostinato/tools/master_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The master track: its effects and volume reach the sound, live and exported. .venv/bin/python tools/master_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/midi_play.py b/apps/ostinato/tools/midi_play.py index 4a68219..5a5267e 100644 --- a/apps/ostinato/tools/midi_play.py +++ b/apps/ostinato/tools/midi_play.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Play a scenario into Ostinato from the fake keyboard and record what came out. .venv/bin/python tools/midi_play.py --minutes 10 --stress 1 --out runs/p0-a diff --git a/apps/ostinato/tools/offline_check.py b/apps/ostinato/tools/offline_check.py index 0cc4089..4ea6e7c 100644 --- a/apps/ostinato/tools/offline_check.py +++ b/apps/ostinato/tools/offline_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check that the app starts offline after one visit, fonts included. .venv/bin/python tools/offline_check.py diff --git a/apps/ostinato/tools/serve.py b/apps/ostinato/tools/serve.py index a840e55..3023ccb 100644 --- a/apps/ostinato/tools/serve.py +++ b/apps/ostinato/tools/serve.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Serve Ostinato from its folder for local use and tests. .venv/bin/python tools/serve.py [--port 8765] [--isolate] diff --git a/apps/ostinato/tools/sketch_render.py b/apps/ostinato/tools/sketch_render.py index fffe37e..c2a55b6 100644 --- a/apps/ostinato/tools/sketch_render.py +++ b/apps/ostinato/tools/sketch_render.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Render every track of a song to see that each one makes sound. .venv/bin/python tools/sketch_render.py [SONG.json ...] [--seconds 20] [--wav-dir DIR] diff --git a/apps/ostinato/tools/soundfont_check.py b/apps/ostinato/tools/soundfont_check.py index 020c89a..5c34202 100644 --- a/apps/ostinato/tools/soundfont_check.py +++ b/apps/ostinato/tools/soundfont_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The SoundFont instruments in the app: songs/sketches/soundfont_check.json opens, exports with every track heard, and plays; and without a bank the tracks stay silent while the status line says so. diff --git a/apps/ostinato/tools/split_check.py b/apps/ostinato/tools/split_check.py index d9ae4ac..d0e5b3e 100644 --- a/apps/ostinato/tools/split_check.py +++ b/apps/ostinato/tools/split_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The desktop app split in two on Windows, with a browser as a second front panel. .venv/bin/python tools/split_check.py diff --git a/apps/ostinato/tools/switch_check.py b/apps/ostinato/tools/switch_check.py index fe9f2da..2ff0b1b 100644 --- a/apps/ostinato/tools/switch_check.py +++ b/apps/ostinato/tools/switch_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Changing an instrument mid-song silences only that track. .venv/bin/python tools/switch_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/tap_check.py b/apps/ostinato/tools/tap_check.py index 544d567..cfe9270 100644 --- a/apps/ostinato/tools/tap_check.py +++ b/apps/ostinato/tools/tap_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check what Ostinato played against what the keyboard sent. .venv/bin/python tools/tap_check.py runs/p0-a [--max-p99-ms 40] diff --git a/apps/ostinato/tools/timing_check.py b/apps/ostinato/tools/timing_check.py index 6f10e52..4ce8ae7 100644 --- a/apps/ostinato/tools/timing_check.py +++ b/apps/ostinato/tools/timing_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Playback timing of the demo song, Bass soloed, while its knobs, patch and instrument change. diff --git a/apps/ostinato/tools/transport_check.py b/apps/ostinato/tools/transport_check.py index ba78820..bd31fed 100644 --- a/apps/ostinato/tools/transport_check.py +++ b/apps/ostinato/tools/transport_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Check the transport scenario: CC 19 (Play) starts, CC 18 (Stop) stops. .venv/bin/python tools/transport_check.py runs/p3-transport diff --git a/apps/ostinato/tools/win_send.py b/apps/ostinato/tools/win_send.py index 7297324..98963b8 100644 --- a/apps/ostinato/tools/win_send.py +++ b/apps/ostinato/tools/win_send.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Send a scenario into a Windows MIDI port in real time (runs under python.exe). python.exe win_send.py "Ostinato Loopback" events.json sent.json diff --git a/apps/ostinato/tools/workflow2_check.py b/apps/ostinato/tools/workflow2_check.py index 8dddfd4..b4f2c83 100644 --- a/apps/ostinato/tools/workflow2_check.py +++ b/apps/ostinato/tools/workflow2_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The second UI audit's changes and the groups added with them, through the real UI. .venv/bin/python tools/workflow2_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/workflow3_check.py b/apps/ostinato/tools/workflow3_check.py index 578f0bf..7eb32ef 100644 --- a/apps/ostinato/tools/workflow3_check.py +++ b/apps/ostinato/tools/workflow3_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Frozen canvas, scene moves and names, and selecting several notes. .venv/bin/python tools/workflow3_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/ostinato/tools/workflow_check.py b/apps/ostinato/tools/workflow_check.py index 75c2086..e4f8a58 100644 --- a/apps/ostinato/tools/workflow_check.py +++ b/apps/ostinato/tools/workflow_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The workflow improvements from the UI audit, through the real UI. .venv/bin/python tools/workflow_check.py --cdp http://127.0.0.1:9222 diff --git a/apps/stompbox/__init__.py b/apps/stompbox/__init__.py index b3af3bc..8f83cd0 100644 --- a/apps/stompbox/__init__.py +++ b/apps/stompbox/__init__.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Stompbox: play a guitar or a WAV clip through an airwindows effect. The effect's knobs are on screen, built from the plug-in's own parameters. Live diff --git a/apps/stompbox/app.py b/apps/stompbox/app.py index 24328e6..e381d6e 100644 --- a/apps/stompbox/app.py +++ b/apps/stompbox/app.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The app: the engine, the screen, and what joins them. A computer can set these in its environment; a board needs none of them: diff --git a/apps/stompbox/effects.py b/apps/stompbox/effects.py index f4a21ac..959b0cd 100644 --- a/apps/stompbox/effects.py +++ b/apps/stompbox/effects.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Effect adapters: any library's effect, behind one small shape. An adapter builds the effect's audio node and describes its parameters, and diff --git a/apps/stompbox/engine.py b/apps/stompbox/engine.py index 6f9bad3..cc3fd4f 100644 --- a/apps/stompbox/engine.py +++ b/apps/stompbox/engine.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The engine: input -> effect slot -> master -> output. clip --> input mixer (loop, trim) --> effect --> master mixer diff --git a/apps/stompbox/inputs.py b/apps/stompbox/inputs.py index 366e0c7..2f0ae09 100644 --- a/apps/stompbox/inputs.py +++ b/apps/stompbox/inputs.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Input adapters. Each reports its rate, channel count and clock, and hands the engine an audiocore source at the engine's rate and channel count. diff --git a/apps/stompbox/outputs.py b/apps/stompbox/outputs.py index 6108da5..6bbf456 100644 --- a/apps/stompbox/outputs.py +++ b/apps/stompbox/outputs.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Output adapters. The output's clock drives the engine: it pulls the graph. DefaultOutput is the board's `audio_out` role, or a computer's default device diff --git a/apps/stompbox/ui.py b/apps/stompbox/ui.py index ea7b605..16fddda 100644 --- a/apps/stompbox/ui.py +++ b/apps/stompbox/ui.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The screen: an LVGL panel over the engine, laid out to whatever size the app is given. The panels flow, the knobs scroll when they don't fit, and on a small panel the whole screen does. diff --git a/apps/stompbox/wavfile.py b/apps/stompbox/wavfile.py index b8d24f8..255a622 100644 --- a/apps/stompbox/wavfile.py +++ b/apps/stompbox/wavfile.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Read a PCM WAV into an int16 array, on any Python we run on.""" import array diff --git a/apps/synthstation/audio.py b/apps/synthstation/audio.py index b961b4d..9d70946 100644 --- a/apps/synthstation/audio.py +++ b/apps/synthstation/audio.py @@ -11,7 +11,7 @@ the board config's ``audio_out``, which uses the pump too where there is one. -:meth:`Output.load` reads the pump's status block: how long a block of +`Output.load` reads the pump's status block: how long a block of audio took to render, as a share of how long it lasts, and how many times the speaker ran dry since the instrument started. """ @@ -52,8 +52,8 @@ class Output: """Plays one instrument at a time. *scope* is the app's scope: the desktop output runs its service on the - app's timers. Construct it once; :meth:`start` and :meth:`stop` swap - instruments; :meth:`close` powers the codec down. + app's timers. Construct it once; `start` and `stop` swap + instruments; `close` powers the codec down. """ def __init__(self, scope, rate=RATE, channels=CHANNELS): @@ -232,7 +232,7 @@ def load(self): ``load_pct`` is the render time of the blocks since the last call as a percentage of the time they last: over 100 can't keep up. - ``worst_pct`` is the slowest single block since :meth:`start`. + ``worst_pct`` is the slowest single block since `start`. ``underruns`` counts the calls between which the speaker ran out of audio (on a board), or blocks the output had no room for (on a desktop). Both loads are None until there's something to measure. @@ -294,5 +294,5 @@ def block_us(self): return DMA_FRAME * 1000000 // self.rate def starved_ms(self): - """On a board, the silence heard since :meth:`start`, in ms.""" + """On a board, the silence heard since `start`, in ms.""" return (self._starved - self._base) * 1000 // (self.rate * self.channels * 2) diff --git a/apps/synthstation/meters.py b/apps/synthstation/meters.py index 8542195..46b461b 100644 --- a/apps/synthstation/meters.py +++ b/apps/synthstation/meters.py @@ -41,7 +41,7 @@ def bar_height(peak): - """A peak in 0.0 to 1.0 as a bar's height in 0 to :data:`BAR_TOP`.""" + """A peak in 0.0 to 1.0 as a bar's height in 0 to `BAR_TOP`.""" if peak <= 0: return 0 db = 20 * math.log10(peak) @@ -60,7 +60,7 @@ def _legend(parent, text, x, y, w): class Meters: - """The bars and the light, drawn on *parent* at (*x*, *y*), :meth:`width` + """The bars and the light, drawn on *parent* at (*x*, *y*), `width` wide and *h* high.""" @staticmethod diff --git a/apps/synthstation/midi.py b/apps/synthstation/midi.py index 0fd80de..e0d6dd1 100644 --- a/apps/synthstation/midi.py +++ b/apps/synthstation/midi.py @@ -9,7 +9,7 @@ has, through ``midiports.windows`` or ``midiports.linux``. In a browser it's every MIDI input the browser offers, through ``midiports.webmidi``. -:class:`Parser` turns the bytes into calls on an object you pass, with no +`Parser` turns the bytes into calls on an object you pass, with no allocation per message, so a quiet keyboard costs nothing beside the audio. """ diff --git a/apps/synthstation/pads.py b/apps/synthstation/pads.py index 8ab8413..7c967ba 100644 --- a/apps/synthstation/pads.py +++ b/apps/synthstation/pads.py @@ -4,10 +4,10 @@ """Pads that play a drum kit, and pads that play notes when there's no keyboard. A kit's General MIDI notes leave gaps and repeats under a keyboard's pads, so -:data:`KIT_PADS` lays each kit out as one pad per sound. :func:`pad_notes` is +`KIT_PADS` lays each kit out as one pad per sound. `pad_notes` is what a pad note plays on an instrument. -:class:`NotePads` stands between the panel and the instrument's adapter. +`NotePads` stands between the panel and the instrument's adapter. Bank A's sixteen pads become notes: a drum machine's voices, or two octaves of a C major scale. The instrument's patches move up to bank B onwards, so they're still a press away. diff --git a/apps/synthstation/station.py b/apps/synthstation/station.py index d22061a..08f829d 100644 --- a/apps/synthstation/station.py +++ b/apps/synthstation/station.py @@ -588,7 +588,7 @@ def _govern(self, inst): keep up with doesn't slow the app, it starves the speaker: the audio runs behind for as long as the keys are held. Each sounding voice costs about the same, so from the cost of the blocks just rendered - this works out how many fit in :data:`BUDGET` of a block's time and + this works out how many fit in `BUDGET` of a block's time and sets the instrument's ``voices`` to that. The extra notes steal the oldest instead of everything going quiet. The cap is set from the first sounding voice, before a chord needs it. That voice also wakes diff --git a/apps/vamp/__init__.py b/apps/vamp/__init__.py index 53802a6..b44f029 100644 --- a/apps/vamp/__init__.py +++ b/apps/vamp/__init__.py @@ -1,13 +1,17 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Vamp: a headless groovebox. One MicroPython holds the song, runs the sequencer and makes the sound; any screen in the room is its front panel. -- :mod:`vamp.engine`: the sound, every SoundFont track on one sampler, with +- `vamp.engine`: the sound, every SoundFont track on one sampler, with notes sequenced ahead so they land on their exact frame. -- :mod:`vamp.device`: the song as an hmilink device, which Ostinato's window +- `vamp.device`: the song as an hmilink device, which Ostinato's window and the browser page drive over TCP. -- :mod:`vamp.main`: the program, on a board or desktop MicroPython. +- `vamp.main`: the program, on a board or desktop MicroPython. -The song is :mod:`songmodel.model`'s, the same one Ostinato edits. +The song is `songmodel.model`'s, the same one Ostinato edits. """ diff --git a/apps/vamp/device.py b/apps/vamp/device.py index defdf44..1388904 100644 --- a/apps/vamp/device.py +++ b/apps/vamp/device.py @@ -1,9 +1,13 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Vamp as an hmilink device: it holds the song and makes the sound, in one MicroPython process. Ostinato's split device (apps/ostinato/desktop/device.py) runs engines in processes of their own and passes messages; this one has one engine -(:mod:`vamp.engine`) and calls it. To a panel the two look the same: Ostinato's +(`vamp.engine`) and calls it. To a panel the two look the same: Ostinato's desktop window (``python -m ostinato --connect BOX:9100``) and the browser page the box serves at ``http://BOX:9100/`` edit the song with JSON Patch ops, the box numbers and applies them, and its telemetry says where the playhead is. diff --git a/apps/vamp/engine.py b/apps/vamp/engine.py index 8d27db6..9ce4071 100644 --- a/apps/vamp/engine.py +++ b/apps/vamp/engine.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Vamp's sound: every SoundFont track on one sampler, sequenced ahead. This is a groovebox's engine on a board (an ESP32-P4) and on desktop @@ -449,7 +453,7 @@ def retempo(self): # -- what plays --------------------------------------------------------------- def segments(self, t0, t1): - """What plays between ticks t0 and t1: :func:`songmodel.model.segments`.""" + """What plays between ticks t0 and t1: `songmodel.model.segments`.""" return model.segments(self.project, t0, t1) def _schedule(self, f0, f1): diff --git a/apps/vamp/main.py b/apps/vamp/main.py index 99888a6..cae3e2f 100644 --- a/apps/vamp/main.py +++ b/apps/vamp/main.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Vamp: the song and the sound on one MicroPython, the screens elsewhere. On the ESP32-P4 board, with the files from ``tools/vamp_files.py`` on it:: diff --git a/lib/appkit/__init__.py b/lib/appkit/__init__.py index 5aa2b68..22173a2 100644 --- a/lib/appkit/__init__.py +++ b/lib/appkit/__init__.py @@ -19,7 +19,7 @@ def run(name=None, folder="apps", *, data_root=None): """Run the apps in *folder*: a home screen listing them, or app *name* alone. - It ends in the main-thread loop, :func:`serve`, and returns the + It ends in the main-thread loop, `serve`, and returns the ``Launcher`` when the program quits. In a browser, where the page has the main thread, it returns the launcher at once. *data_root* moves every app's ``data_dir`` and the launcher's restart file (see diff --git a/lib/appkit/data.py b/lib/appkit/data.py index d5f40c5..acb9d8d 100644 --- a/lib/appkit/data.py +++ b/lib/appkit/data.py @@ -16,7 +16,7 @@ ============== ========================================================== ``appkit.run(data_root=...)``, the same argument to ``Shell`` or -:func:`set_root` moves it everywhere. Without one, ``APPKIT_DATA`` moves it +`set_root` moves it everywhere. Without one, ``APPKIT_DATA`` moves it on a desktop. """ @@ -80,7 +80,7 @@ def default_root(platform=None, implementation=None, getenv=None): def root(): - """The data root: what :func:`set_root` set, else :func:`default_root`.""" + """The data root: what `set_root` set, else `default_root`.""" return _root if _root is not None else default_root() diff --git a/lib/appkit/examples/appkit_simpletest/apps/__init__.py b/lib/appkit/examples/appkit_simpletest/apps/__init__.py index e69de29..b3e3bac 100644 --- a/lib/appkit/examples/appkit_simpletest/apps/__init__.py +++ b/lib/appkit/examples/appkit_simpletest/apps/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT diff --git a/lib/appkit/foreground.py b/lib/appkit/foreground.py index 44bc219..e01700f 100644 --- a/lib/appkit/foreground.py +++ b/lib/appkit/foreground.py @@ -13,9 +13,9 @@ def frame(): scope.foreground(frame) -:func:`serve` calls the app's function over and over, outside every timer +`serve` calls the app's function over and over, outside every timer callback, until the app closes or clears it. ``appkit.run()`` ends in -:func:`serve`; an app set with its own launcher calls ``appkit.serve()`` as +`serve`; an app set with its own launcher calls ``appkit.serve()`` as the last line of ``main.py``. There's one foreground function at a time, for the whole program: the last @@ -25,10 +25,10 @@ def frame(): middle of a call, so a function returns promptly once its app has closed, and an exception it raises after that is ignored. -Where nothing runs :func:`serve` (a browser page, an app started at the +Where nothing runs `serve` (a browser page, an app started at the REPL, or the REPL after Ctrl-C), a function that does one step and returns, like a frame, runs from a timer instead. One that never returns -(``steps=False``) waits until :func:`serve` runs again, so it can't take the +(``steps=False``) waits until `serve` runs again, so it can't take the REPL: ``scope.foreground()`` returns False, so the app can say so. Ctrl-C in the loop goes to the REPL with every timer still running. One @@ -73,7 +73,7 @@ def set_function(scope, fn, steps=True): *scope*'s. This is ``scope.foreground(fn, steps=steps)``. Returns True when *fn* runs now (the loop is running, or *fn* steps from - a timer), False when it waits for :func:`serve`. + a timer), False when it waits for `serve`. """ global _fn, _owner, _steps, _token if fn is None: @@ -124,7 +124,7 @@ def after_calls(fn, *args): def _call(fn): """Call *fn* once. An error in it is reported, and clears the function if it is still the foreground one. The caller keeps no reference to - *fn* while the call runs, and drops its own before :func:`_run_after`.""" + *fn* while the call runs, and drops its own before `_run_after`.""" global _calls _calls += 1 try: @@ -139,7 +139,7 @@ def _call(fn): def _run_after(): - """Run what :func:`after_calls` queued, now that the call is over.""" + """Run what `after_calls` queued, now that the call is over.""" while _calls == 0 and _after_calls: fn, args = _after_calls.pop(0) fn(*args) @@ -156,12 +156,12 @@ def owner(): def serving(): - """True while :func:`serve` is running the loop.""" + """True while `serve` is running the loop.""" return _serving def stop(): - """Have :func:`serve` return after the call it's in.""" + """Have `serve` return after the call it's in.""" global _stop _stop = True diff --git a/lib/appkit/index.py b/lib/appkit/index.py index a60e50b..a4bd40e 100644 --- a/lib/appkit/index.py +++ b/lib/appkit/index.py @@ -14,7 +14,7 @@ import os include("/path/to/appkit", apps=os.path.abspath("apps")) -or call :func:`freeze_apps` yourself. From a shell:: +or call `freeze_apps` yourself. From a shell:: python -m appkit.index apps -o build/_appkit_apps.py @@ -52,7 +52,7 @@ def collect(folder): def render(apps, source=None): - """The ``_appkit_apps`` module's text for *apps* (see :func:`collect`).""" + """The ``_appkit_apps`` module's text for *apps* (see `collect`).""" lines = [ "# Generated by appkit.index%s: do not edit." % (" from " + source if source else ""), "# The package the apps are frozen in, and (entry folder, app.json", diff --git a/lib/appkit/launcher.py b/lib/appkit/launcher.py index 99aa3b9..d723eaa 100644 --- a/lib/appkit/launcher.py +++ b/lib/appkit/launcher.py @@ -21,7 +21,7 @@ # later, from a button, a gesture or the REPL: launcher.switch("steps") -Each switch closes the old app's :class:`~appkit.scope.AppScope` (its +Each switch closes the old app's `AppScope ` (its timers, subscriptions, devices, LVGL objects and modules) and starts the new one in the same process. After the close it checks the heap: if free memory or the largest free block has fallen below its threshold, or the old @@ -46,10 +46,10 @@ The file is in the data root (see ``appkit.data``), so it moves with ``data_root``. ``PYDEVICES_NEXT_APP`` overrides its path. -A plain script (:meth:`Launcher.add_script`, or ``"script"`` in an +A plain script (`Launcher.add_script`, or ``"script"`` in an ``app.json``) builds its own ``App`` when imported, so it never runs in the launcher's process: the launcher restarts the program into it, and -:func:`run_pending_script`, first thing in ``main.py``, runs it and restarts +`run_pending_script`, first thing in ``main.py``, runs it and restarts back to the home screen when it quits. See appkit's README.md. @@ -280,7 +280,7 @@ def take_next(): def read_next(clear=False): """The ``(name, count, path)`` a restart left for this boot, or None. - Cleared once read when *clear*; left for :meth:`Launcher.boot` otherwise. + Cleared once read when *clear*; left for `Launcher.boot` otherwise. """ h = host() text = None @@ -507,8 +507,8 @@ class Launcher: """Owns the long-lived App, starts apps by name and switches between them. *apps* maps a name to the module holding that app's ``main(scope)`` - (``{"steps": "apps.steps"}``); :meth:`add` registers more. *home* is - started by :meth:`boot` when no restart is pending, and after an app + (``{"steps": "apps.steps"}``); `add` registers more. *home* is + started by `boot` when no restart is pending, and after an app fails to start. *min_free* and *min_block* are the heap thresholds in bytes, checked @@ -598,7 +598,7 @@ def scan(self, folder="apps"): filesystem). The folder's parent goes on the end of ``sys.path``. An ``app.json`` with ``"script": ""`` is a plain script (see - :meth:`add_script`), looked for in its own folder first. Where + `add_script`), looked for in its own folder first. Where *folder* doesn't exist (a build with the apps frozen in), the same entries come from the generated module ``_appkit_apps``, with the apps frozen as the package ``apps`` (see ``appkit.index``). Returns @@ -700,7 +700,7 @@ def boot(self, default=None): return name def start(self, name): - """Start app *name*; the same as :meth:`switch`.""" + """Start app *name*; the same as `switch`.""" self.switch(name) def request(self, name): diff --git a/lib/appkit/scope.py b/lib/appkit/scope.py index bf2a386..a1555b1 100644 --- a/lib/appkit/scope.py +++ b/lib/appkit/scope.py @@ -8,7 +8,7 @@ references: a timer or event callback pointing at its functions, a device it registered, an LVGL screen, its entry in ``sys.modules``, anything it parked in another module's global. An ``AppScope`` records what the app makes -through it, and :meth:`AppScope.close` undoes all of it. +through it, and `AppScope.close` undoes all of it. :: @@ -469,7 +469,7 @@ def _close_lvgl(self, problems): def close(self): """Release everything this app made; return what couldn't be released. - In order: its foreground function (see :meth:`foreground`), the + In order: its foreground function (see `foreground`), the ``on_close`` hooks, the worker jobs it queued, timers, event subscriptions, devices, adopted objects, LVGL objects, then the app's modules leave ``sys.modules`` and the heap is collected. Each step runs even when diff --git a/lib/appkit/shell.py b/lib/appkit/shell.py index 4dc6140..0bb3ffa 100644 --- a/lib/appkit/shell.py +++ b/lib/appkit/shell.py @@ -6,9 +6,9 @@ This one is the plainest: an app gets the whole screen, and a Home button sits in the corner when there's a home screen to go back to. A board that wants a header, its own look or its own buttons subclasses it: override -:meth:`Shell.make_root` to give apps a smaller area, -:meth:`Shell.make_home_button` for its own Home button, or -:meth:`Shell.prepare` to hand them other services. +`Shell.make_root` to give apps a smaller area, +`Shell.make_home_button` for its own Home button, or +`Shell.prepare` to hand them other services. """ import lvgl as lv @@ -35,7 +35,7 @@ def make_home_button(self): Override it for a button of your own look. Put it on ``lv.layer_top()`` or ``lv.layer_sys()``, so it shows over every - app's screen; :meth:`prepare` hides it on the home screen itself. + app's screen; `prepare` hides it on the home screen itself. """ launcher = self.launcher b = lv.button(lv.layer_top()) diff --git a/lib/appkit/worker.py b/lib/appkit/worker.py index 6a0d8bd..8cb4e77 100644 --- a/lib/appkit/worker.py +++ b/lib/appkit/worker.py @@ -30,7 +30,7 @@ def done(value, error): the call. Where there are no threads (the browser, CircuitPython on a board, -MicroPython's Windows port) a :class:`TimerWorker` runs each job on a timer +MicroPython's Windows port) a `TimerWorker` runs each job on a timer instead, one per tick, so the screen still gets a turn between jobs. """ @@ -318,8 +318,8 @@ def threads(): def shared(): - """The program's one worker, started on first use: a :class:`Worker` - where there are threads, a :class:`TimerWorker` where there aren't.""" + """The program's one worker, started on first use: a `Worker` + where there are threads, a `TimerWorker` where there aren't.""" global _shared if _shared is None: _shared = Worker() if threads() else TimerWorker() diff --git a/lib/controlkit/examples/controls_demo/apps/__init__.py b/lib/controlkit/examples/controls_demo/apps/__init__.py index e69de29..b3e3bac 100644 --- a/lib/controlkit/examples/controls_demo/apps/__init__.py +++ b/lib/controlkit/examples/controls_demo/apps/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT diff --git a/lib/sayso/__init__.py b/lib/sayso/__init__.py index 28513e2..88e1166 100644 --- a/lib/sayso/__init__.py +++ b/lib/sayso/__init__.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + from . import objects as _objects from .auth import ( AuthorizationCodeAuth, diff --git a/lib/sayso/auth.py b/lib/sayso/auth.py index 5bc212e..1b4147e 100644 --- a/lib/sayso/auth.py +++ b/lib/sayso/auth.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import time from .transport import TransportError, post_form_json, query_string, unquote_plus diff --git a/lib/sayso/client.py b/lib/sayso/client.py index a5679d0..c50c1f1 100644 --- a/lib/sayso/client.py +++ b/lib/sayso/client.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + from .auth import ClientCredentialsAuth from .objects import ( Album, diff --git a/lib/sayso/keepalive.py b/lib/sayso/keepalive.py index 6ac06fe..d65e714 100644 --- a/lib/sayso/keepalive.py +++ b/lib/sayso/keepalive.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """A small HTTP/1.1 client that keeps its TLS connections open. MicroPython's ``requests`` opens a new TCP connection and does a new TLS diff --git a/lib/sayso/local.py b/lib/sayso/local.py index 954eb2f..9d1b0b9 100644 --- a/lib/sayso/local.py +++ b/lib/sayso/local.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Optional same-interpreter adapter for an earful Connect device. Importing this module never starts earful and never reads credentials. A diff --git a/lib/sayso/object_specs.py b/lib/sayso/object_specs.py index 91fe0a7..944727c 100644 --- a/lib/sayso/object_specs.py +++ b/lib/sayso/object_specs.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + SPOTIFY_OBJECT_SPECS = ( { "name": "Paging", diff --git a/lib/sayso/objects.py b/lib/sayso/objects.py index 78c09ee..ac3b393 100644 --- a/lib/sayso/objects.py +++ b/lib/sayso/objects.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + from .object_specs import SPOTIFY_OBJECT_SPECS from .transport import TransportError diff --git a/lib/sayso/schema_coverage.py b/lib/sayso/schema_coverage.py index cc70a41..60aa35b 100644 --- a/lib/sayso/schema_coverage.py +++ b/lib/sayso/schema_coverage.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + from .object_specs import SPOTIFY_OBJECT_SPECS diff --git a/lib/sayso/transport.py b/lib/sayso/transport.py index 3046e58..07b4273 100644 --- a/lib/sayso/transport.py +++ b/lib/sayso/transport.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + API_BASE_URL = "https://api.spotify.com/v1" # Resolved once here, never inside a function. On MicroPython json is an diff --git a/lib/songmodel/__init__.py b/lib/songmodel/__init__.py index af3c6b5..75a409b 100644 --- a/lib/songmodel/__init__.py +++ b/lib/songmodel/__init__.py @@ -1,10 +1,14 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The song both of studio's groovebox applications edit, and what carries it to a front panel. -- :mod:`songmodel.model`: the project, plain JSON data changed only by ops that +- `songmodel.model`: the project, plain JSON data changed only by ops that every side applies the same way. Imports on CPython, Pyodide and MicroPython. -- :mod:`songmodel.link`: the project as an ``hmilink`` model, so any screen can +- `songmodel.link`: the project as an ``hmilink`` model, so any screen can be a front panel (needs ``hmilink``, from pydevices). - ``page.html``: the browser front panel a device serves on its hmilink port. diff --git a/lib/songmodel/link.py b/lib/songmodel/link.py index e1d31b6..50f0ca5 100644 --- a/lib/songmodel/link.py +++ b/lib/songmodel/link.py @@ -1,6 +1,10 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Ostinato's song as an hmilink model: any screen as the groovebox's front panel. -The project is already plain data changed by ops (:mod:`model`), so this is +The project is already plain data changed by ops (`model`), so this is a thin coat. The device holds the song; every panel (the desktop UI, the browser page the device serves, later the LCD-7) sends JSON Patch ops with JSON Pointer paths, and the device numbers them and sends them to every @@ -24,7 +28,7 @@ - ``remove /tracks/0/patterns/0/notes/3`` Ostinato's own ops find a track by id (``["track", 7]``), so they survive a -reorder; a pointer finds it by index. :func:`to_pointer` turns one into the +reorder; a pointer finds it by index. `to_pointer` turns one into the other on the panel that sends it, against that panel's copy. Imports on CPython and MicroPython: nothing here touches a file, a thread or @@ -76,7 +80,7 @@ def to_pointer(project, op): def from_pointer(op): - """A JSON Patch op as the Ostinato op :func:`model.apply` takes.""" + """A JSON Patch op as the Ostinato op `model.apply` takes.""" keys = hmilink.split_pointer(op["path"]) if not keys: raise ValueError("replace the whole song with the load command") diff --git a/lib/songmodel/model.py b/lib/songmodel/model.py index c818e09..2764fb9 100644 --- a/lib/songmodel/model.py +++ b/lib/songmodel/model.py @@ -1,10 +1,14 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The song: plain data, shared by every panel and the engine that plays it. Ostinato's UI and engines use it, and so does Vamp. A project is dicts, lists, numbers and strings only, so it saves as JSON and crosses between workers as a message. Neither side edits it in place behind -the other's back: every change is an op (:func:`apply`), applied locally and +the other's back: every change is an op (`apply`), applied locally and sent to the other side, which applies the same op to its copy. Times are in ticks, PPQ to a quarter note. A step on the grid is a diff --git a/lib/spectrum/analyzer.py b/lib/spectrum/analyzer.py index e28d167..27cf457 100644 --- a/lib/spectrum/analyzer.py +++ b/lib/spectrum/analyzer.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """ analyzer.py -- the spectrum analyzer as a widget any app can put on its screen. diff --git a/lib/spectrum/fake_music.py b/lib/spectrum/fake_music.py index 7e9e5d8..1357b59 100644 --- a/lib/spectrum/fake_music.py +++ b/lib/spectrum/fake_music.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """ fake_music.py -- band levels that move like a band playing, with no audio. diff --git a/lib/spectrum/pump_levels.py b/lib/spectrum/pump_levels.py index 275a179..cc4bce5 100644 --- a/lib/spectrum/pump_levels.py +++ b/lib/spectrum/pump_levels.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """ pump_levels.py -- band levels from audiodsp's audiometer, fed in C. diff --git a/lib/spectrum/spectrum_view.py b/lib/spectrum/spectrum_view.py index 76780f0..f280c07 100644 --- a/lib/spectrum/spectrum_view.py +++ b/lib/spectrum/spectrum_view.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """ spectrum_view.py -- draws a log-frequency spectrum analyzer with pygraphics. diff --git a/lib/touchkit/cam.py b/lib/touchkit/cam.py index a551b6f..2f135a6 100644 --- a/lib/touchkit/cam.py +++ b/lib/touchkit/cam.py @@ -37,7 +37,7 @@ class NoCameraPort(OSError): def no_camera_text(err, app): - """What *app* shows when :func:`open_camera` raised *err*.""" + """What *app* shows when `open_camera` raised *err*.""" if isinstance(err, NoCameraPort): return "No camera: %s\n\n%s needs the board's CSI camera, which this computer doesn't have." % (err, app) return "No camera: %s\n\nConnect one to the CSI port, then open %s again." % (err, app) @@ -98,7 +98,7 @@ def photos_dir(scope): def mount_sd(readonly=True): """Mount the SD card at ``/sd``: the card object (unmount it with - :func:`unmount_sd`), True when someone else has it mounted, or False + `unmount_sd`), True when someone else has it mounted, or False without a card.""" try: os.stat("/sd") diff --git a/lib/touchkit/net.py b/lib/touchkit/net.py index c9323eb..9e5d41c 100644 --- a/lib/touchkit/net.py +++ b/lib/touchkit/net.py @@ -4,12 +4,12 @@ """touchkit.net -- Wi-Fi for the board's apps: when it's on is the set's call. A desktop has no ``wifi`` or ``network`` module and is on its network -already: :func:`up` is then True once the computer has an address, and -:func:`ip` and :func:`hostname` are the computer's own. +already: `up` is then True once the computer has an address, and +`ip` and `hostname` are the computer's own. -The board joins a known network at boot (:func:`boot`) and stays joined +The board joins a known network at boot (`boot`) and stays joined while apps come and go, like the display. An app that needs the network -calls :func:`up` from a timer until it says True. The hostname comes from +calls `up` from a timer until it says True. The hostname comes from ``settings.json`` (see ``touchkit.settings``). """ diff --git a/lib/touchkit/shell.py b/lib/touchkit/shell.py index 7d24637..c98c2f5 100644 --- a/lib/touchkit/shell.py +++ b/lib/touchkit/shell.py @@ -3,12 +3,12 @@ # SPDX-License-Identifier: MIT """touchkit.shell -- the board's chrome and launcher. -* :class:`TouchShell` gives every app but Home a header: its title, and a +* `TouchShell` gives every app but Home a header: its title, and a status line at the right (``scope.status``, see ``touchkit.ui.status``). ``scope.root`` is the area below it. Its Home button sits at the header's left, on LVGL's system layer, so it never presses an app's own widgets; the board's BOOT button goes home too, through a pin interrupt. -* :class:`TouchLauncher` keeps a short history of switches for System, +* `TouchLauncher` keeps a short history of switches for System, and takes the USB sound card off when the next app isn't one that plays through it, so the PC goes back to its own speakers. diff --git a/lib/touchkit/ui.py b/lib/touchkit/ui.py index 1a0f114..f03c78f 100644 --- a/lib/touchkit/ui.py +++ b/lib/touchkit/ui.py @@ -4,7 +4,7 @@ """touchkit.ui -- the board's look, and the few widgets its apps build from. The colours and fonts are here so an app can use them without the shell; -:func:`apply_theme` puts them on the display, once, at boot. +`apply_theme` puts them on the display, once, at boot. """ import lvgl as lv diff --git a/tests/appkit/sample_named/apps/__init__.py b/tests/appkit/sample_named/apps/__init__.py index 121e297..4f15319 100644 --- a/tests/appkit/sample_named/apps/__init__.py +++ b/tests/appkit/sample_named/apps/__init__.py @@ -1 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + # The apps folder is a package: appkit imports each app in it as apps.. diff --git a/tests/appkit/sample_set/apps/__init__.py b/tests/appkit/sample_set/apps/__init__.py index 121e297..4f15319 100644 --- a/tests/appkit/sample_set/apps/__init__.py +++ b/tests/appkit/sample_set/apps/__init__.py @@ -1 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + # The apps folder is a package: appkit imports each app in it as apps.. diff --git a/tests/appkit/sample_set/autopilot.py b/tests/appkit/sample_set/autopilot.py index 2a436ff..e7764d2 100644 --- a/tests/appkit/sample_set/autopilot.py +++ b/tests/appkit/sample_set/autopilot.py @@ -104,7 +104,7 @@ def _button(lv, page, text): def watch(): - """Wait for the launcher ``appkit.run()`` makes, then :func:`drive` it. + """Wait for the launcher ``appkit.run()`` makes, then `drive` it. ``run()`` doesn't return until the program quits, so the autopilot finds the launcher through the scope of the app it started. diff --git a/tests/sayso/test_client.py b/tests/sayso/test_client.py index 92298b8..5fb65d5 100644 --- a/tests/sayso/test_client.py +++ b/tests/sayso/test_client.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import unittest from sayso import SpotifyClient diff --git a/tests/sayso/test_live.py b/tests/sayso/test_live.py index 3862a32..d7a8d18 100644 --- a/tests/sayso/test_live.py +++ b/tests/sayso/test_live.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import unittest from sayso import config_available diff --git a/tests/sayso/test_objects.py b/tests/sayso/test_objects.py index b8fca4a..28dcb9b 100644 --- a/tests/sayso/test_objects.py +++ b/tests/sayso/test_objects.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import unittest from sayso import ( diff --git a/tests/sayso/test_transport.py b/tests/sayso/test_transport.py index 5e00723..ed30a99 100644 --- a/tests/sayso/test_transport.py +++ b/tests/sayso/test_transport.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import unittest from sayso import transport diff --git a/tests/vamp_check.py b/tests/vamp_check.py index 1478b82..6b7bdf2 100644 --- a/tests/vamp_check.py +++ b/tests/vamp_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Checks Vamp's engine (apps/vamp/engine.py) offline, on MicroPython and CPython. Pulls the engine's output itself, so it owns the clock, and listens to what diff --git a/tools/sayso/configure.py b/tools/sayso/configure.py index b7e5ac0..43a506a 100644 --- a/tools/sayso/configure.py +++ b/tools/sayso/configure.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import getpass import os import sys diff --git a/tools/sayso/endpoint_coverage.py b/tools/sayso/endpoint_coverage.py index 2d25055..44e3734 100644 --- a/tools/sayso/endpoint_coverage.py +++ b/tools/sayso/endpoint_coverage.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import argparse import ast import os diff --git a/tools/sayso/generate_object_specs.py b/tools/sayso/generate_object_specs.py index fab6e27..38df48b 100644 --- a/tools/sayso/generate_object_specs.py +++ b/tools/sayso/generate_object_specs.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import argparse import json import pprint diff --git a/tools/sayso/sayso_album_discovery.py b/tools/sayso/sayso_album_discovery.py index ea65224..94fcab0 100644 --- a/tools/sayso/sayso_album_discovery.py +++ b/tools/sayso/sayso_album_discovery.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/sayso/sayso_playback.py b/tools/sayso/sayso_playback.py index 5f9e42b..87535e5 100644 --- a/tools/sayso/sayso_playback.py +++ b/tools/sayso/sayso_playback.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys import termios diff --git a/tools/sayso/sayso_playlist_discovery.py b/tools/sayso/sayso_playlist_discovery.py index 5aab1c8..3aa581b 100644 --- a/tools/sayso/sayso_playlist_discovery.py +++ b/tools/sayso/sayso_playlist_discovery.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/sayso/sayso_saved_discovery.py b/tools/sayso/sayso_saved_discovery.py index d2c4c00..a59d561 100644 --- a/tools/sayso/sayso_saved_discovery.py +++ b/tools/sayso/sayso_saved_discovery.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/sayso/sayso_simpletest.py b/tools/sayso/sayso_simpletest.py index a32f753..74b105e 100644 --- a/tools/sayso/sayso_simpletest.py +++ b/tools/sayso/sayso_simpletest.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/sayso/smoke_client_credentials.py b/tools/sayso/smoke_client_credentials.py index 47fcf8a..382d3e9 100644 --- a/tools/sayso/smoke_client_credentials.py +++ b/tools/sayso/smoke_client_credentials.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/sayso/smoke_oauth.py b/tools/sayso/smoke_oauth.py index 3acb8cf..e59bab8 100644 --- a/tools/sayso/smoke_oauth.py +++ b/tools/sayso/smoke_oauth.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + import os import sys diff --git a/tools/stompbox/c_path.py b/tools/stompbox/c_path.py index 901f09a..4a98691 100644 --- a/tools/stompbox/c_path.py +++ b/tools/stompbox/c_path.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Live input to output entirely in C, on the pump's thread, and what it costs. The microphone is `_audioif.Input` (WASAPI capture) and the speaker is an diff --git a/tools/stompbox/chain_depth.py b/tools/stompbox/chain_depth.py index 9dfa045..169c9aa 100644 --- a/tools/stompbox/chain_depth.py +++ b/tools/stompbox/chain_depth.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Live input through the chain to the default output, and how much audio each stage holds while it runs, in milliseconds. It measures what the software path adds; the converters and the cable are not in it. diff --git a/tools/stompbox/drift_path.py b/tools/stompbox/drift_path.py index 4a847e9..304ef83 100644 --- a/tools/stompbox/drift_path.py +++ b/tools/stompbox/drift_path.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """The live path between two clocks, and whether its ring holds. The microphone (`_audioif.Input` with a Ring) writes on its own thread at diff --git a/tools/stompbox/drive.py b/tools/stompbox/drive.py index 73d1140..f9fb871 100644 --- a/tools/stompbox/drive.py +++ b/tools/stompbox/drive.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Run Stompbox on a computer and script it, to prove it from a recording. micropython -X heapsize=128M tools/stompbox/drive.py [--clips DIR] diff --git a/tools/stompbox/endpoints.py b/tools/stompbox/endpoints.py index f457ee4..c4e1646 100644 --- a/tools/stompbox/endpoints.py +++ b/tools/stompbox/endpoints.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Open given WASAPI endpoints instead of the defaults, for this process only. audiodev's win_audio always opens the default endpoint. use(out_id, in_id) diff --git a/tools/stompbox/roundtrip.py b/tools/stompbox/roundtrip.py index 8cce4c8..699dea9 100644 --- a/tools/stompbox/roundtrip.py +++ b/tools/stompbox/roundtrip.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Input-to-output latency through the chain, with a cable from an output into an input. diff --git a/tools/vamp_files.py b/tools/vamp_files.py index 5cc0739..ce15744 100644 --- a/tools/vamp_files.py +++ b/tools/vamp_files.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Puts Vamp on a board through mpftp: vamp and songmodel in /lib, hmilink beside them, and a song to start with. diff --git a/tools/vamp_link_check.py b/tools/vamp_link_check.py index b0da143..d7ecca2 100644 --- a/tools/vamp_link_check.py +++ b/tools/vamp_link_check.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Brad Barnett +# +# SPDX-License-Identifier: MIT + """Drives a running Vamp (apps/vamp/main.py) over hmilink, as Ostinato's window does, and checks the box keeps the song and the time.