Skip to content

Release v0.8.0

Latest

Choose a tag to compare

@github-actions github-actions released this 14 Nov 02:27

Release Notes v0.8.0

Released: 2026-11-14

blocksd gains custom LittleFoot program loading with a bundled, offline compiler, plus preview-level Windows support for ROLI Piano M and other Blocks hardware. Touch velocity, button identity, and battery reporting are corrected against the ROLI SDK, and distribution moves from pure-Python wheels to platform wheels that carry the native compiler.

🌟 Highlights

✨ Load custom LittleFoot programs onto devices

A new blocksd program command group compiles .littlefoot source and uploads it to a connected device. blocksd program load FILE resolves #include directives from the source directory or --include arguments, blocksd program status reports the active program and upload state, and blocksd program reset restores the device's saved program by cycling API mode. Implementation lives in src/blocksd/littlefoot/compiler.py, source.py, and upload.py, with CLI wiring in src/blocksd/cli/program.py.

🔧 Bundled offline compiler, no filesystem access

The compiler binary is built from vendored JUCE 7.0.12 juce_core and the ROLI SDK (both ISC licensed) by native/littlefoot/CMakeLists.txt and native/littlefoot/main.cpp. The build patches resolveIncludePath() to a stub, so the compiler can never read the daemon's filesystem: all includes are supplied as a virtual file map by Python. Source archives are checksummed in native/littlefoot/README.md for reproducible builds.

🪟 Windows preview support

blocksd now runs on Windows in foreground mode. src/blocksd/topology/detector.py recognizes ROLI Piano M and LUMI alongside BLOCK, and strips the per-direction enumeration index that WinMM appends to input and output port names so pairs resolve correctly. CLI commands reach the daemon over the local WebSocket endpoint (ws://127.0.0.1:9010/ws) instead of a Unix socket.

🐛 Correct touch velocity, battery, and button identity

Velocity fields now decode as two's complement signed 8-bit via _bipolar8() in src/blocksd/device/models.py, matching the SDK's IntegerWithBitSize::toBipolarFloat. Battery is scaled from the 5-bit topology field to a real percentage in src/blocksd/topology/device_group.py, so a full battery reports 100 instead of 31. Button events carry button_id and a human-readable button function name resolved from a per-block-type table in the new src/blocksd/device/registry.py.

👷 Platform wheels with the native compiler

hatch_build.py runs CMake during wheel builds and installs the compiler into src/blocksd/littlefoot/_native/, marking the wheel non-pure with a py3-none-{platform} tag. .github/workflows/wheels.yml builds cp313 wheels across Linux x86_64/ARM, macOS Intel/Apple Silicon, and Windows x86_64 with cibuildwheel 4.3.5.

🤖 LittleFoot Programs

  • Six bundled library files ship under src/blocksd/littlefoot/library/: AppleControlSurface.littlefoot, NoteGridBase.littlefoot, ClusterSync.littlefoot, ConfigIds.littlefoot, RPNHandler.littlefoot, and Scale.littlefoot. They resolve by basename without being passed explicitly.
  • New API messages program_load, program_status, and program_reset in src/blocksd/api/programs.py and src/blocksd/api/commands.py. program_load accepts either source plus an includes map or a base64 code payload, and runs compilation on a thread so MIDI keepalive is never delayed.
  • Program state is reported as idle, compiling, uploading, unresponsive, uploaded, or ready, with code_size and heap_size so clients can distinguish transport acknowledgement from renderer readiness.
  • Memory budgets are validated before upload: program code, heap, and global stack use must fit the device's reported capacity.
  • Source is capped at 256 KiB combined, and compilation times out after 15 seconds.
  • While a custom program owns a device, blocksd's LED calls (set_led_data, set_key_led_data) are rejected rather than silently fighting the program for the display.
  • Dashboard metadata defaults accept int, float, bool, and midiNote only; other declared types are rejected by the compiler.
  • User guide at docs/guide/programs.md.

🌐 Dashboard

  • New ProgramPanel.tsx with a .littlefoot file picker, an extra-includes section, a live state badge, transfer size details, and a restore button that appears only when a custom program is loaded. Markup uses aria-live and aria-describedby so status transitions are announced.
  • useDeviceProgram.ts polls program_status once per second while connected and tracks a revision counter so responses from a previous device or connection are discarded.
  • web/src/lib/program.ts validates file extension, rejects duplicate filenames, and enforces the same 256 KiB limit as the daemon before anything is sent.
  • useDeviceSettings.ts listens for the new config_reset event and clears cached values, schema, pending writes, and errors, so stale controls do not linger after a device starts a new configuration list.
  • Decorative taglines, eyebrow labels, and the instrument tip box were removed from Studio.tsx, with the matching .instrument-tip rules dropped from web/src/theme/hardware.css.
  • Settings filtering falls back to the all group when the previously active group disappears after a reset, and the empty state explains why no settings are shown.

🔌 API and Protocol

  • touch events gain a timestamp field (device clock in milliseconds, comparable only within a single device's stream).
  • button events gain button_id, button (SDK function name such as mode, up, play_or_pause), and timestamp. Function tables cover LUMI Keys, Lightpad, Seaboard, Loop, Live, Dev Controller, and Touch blocks.
  • config_get accepts an optional refresh: true flag that triggers a factory-sync read before responding, backed by the new build_config_request_factory_sync() in src/blocksd/protocol/builder.py. This requests current settings and ranges; it is not a factory reset.
  • New config_reset event broadcasts when a device begins a new active configuration list, telling clients their cached settings are invalid.
  • Stalled event subscribers are now disconnected instead of being silently unsubscribed. When a bounded queue overflows, src/blocksd/api/events.py shuts the queue down and records it in a weak set; the transport sends {"type": "error", "message": "event queue overflowed; reconnect and subscribe again"} and closes the connection (WebSocket clients get RFC 6455 close status 1008). Resubscribe attempts on a shut-down queue are rejected.
  • When _handle_config() in src/blocksd/protocol/decoder.py encounters an unknown config command, it now returns False and decode_packet() breaks out of the decode loop, abandoning the remainder of the packet. Unknown config bodies have no length prefix, so the next message boundary is unknowable; previously the decoder returned True and kept parsing at an unknown offset, silently desynchronizing and corrupting every following message in the packet.
  • Static file serving in src/blocksd/api/http.py rejects backslashes, drive letters, and UNC paths before resolution, and uses is_relative_to() for containment instead of string prefix matching.
  • Device names from topology are partitioned at the first null byte and validated as printable and non-empty before being stored.
  • Firmware version strings get the same printable check and report null when the device sends non-text bytes, as a Lightpad Block M on firmware 1.1.0 does.

🪟 Windows Support

  • Run the daemon with blocksd run --foreground. There is no background service on Windows: blocksd install and blocksd uninstall raise OSError("LaunchAgents are unavailable on Windows.").
  • api_enabled defaults to false on Windows (Field(default_factory=lambda: sys.platform != "win32") in src/blocksd/config/schema.py). The Unix socket API is unavailable; src/blocksd/api/server.py raises if it is requested. Setting api_enabled = true or api_socket on Windows fails at startup with a plain one-line error from src/blocksd/cli/app.py.
  • CLI commands require web_enabled = true. src/blocksd/cli/client.py builds the endpoint from web_host and web_port, normalizing 0.0.0.0 to 127.0.0.1 and :: to ::1, bracketing IPv6 literals, and ignoring proxy environment variables.
  • Configuration and presets live under %APPDATA%\blocksd\ via _windows_config_directory() in src/blocksd/paths.py.
  • Preset reads and writes pin UTF-8 explicitly in src/blocksd/config/presets.py so Windows code pages cannot corrupt non-ASCII device or preset names.
  • systemd notifications in src/blocksd/sdnotify.py short-circuit on Windows, and src/blocksd/daemon.py falls back to signal.signal() with loop.call_soon_threadsafe() since add_signal_handler() is unavailable, restoring original handlers on shutdown.
  • Windows holds exclusive MIDI port locks: close ROLI Dashboard and any DAW owning the device before starting blocksd. Physical Piano M renderer execution, replug, and DAW coexistence are still untested, which is why this is labeled preview.

⚙️ Build, Packaging, and CI

  • pyproject.toml adds websockets>=17.2, the Operating System :: Microsoft :: Windows classifier, the custom Hatchling build hook, /native and hatch_build.py in the sdist, and uv cache keys covering native/**/*.
  • scripts/check_distribution.py compiles a one-line LittleFoot program and asserts the output exceeds 10 bytes, so a broken or missing compiler bundle fails the distribution check rather than shipping.
  • The Homebrew formula template packaging/homebrew/blocksd.rb.in runs the same compiler smoke test during brew test.
  • AUR PKGBUILD moves from arch=('any') to arch=('x86_64' 'aarch64') and adds cmake plus python-websockets>=17.2.
  • Release publishing adopts shared-workflows v2 with artifact-mode: files, keeping individual asset download URLs instead of a single archive.
  • Dependency bumps across the JavaScript (14 updates) and Python (8 updates) groups, plus astral-sh/setup-uv, pypa/gh-action-pypi-publish, actions/setup-node, and actions/upload-artifact.
  • New test suites: tests/littlefoot/test_compiler.py, tests/topology/test_program_loading.py, tests/cli/test_program.py, tests/test_config_sync.py, tests/windows/test_client.py, and tests/windows/test_runtime.py.

⚠️ Breaking Changes

  • Battery is now a percentage. The battery value in logs, CLI output, API payloads, and the dashboard is scaled round(level * 100 / 31). A full battery reports 100 where v0.7.0 reported 31. Remove any client-side rescaling of this field.
  • Touch velocity sign and scale changed. vx, vy, and vz were decoded as offset binary around 128 and are now two's complement signed 8-bit over [-1.0, 127/128]. Values that previously came through inverted or offset will differ; recalibrate any thresholds derived from v0.7.0 output.
  • Overflowing event subscribers are disconnected. A client that falls behind now receives an error message and a closed connection instead of a socket that stays open and silently stops delivering events. Clients must reconnect and resubscribe.
  • Wheels are platform-specific. blocksd no longer publishes a pure-Python wheel. Installs resolve a py3-none-{platform} wheel carrying the native compiler, and source installs require CMake 3.24 or newer.

🧪 Hardware Verification

  • LED rendering is confirmed on Lightpad Block M firmware 1.1.0 and LUMI Keys firmware 1.3.9 on Linux. The original Lightpad Block, Seaboard Block, and control blocks have not been checked on hardware.
  • Program compilation and upload are covered by CI on every wheel platform. On-device playback of third-party apps such as Seqoli has not been confirmed on hardware.
  • Windows has no hardware results yet, and macOS LED output, sleep and wake, and DAW coexistence remain unverified.

📝 Upgrade Notes

  • Update any dashboard, logging, or automation that assumed the 0 to 31 battery scale.
  • Audit touch-velocity consumers for the corrected sign and range.
  • Add reconnect-and-resubscribe handling for the event queue overflowed error, and handle the new config_reset event by discarding cached settings.
  • Building from source now needs CMake 3.24+. Package installs from PyPI, Homebrew, or AUR pick up the prebuilt compiler automatically; AUR users should note the move to arch-specific packages.
  • On Windows, start the daemon with blocksd run --foreground, keep web_enabled = true, leave api_enabled at its default of false, and remove any api_socket setting copied from a Linux or macOS config. Close ROLI Dashboard first so the MIDI ports are free.
  • Custom LittleFoot programs do not persist across daemon restarts, and while one is loaded blocksd's LED commands are rejected. Use blocksd program reset to hand control back to the device's saved program.