diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 799b44a..d60ba06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: mkdir -p vendor curl --fail --silent --show-error --location \ --retry 3 --retry-delay 5 \ - -H "User-Agent: xdj-clock-ci/1.0" \ + -H "User-Agent: autoblink-ci/1.0" \ "https://static.crates.io/crates/rusty_link/rusty_link-$RUSTY_LINK_VERSION.crate" \ -o "$RUNNER_TEMP/rusty_link.tar.gz" tar xzf "$RUNNER_TEMP/rusty_link.tar.gz" -C vendor @@ -89,7 +89,7 @@ jobs: mkdir -p vendor curl --fail --silent --show-error --location \ --retry 3 --retry-delay 5 \ - -H "User-Agent: xdj-clock-ci/1.0" \ + -H "User-Agent: autoblink-ci/1.0" \ "https://static.crates.io/crates/rusty_link/rusty_link-$RUSTY_LINK_VERSION.crate" \ -o "$RUNNER_TEMP/rusty_link.tar.gz" tar xzf "$RUNNER_TEMP/rusty_link.tar.gz" -C vendor @@ -103,7 +103,7 @@ jobs: New-Item -ItemType Directory -Force vendor | Out-Null $url = "https://static.crates.io/crates/rusty_link/rusty_link-$env:RUSTY_LINK_VERSION.crate" $tmp = "$env:RUNNER_TEMP\rusty_link.tar.gz" - curl.exe --fail --show-error --location --retry 3 --retry-delay 5 -H "User-Agent: xdj-clock-ci/1.0" "$url" -o "$tmp" + curl.exe --fail --show-error --location --retry 3 --retry-delay 5 -H "User-Agent: autoblink-ci/1.0" "$url" -o "$tmp" if ($LASTEXITCODE -ne 0) { Write-Error "curl.exe failed with exit code $LASTEXITCODE" exit 1 diff --git a/.github/workflows/esp32-ci.yml b/.github/workflows/esp32-ci.yml index 357f062..3999ac7 100644 --- a/.github/workflows/esp32-ci.yml +++ b/.github/workflows/esp32-ci.yml @@ -57,4 +57,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: esp32-firmware - path: esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32 + path: esp32/target/xtensa-esp32-espidf/release/autoblink-esp32 diff --git a/.github/workflows/esp32-release.yml b/.github/workflows/esp32-release.yml index c5c4bf8..f4384da 100644 --- a/.github/workflows/esp32-release.yml +++ b/.github/workflows/esp32-release.yml @@ -57,15 +57,15 @@ jobs: - name: Prepare release package run: | mkdir -p esp32-release - elf=esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32 + elf=esp32/target/xtensa-esp32-espidf/release/autoblink-esp32 cp "$elf" esp32-release/ # One image with the bootloader and the partition table. Write it at address 0x0. espflash save-image --chip esp32 --merge \ --bootloader esp32/target/xtensa-esp32-espidf/release/bootloader.bin \ --partition-table esp32/partitions.csv \ - "$elf" esp32-release/xdj-clock-esp32-merged.bin + "$elf" esp32-release/autoblink-esp32-merged.bin # The app image without the bootloader. The box downloads it for an OTA update. - espflash save-image --chip esp32 "$elf" esp32-release/xdj-clock-esp32-ota.bin + espflash save-image --chip esp32 "$elf" esp32-release/autoblink-esp32-ota.bin tag="${{ github.ref_name }}" echo "${tag#esp32-v}" > esp32-release/esp32-version.txt tar -czf esp32-firmware.tar.gz esp32-release/ @@ -101,7 +101,7 @@ jobs: files=$(find artifacts -type f | tr '\n' ' ') # The host release stays the latest release of the repository. gh release create "$tag" \ - --title "xdj-clock ESP32 $tag" \ + --title "AUTOBLINK ESP32 $tag" \ --notes "ESP32 firmware release" \ --latest=false \ $files @@ -114,8 +114,12 @@ jobs: tar -xzf artifacts/*/esp32-firmware.tar.gz if ! gh release view esp32-latest > /dev/null 2>&1; then gh release create esp32-latest --prerelease --latest=false \ - --title "xdj-clock ESP32 OTA" \ + --title "AUTOBLINK ESP32 OTA" \ --notes "The ESP32 box downloads its firmware updates from this release." fi + # ponytail: Firmware 1.22.0 and older downloads xdj-clock-esp32-ota.bin. + # Remove this copy when all boxes have firmware 1.22.1 or newer. + cp esp32-release/autoblink-esp32-ota.bin esp32-release/xdj-clock-esp32-ota.bin gh release upload esp32-latest --clobber \ - esp32-release/esp32-version.txt esp32-release/xdj-clock-esp32-ota.bin \ No newline at end of file + esp32-release/esp32-version.txt esp32-release/autoblink-esp32-ota.bin \ + esp32-release/xdj-clock-esp32-ota.bin \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b809907..a23f004 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: mkdir -p vendor curl --fail --silent --show-error --location \ --retry 3 --retry-delay 5 \ - -H "User-Agent: xdj-clock-ci/1.0" \ + -H "User-Agent: autoblink-ci/1.0" \ "https://static.crates.io/crates/rusty_link/rusty_link-$RUSTY_LINK_VERSION.crate" \ -o "$RUNNER_TEMP/rusty_link.tar.gz" tar xzf "$RUNNER_TEMP/rusty_link.tar.gz" -C vendor @@ -69,29 +69,29 @@ jobs: # Windows x86-64 (MSVC) - target: x86_64-pc-windows-msvc runner: windows-latest - artifact: xdj-clock-windows-x86_64 - binary: xdj-clock.exe + artifact: autoblink-windows-x86_64 + binary: autoblink.exe archive_cmd: zip # Linux x86-64 - target: x86_64-unknown-linux-gnu runner: ubuntu-latest - artifact: xdj-clock-linux-x86_64 - binary: xdj-clock + artifact: autoblink-linux-x86_64 + binary: autoblink archive_cmd: tar # Linux ARM64 (Raspberry Pi 3/4/5 with a 64-bit OS) - target: aarch64-unknown-linux-gnu runner: ubuntu-24.04-arm - artifact: xdj-clock-linux-aarch64 - binary: xdj-clock + artifact: autoblink-linux-aarch64 + binary: autoblink archive_cmd: tar # macOS Apple Silicon - target: aarch64-apple-darwin runner: macos-latest - artifact: xdj-clock-macos-aarch64 - binary: xdj-clock + artifact: autoblink-macos-aarch64 + binary: autoblink archive_cmd: tar steps: @@ -120,7 +120,7 @@ jobs: mkdir -p vendor curl --fail --silent --show-error --location \ --retry 3 --retry-delay 5 \ - -H "User-Agent: xdj-clock-ci/1.0" \ + -H "User-Agent: autoblink-ci/1.0" \ "https://static.crates.io/crates/rusty_link/rusty_link-$RUSTY_LINK_VERSION.crate" \ -o "$RUNNER_TEMP/rusty_link.tar.gz" tar xzf "$RUNNER_TEMP/rusty_link.tar.gz" -C vendor @@ -134,7 +134,7 @@ jobs: New-Item -ItemType Directory -Force vendor | Out-Null $url = "https://static.crates.io/crates/rusty_link/rusty_link-$env:RUSTY_LINK_VERSION.crate" $tmp = "$env:RUNNER_TEMP\rusty_link.tar.gz" - curl.exe --fail --show-error --location --retry 3 --retry-delay 5 -H "User-Agent: xdj-clock-ci/1.0" "$url" -o "$tmp" + curl.exe --fail --show-error --location --retry 3 --retry-delay 5 -H "User-Agent: autoblink-ci/1.0" "$url" -o "$tmp" if ($LASTEXITCODE -ne 0) { Write-Error "curl.exe failed with exit code $LASTEXITCODE" exit 1 @@ -219,7 +219,7 @@ jobs: cp "target/${{ matrix.target }}/release/${{ matrix.binary }}" "$dir/" cp config.toml "$dir/" cp README.md "$dir/" - if [ "${{ runner.os }}" = "Linux" ]; then cp contrib/xdj-clock.service "$dir/"; fi + if [ "${{ runner.os }}" = "Linux" ]; then cp contrib/autoblink.service "$dir/"; fi tar -czf "$dir.tar.gz" "$dir" echo "ASSET=$dir.tar.gz" >> $GITHUB_ENV @@ -255,6 +255,6 @@ jobs: # Collect all archive files files=$(find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | tr '\n' ' ') gh release create "$tag" \ - --title "xdj-clock $tag" \ + --title "AUTOBLINK $tag" \ --generate-notes \ $files diff --git a/AGENTS.md b/AGENTS.md index 6fdee20..ff3a40b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ -# AGENTS.md — xdj-midi +# AGENTS.md — AUTOBLINK ## Workspace -- **3 crates**: `xdj-clock-host` (desktop app), `xdj-clock-esp32` (firmware), `xdj-clock-esp32-emulator` (native emulator) +- **3 crates**: `autoblink-host` (desktop app), `autoblink-esp32` (firmware), `autoblink-esp32-emulator` (native emulator) - The root workspace excludes `esp32`. The firmware has its own `Cargo.lock`, toolchain (`esp`) and target directory. - **Root `Cargo.toml`** defines workspace members and patches `rusty_link` from `vendor/rusty_link` - Package version is set once at workspace level (`[workspace.package]`) @@ -13,11 +13,11 @@ # Host (desktop app). A bare `cargo run` starts it (`default-run` in host/Cargo.toml). # The web UI is at http://127.0.0.1:11111 (next free port when in use). cargo run -cargo run -p xdj-clock-host -cargo run -p xdj-clock-host -- --web-bind 127.0.0.1:11111 -cargo run -p xdj-clock-host -- --list-midi -cargo run -p xdj-clock-host -- --list-interfaces -cargo build -p xdj-clock-host --release +cargo run -p autoblink-host +cargo run -p autoblink-host -- --web-bind 127.0.0.1:11111 +cargo run -p autoblink-host -- --list-midi +cargo run -p autoblink-host -- --list-interfaces +cargo build -p autoblink-host --release # ESP32 firmware for the WT32-ETH01 (see "ESP32 Build Requirements") . ~/export-esp.sh @@ -25,14 +25,14 @@ cd esp32 && cargo build --release cd esp32 && cargo run --release # flashes the board and opens the serial monitor # Emulator (Web dashboard at http://localhost:8080) -cargo run -p xdj-clock-esp32-emulator +cargo run -p autoblink-esp32-emulator # Tests (all crates) cargo test --locked # Single workspace member -cargo test -p xdj-clock-host -cargo build -p xdj-clock-esp32-emulator +cargo test -p autoblink-host +cargo build -p autoblink-esp32-emulator ``` ## Vendor Setup (required before local build) @@ -53,7 +53,7 @@ cp .github/vendor-patches/link_bindings.rs vendor/rusty_link/link_bindings.rs - Windows x86-64 → `.zip` archive - Linux x86-64 → `.tar.gz` archive -- Linux ARM64 (Raspberry Pi) → `.tar.gz` archive, built on the native `ubuntu-24.04-arm` runner. The Linux archives include `contrib/xdj-clock.service`. +- Linux ARM64 (Raspberry Pi) → `.tar.gz` archive, built on the native `ubuntu-24.04-arm` runner. The Linux archives include `contrib/autoblink.service`. - macOS ARM64 → `.tar.gz` archive - ESP32 firmware → `esp32-firmware.tar.gz`. It holds the ELF file, the merged image for address 0x0 and the OTA image. The workflow also puts the OTA image and `esp32-version.txt` into the prerelease `esp32-latest`. @@ -63,8 +63,8 @@ cp .github/vendor-patches/link_bindings.rs vendor/rusty_link/link_bindings.rs |---|---|---| | `host` | Desktop app + shared library | `config`, `midi/*`, `prolink/*`, `state`, `web/*`, `settings`, `link` | | `esp32` | Firmware for the WT32-ETH01, ESP-IDF with `std` | `net.rs` (Ethernet, WiFi AP, WiFi station), `ota.rs` (update from GitHub), `prolink.rs` (virtual CDJ, UDP receive), `tracks.rs` (dbserver fetch), `midi.rs` (UART MIDI out), `led.rs` (beat LED, status LED), `dmx.rs` (feature `dmx`), `webui.rs` (page, protocol v1, waveform route), `settings.rs` (NVS), `logring.rs` (log that stays through a restart, core dump report) | -| `crates/xdj-core-dbserver` | Blocking dbserver client of the firmware, host-testable | `client.rs` (fetch), `protocol.rs` (wire format), `parse.rs` (PSSI, PWV4, PQTZ, cues), `types.rs` (song structure, phrase position) | -| `crates/xdj-core-midi` | MIDI logic of the firmware, host-testable | `clock.rs`, `mapper.rs`, `settings.rs` (host setting indices) | +| `crates/autoblink-core-dbserver` | Blocking dbserver client of the firmware, host-testable | `client.rs` (fetch), `protocol.rs` (wire format), `parse.rs` (PSSI, PWV4, PQTZ, cues), `types.rs` (song structure, phrase position) | +| `crates/autoblink-core-midi` | MIDI logic of the firmware, host-testable | `clock.rs`, `mapper.rs`, `settings.rs` (host setting indices) | | `esp32-emulator` | Native emulator | `main.rs`, `verify_stopped.rs` | ### Host library exports (`host/src/lib.rs`) @@ -104,7 +104,7 @@ Plus `pub fn interface_priority()` for network interface ranking. All have `#[cfg(not(windows))]` guards around reuse_port calls. -**ESP32 vs host**: The ESP32 uses `std::net::UdpSocket` and `TcpStream` on the ESP-IDF network stack. It has no `tokio` runtime; each task is a `std::thread`. It parses packets with `xdj-core-prolink`, and `xdj_core_prolink::Tracker` keeps the deck and master state. It fetches the track data with `xdj-core-dbserver`, one track after the other. The host does not use these crates yet. It has its own async copy of the dbserver client in `host/src/prolink/metadata.rs`. +**ESP32 vs host**: The ESP32 uses `std::net::UdpSocket` and `TcpStream` on the ESP-IDF network stack. It has no `tokio` runtime; each task is a `std::thread`. It parses packets with `autoblink-core-prolink`, and `autoblink_core_prolink::Tracker` keeps the deck and master state. It fetches the track data with `autoblink-core-dbserver`, one track after the other. The host does not use these crates yet. It has its own async copy of the dbserver client in `host/src/prolink/metadata.rs`. ## Toolchain & Dependencies @@ -133,7 +133,7 @@ The firmware is for the WT32-ETH01 board: a classic ESP32 (Xtensa) with a LAN872 4. Connect the USB-C programming dock. Run `cargo run --release` in `esp32/` to flash the board. The runner writes the bootloader of the build, `partitions.csv` and the app into `ota_0`. It erases `otadata`. 5. If espflash cannot connect, hold BOOT, push RST, then release BOOT. -The output is `esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32`. +The output is `esp32/target/xtensa-esp32-espidf/release/autoblink-esp32`. ### OTA update @@ -155,14 +155,14 @@ The output is `esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32`. ### Debug logs -- Run `curl http://xdj-clock.local/log` to read the log over WiFi. The page shows no log. -- Run `curl http://xdj-clock.local/mem` to read the heap and the free stack of each task. The free stack is the low point since the start. Set a stack size from it, and keep a margin. +- Run `curl http://autoblink.local/log` to read the log over WiFi. The page shows no log. +- Run `curl http://autoblink.local/mem` to read the heap and the free stack of each task. The free stack is the low point since the start. Set a stack size from it, and keep a margin. - Start a thread with `crate::spawn`. It gives the FreeRTOS task the thread name. Without it, `/mem` shows each Rust thread as "pthread". - `crate::spawn` gives priority 5 on the two cores, the same as the HTTP server task. `crate::spawn_rt` sets a priority and a core. The WiFi task runs on core 0. The `midi` thread has priority 10 on core 1. The `prolink-rx` thread has priority 8 on core 1. - `prolink-rx` receives on the three Pro DJ Link sockets with one `poll` loop and one buffer. The task watchdog watches it and the `midi` thread. After a receive error, the thread sleeps for 10 ms, so that the idle task can run. - Each stack comes from the heap. The sizes in 1.19.0 keep about 1.5 to 2 KB free in each task. Measure with `/mem` again after a change that adds work to a thread. Version 1.21.0 changes `main` to 7 KB and adds `prolink-rx` with 6 KB. Measure these two. - The status message and the settings message are `Serialize` structs. Do not build them as a `serde_json::Value` tree. A tree has a heap block for each field, and the blocks fragment the heap. -- The firmware keeps 600 columns of the waveform preview (`xdj_core_dbserver::PREVIEW_COLUMNS`). The players send 1200. +- The firmware keeps 600 columns of the waveform preview (`autoblink_core_dbserver::PREVIEW_COLUMNS`). The players send 1200. - The dbserver client reads the PWV4 preview and the PQTZ beat grid as a stream (`read_message_with`). It keeps only 600 preview columns and the beats of the phrase starts, the fill-ins and the end. A buffered grid and its parsed copy needed up to 72 KB for one fetch. Without phrases, the client does not request the beat grid. - The fetch does not start below 24 KB free heap or an 8 KB largest block. The page then shows "not enough memory for the track data", and the fetch starts again after 3 s. - Do not use `std::io::copy` in a firmware thread. It puts an 8 KB buffer on the stack. @@ -177,7 +177,7 @@ The output is `esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32`. ```sh ~/.rustup/toolchains/esp/xtensa-esp-elf/*/xtensa-esp-elf/bin/xtensa-esp-elf-addr2line -pfiaC \ - -e esp32/target/xtensa-esp32-espidf/release/xdj-clock-esp32 0x400d1234 0x400d5678 + -e esp32/target/xtensa-esp32-espidf/release/autoblink-esp32 0x400d1234 0x400d5678 ``` - The release workflow puts the ELF into `esp32-firmware.tar.gz`. Use that ELF for a box with a release firmware. @@ -214,8 +214,8 @@ Do not connect a load to IO12 at power-on. If IO12 is high, the ESP32 sets the f ### Network -- The firmware starts its own WiFi network `xdj-clock-XXXX` (the last 2 bytes of the MAC address). The default password is `xdjclock123`. The web page is at http://192.168.1.1. -- The page settings "Join WiFi name" and "Join WiFi password" (indices 102 and 103) set an existing WiFi network. After a restart, the firmware joins it, and the own network stays on. The page is then also at the DHCP address of that network and at http://xdj-clock.local (mDNS). The iface field of the status shows the two addresses. +- The firmware starts its own WiFi network `autoblink-XXXX` (the last 2 bytes of the MAC address). The default password is `autoblink123`. The web page is at http://192.168.1.1. +- The page settings "Join WiFi name" and "Join WiFi password" (indices 102 and 103) set an existing WiFi network. After a restart, the firmware joins it, and the own network stays on. The page is then also at the DHCP address of that network and at http://autoblink.local (mDNS). The iface field of the status shows the two addresses. - When the firmware loses the joined network, it tries to join again after 30 s. After each failed try, the time doubles, up to 10 minutes, because each try scans all channels. The log shows each try. - Do not join a 192.168.1.x WiFi network. The page can fail on one of the two networks. The firmware logs a warning for this case. - The virtual CDJ uses the Ethernet port while it has an address. Without an Ethernet address, it uses the joined WiFi network. The Pro DJ Link sockets receive and send only on that interface (lwIP `SO_BINDTODEVICE`). Thus a joined WiFi network in the subnet of the Ethernet port gives no second copy of each beat. The firmware logs a warning when the two networks use one subnet. @@ -242,17 +242,17 @@ Do not connect a load to IO12 at power-on. If IO12 is high, the ESP32 sets the f ## Refactor Context (for future sessions) -- `crates/xdj-core-prolink` holds a copy of the protocol constants and the packet parsing from `host/src/prolink/`. The host does not use the shared crate yet. -- The ESP32 MIDI output follows the host rules (`xdj-core-midi`), with the phrase notes and CCs. It has no No Bass note, no MTC and no OSC. +- `crates/autoblink-core-prolink` holds a copy of the protocol constants and the packet parsing from `host/src/prolink/`. The host does not use the shared crate yet. +- The ESP32 MIDI output follows the host rules (`autoblink-core-midi`), with the phrase notes and CCs. It has no No Bass note, no MTC and no OSC. - The ESP32 page has no mixer section. The XDJ-AZ sends the EQ and fader positions only as USB MIDI, and the classic ESP32 has no USB host. - The ESP32 sends the Phrase 16 CC as the page help says (0 at each block start). The host never sends it, because its dedupe starts at 0. - The DMX output (`--features dmx`) flashes channel 1 on each beat. It has no settings yet. -- The emulator uses old setting indices (for example 8 for Note Channel, which the host has at 9). It does not use `xdj-core-midi` yet. +- The emulator uses old setting indices (for example 8 for Note Channel, which the host has at 9). It does not use `autoblink-core-midi` yet. - A refactor plan exists at `.sisyphus/plans/multi-architecture-refactor-plan.md` - `host/src/state.rs` and `host/src/main.rs` are the highest-coupling files in the host crate - Adding a new architecture target should mirror the `esp32`/`esp32-emulator` split: - firmware crate + native emulator crate - - both share protocol via a new shared crate (planned as `xdj-core-prolink`) + - both share protocol via a new shared crate (planned as `autoblink-core-prolink`) ## Constraints diff --git a/Cargo.lock b/Cargo.lock index 90b992e..0ed6c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "autoblink-core-dbserver" +version = "1.22.1" + +[[package]] +name = "autoblink-core-midi" +version = "1.22.1" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "autoblink-core-prolink" +version = "1.22.1" + +[[package]] +name = "autoblink-esp32-emulator" +version = "1.22.1" +dependencies = [ + "anyhow", + "autoblink-core-prolink", + "axum", + "serde", + "serde_json", + "socket2 0.5.10", + "tokio", + "toml", +] + +[[package]] +name = "autoblink-host" +version = "1.22.1" +dependencies = [ + "anyhow", + "autoblink-core-prolink", + "axum", + "clap 4.6.0", + "futures-util", + "midir", + "network-interface", + "parking_lot", + "rusty_link", + "serde", + "serde_json", + "socket2 0.6.3", + "tokio", + "tokio-tungstenite", + "toml", + "tracing", + "tracing-subscriber", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -1721,59 +1774,6 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -[[package]] -name = "xdj-clock-esp32-emulator" -version = "1.22.0" -dependencies = [ - "anyhow", - "axum", - "serde", - "serde_json", - "socket2 0.5.10", - "tokio", - "toml", - "xdj-core-prolink", -] - -[[package]] -name = "xdj-clock-host" -version = "1.22.0" -dependencies = [ - "anyhow", - "axum", - "clap 4.6.0", - "futures-util", - "midir", - "network-interface", - "parking_lot", - "rusty_link", - "serde", - "serde_json", - "socket2 0.6.3", - "tokio", - "tokio-tungstenite", - "toml", - "tracing", - "tracing-subscriber", - "xdj-core-prolink", -] - -[[package]] -name = "xdj-core-dbserver" -version = "1.22.0" - -[[package]] -name = "xdj-core-midi" -version = "1.22.0" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "xdj-core-prolink" -version = "1.22.0" - [[package]] name = "zerocopy" version = "0.8.48" diff --git a/Cargo.toml b/Cargo.toml index 2646015..aafa611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,9 @@ resolver = "2" members = [ "host", "esp32-emulator", - "crates/xdj-core-midi", - "crates/xdj-core-prolink", - "crates/xdj-core-dbserver", + "crates/autoblink-core-midi", + "crates/autoblink-core-prolink", + "crates/autoblink-core-dbserver", ] # The firmware builds with the Xtensa toolchain and its own target. It has its own @@ -13,10 +13,10 @@ members = [ exclude = ["esp32"] [workspace.package] -version = "1.22.0" +version = "1.22.1" edition = "2021" license = "Proprietary" -authors = ["xdj-midi team"] +authors = ["AUTOBLINK team"] [profile.release] lto = true diff --git a/README.md b/README.md index 3568807..51c5941 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# xdj-midi +# AUTOBLINK [![Version](https://img.shields.io/badge/version-1.10.0-blue.svg)](Cargo.toml) @@ -77,7 +77,7 @@ The page gets the waveform, the cues and the phrases of a deck from `GET /wavefo **Warning:** The dashboard has no login. By default, it listens on all interfaces. Anyone on the same network can change the MIDI settings. To allow access from this computer only, use `--web-bind 127.0.0.1:11111`. ### Raspberry Pi and unattended use -The `xdj-clock-linux-aarch64` release runs on a Raspberry Pi 3, 4 or 5 with a 64-bit OS. +The `autoblink-linux-aarch64` release runs on a Raspberry Pi 3, 4 or 5 with a 64-bit OS. The app recovers from these faults without a restart: - When the MIDI device goes away, the app tries to open the same port again every 2 s. When the port comes back, the clock sends Stop, then Start on the next phrase. MTC sends a full frame. @@ -86,17 +86,17 @@ The app recovers from these faults without a restart: - When you change the interface in the web UI, the app uses the new interface within 2 s. To run the app as a service: -1. Copy `xdj-clock` and `config.toml` to `/opt/xdj-clock/`. -2. Copy `xdj-clock.service` to `/etc/systemd/system/`. +1. Copy `autoblink` and `config.toml` to `/opt/autoblink/`. +2. Copy `autoblink.service` to `/etc/systemd/system/`. 3. Run `sudo systemctl daemon-reload`. -4. Run `sudo systemctl enable --now xdj-clock`. -5. Read the log with `journalctl -u xdj-clock -f`. +4. Run `sudo systemctl enable --now autoblink`. +5. Read the log with `journalctl -u autoblink -f`. The service runs the app with real-time priority. Open the web UI from another computer at `http://:11111`. `systemctl stop` sends MIDI Stop before the app exits. Before a long night, test the MIDI path on the target machine: ```sh -./xdj-clock --soak midi-clock --duration-secs 36000 --report soak.json +./autoblink --soak midi-clock --duration-secs 36000 --report soak.json ``` The test passes when the p99 jitter is 5 ms or less and no messages drop. @@ -113,7 +113,7 @@ Settings are managed via `config.toml` for the host and `sdkconfig` for the ESP3 source = "auto" # "auto" | "link" | "prolink" device_number = 7 # Virtual CDJ ID interface = "auto" # Network adapter selection -device_name = "xdj-clock" +device_name = "autoblink" [midi] output = "auto" # MIDI port substring match @@ -320,12 +320,12 @@ Core outputs: ### Building the ESP32 Firmware ```sh . /path/to/esp-idf/export.sh -cargo build --release -p xdj-clock-esp32 +cargo build --release -p autoblink-esp32 ``` ### Running the Emulator ```sh -cargo run -p xdj-clock-esp32-emulator +cargo run -p autoblink-esp32-emulator ``` Open `http://localhost:8080` to view the responsive WebSocket dashboard. diff --git a/config.toml b/config.toml index b868c3f..bf823bf 100644 --- a/config.toml +++ b/config.toml @@ -21,7 +21,7 @@ interface = "auto" device_number = 7 # Name broadcast on the network (max 16 bytes, ASCII). -device_name = "xdj-clock" +device_name = "autoblink" # ── Ableton Link ───────────────────────────────────────────────────────────── @@ -31,7 +31,7 @@ device_name = "xdj-clock" bind = "0.0.0.0:11111" [link] -# Enable Ableton Link session participation. When enabled, xdj-clock will +# Enable Ableton Link session participation. When enabled, AUTOBLINK will # appear in the Link session (e.g. visible in rekordbox's Link status panel). enabled = true diff --git a/contrib/xdj-clock.service b/contrib/autoblink.service similarity index 53% rename from contrib/xdj-clock.service rename to contrib/autoblink.service index 268ee08..2c94022 100644 --- a/contrib/xdj-clock.service +++ b/contrib/autoblink.service @@ -1,23 +1,23 @@ -# systemd unit for an unattended xdj-clock box (for example a Raspberry Pi). +# systemd unit for an unattended AUTOBLINK box (for example a Raspberry Pi). # # Install: -# sudo mkdir -p /opt/xdj-clock -# sudo cp xdj-clock config.toml /opt/xdj-clock/ -# sudo cp xdj-clock.service /etc/systemd/system/ +# sudo mkdir -p /opt/autoblink +# sudo cp autoblink config.toml /opt/autoblink/ +# sudo cp autoblink.service /etc/systemd/system/ # sudo systemctl daemon-reload -# sudo systemctl enable --now xdj-clock +# sudo systemctl enable --now autoblink # # Read the log: -# journalctl -u xdj-clock -f +# journalctl -u autoblink -f [Unit] -Description=xdj-clock (Pro DJ Link to MIDI clock bridge) +Description=AUTOBLINK (Pro DJ Link to MIDI clock bridge) Wants=network-online.target After=network-online.target sound.target [Service] -WorkingDirectory=/opt/xdj-clock -ExecStart=/opt/xdj-clock/xdj-clock --config /opt/xdj-clock/config.toml +WorkingDirectory=/opt/autoblink +ExecStart=/opt/autoblink/autoblink --config /opt/autoblink/config.toml # The app recovers from a lost MIDI device or network by itself. # The restart is a last-resort safety net only. Restart=on-failure diff --git a/crates/xdj-core-dbserver/Cargo.toml b/crates/autoblink-core-dbserver/Cargo.toml similarity index 79% rename from crates/xdj-core-dbserver/Cargo.toml rename to crates/autoblink-core-dbserver/Cargo.toml index 466cc68..63b0220 100644 --- a/crates/xdj-core-dbserver/Cargo.toml +++ b/crates/autoblink-core-dbserver/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xdj-core-dbserver" +name = "autoblink-core-dbserver" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/xdj-core-dbserver/src/client.rs b/crates/autoblink-core-dbserver/src/client.rs similarity index 100% rename from crates/xdj-core-dbserver/src/client.rs rename to crates/autoblink-core-dbserver/src/client.rs diff --git a/crates/xdj-core-dbserver/src/lib.rs b/crates/autoblink-core-dbserver/src/lib.rs similarity index 100% rename from crates/xdj-core-dbserver/src/lib.rs rename to crates/autoblink-core-dbserver/src/lib.rs diff --git a/crates/xdj-core-dbserver/src/parse.rs b/crates/autoblink-core-dbserver/src/parse.rs similarity index 100% rename from crates/xdj-core-dbserver/src/parse.rs rename to crates/autoblink-core-dbserver/src/parse.rs diff --git a/crates/xdj-core-dbserver/src/protocol.rs b/crates/autoblink-core-dbserver/src/protocol.rs similarity index 100% rename from crates/xdj-core-dbserver/src/protocol.rs rename to crates/autoblink-core-dbserver/src/protocol.rs diff --git a/crates/xdj-core-dbserver/src/types.rs b/crates/autoblink-core-dbserver/src/types.rs similarity index 100% rename from crates/xdj-core-dbserver/src/types.rs rename to crates/autoblink-core-dbserver/src/types.rs diff --git a/crates/xdj-core-dbserver/testdata/xdj_az_pssi.bin b/crates/autoblink-core-dbserver/testdata/xdj_az_pssi.bin similarity index 100% rename from crates/xdj-core-dbserver/testdata/xdj_az_pssi.bin rename to crates/autoblink-core-dbserver/testdata/xdj_az_pssi.bin diff --git a/crates/xdj-core-midi/Cargo.toml b/crates/autoblink-core-midi/Cargo.toml similarity index 87% rename from crates/xdj-core-midi/Cargo.toml rename to crates/autoblink-core-midi/Cargo.toml index 25cd7ef..ed8cebf 100644 --- a/crates/xdj-core-midi/Cargo.toml +++ b/crates/autoblink-core-midi/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xdj-core-midi" +name = "autoblink-core-midi" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/xdj-core-midi/src/clock.rs b/crates/autoblink-core-midi/src/clock.rs similarity index 100% rename from crates/xdj-core-midi/src/clock.rs rename to crates/autoblink-core-midi/src/clock.rs diff --git a/crates/xdj-core-midi/src/config.rs b/crates/autoblink-core-midi/src/config.rs similarity index 100% rename from crates/xdj-core-midi/src/config.rs rename to crates/autoblink-core-midi/src/config.rs diff --git a/crates/xdj-core-midi/src/lib.rs b/crates/autoblink-core-midi/src/lib.rs similarity index 100% rename from crates/xdj-core-midi/src/lib.rs rename to crates/autoblink-core-midi/src/lib.rs diff --git a/crates/xdj-core-midi/src/mapper.rs b/crates/autoblink-core-midi/src/mapper.rs similarity index 100% rename from crates/xdj-core-midi/src/mapper.rs rename to crates/autoblink-core-midi/src/mapper.rs diff --git a/crates/xdj-core-midi/src/settings.rs b/crates/autoblink-core-midi/src/settings.rs similarity index 100% rename from crates/xdj-core-midi/src/settings.rs rename to crates/autoblink-core-midi/src/settings.rs diff --git a/crates/xdj-core-prolink/Cargo.toml b/crates/autoblink-core-prolink/Cargo.toml similarity index 83% rename from crates/xdj-core-prolink/Cargo.toml rename to crates/autoblink-core-prolink/Cargo.toml index 2af9f67..2b2e90f 100644 --- a/crates/xdj-core-prolink/Cargo.toml +++ b/crates/autoblink-core-prolink/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xdj-core-prolink" +name = "autoblink-core-prolink" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/xdj-core-prolink/src/build.rs b/crates/autoblink-core-prolink/src/build.rs similarity index 100% rename from crates/xdj-core-prolink/src/build.rs rename to crates/autoblink-core-prolink/src/build.rs diff --git a/crates/xdj-core-prolink/src/constants.rs b/crates/autoblink-core-prolink/src/constants.rs similarity index 100% rename from crates/xdj-core-prolink/src/constants.rs rename to crates/autoblink-core-prolink/src/constants.rs diff --git a/crates/xdj-core-prolink/src/lib.rs b/crates/autoblink-core-prolink/src/lib.rs similarity index 100% rename from crates/xdj-core-prolink/src/lib.rs rename to crates/autoblink-core-prolink/src/lib.rs diff --git a/crates/xdj-core-prolink/src/math.rs b/crates/autoblink-core-prolink/src/math.rs similarity index 100% rename from crates/xdj-core-prolink/src/math.rs rename to crates/autoblink-core-prolink/src/math.rs diff --git a/crates/xdj-core-prolink/src/parse.rs b/crates/autoblink-core-prolink/src/parse.rs similarity index 100% rename from crates/xdj-core-prolink/src/parse.rs rename to crates/autoblink-core-prolink/src/parse.rs diff --git a/crates/xdj-core-prolink/src/tracker.rs b/crates/autoblink-core-prolink/src/tracker.rs similarity index 99% rename from crates/xdj-core-prolink/src/tracker.rs rename to crates/autoblink-core-prolink/src/tracker.rs index b019236..b5a3378 100644 --- a/crates/xdj-core-prolink/src/tracker.rs +++ b/crates/autoblink-core-prolink/src/tracker.rs @@ -493,7 +493,7 @@ mod tests { fn own_packets_are_ignored() { let mut t = Tracker::new(); t.set_own_device(5); - t.on_keepalive(&keepalive(5, "xdj-clock"), 0); + t.on_keepalive(&keepalive(5, "autoblink"), 0); assert!(!t.on_beat(&beat(5, 1, 120.0), 0)); assert!(t.decks().is_empty()); } diff --git a/crates/xdj-core-prolink/src/types.rs b/crates/autoblink-core-prolink/src/types.rs similarity index 100% rename from crates/xdj-core-prolink/src/types.rs rename to crates/autoblink-core-prolink/src/types.rs diff --git a/esp32-emulator/Cargo.toml b/esp32-emulator/Cargo.toml index 6c8dd9a..240de28 100644 --- a/esp32-emulator/Cargo.toml +++ b/esp32-emulator/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "xdj-clock-esp32-emulator" +name = "autoblink-esp32-emulator" version.workspace = true edition.workspace = true license.workspace = true -default-run = "xdj-clock-esp32-emulator" +default-run = "autoblink-esp32-emulator" [[bin]] -name = "xdj-clock-esp32-emulator" +name = "autoblink-esp32-emulator" path = "src/main.rs" [[bin]] @@ -20,7 +20,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "time", "sync", "net", " serde = { version = "1", features = ["derive"] } toml = "0.8" socket2 = "0.5" -xdj-core-prolink = { path = "../crates/xdj-core-prolink" } +autoblink-core-prolink = { path = "../crates/autoblink-core-prolink" } serde_json = "1.0.149" axum = { version = "0.8", default-features = false, features = ["http1", "tokio", "ws"] } diff --git a/esp32-emulator/src/main.rs b/esp32-emulator/src/main.rs index a86676c..6e06d2e 100644 --- a/esp32-emulator/src/main.rs +++ b/esp32-emulator/src/main.rs @@ -166,8 +166,8 @@ impl EmulatorState { last_cc_at: None, }; - state.add_log(0, "INFO", "xdj-clock ESP32 v1.1.7 starting"); - state.add_log(0, "INFO", "WiFi AP: xdj-midi-setup (pass: xdjclock123)"); + state.add_log(0, "INFO", "AUTOBLINK ESP32 v1.1.7 starting"); + state.add_log(0, "INFO", "WiFi AP: autoblink-setup (pass: autoblink123)"); state.add_log(0, "INFO", "Ethernet: 10.10.10.50/24"); state.add_log(0, "INFO", "MIDI: GPIO1(IN) GPIO3(OUT) @ 31250"); state.add_log(0, "INFO", "Pro DJ Link virtual CDJ #5"); @@ -232,16 +232,16 @@ impl EmulatorState { return; } - if &data[..10] != xdj_core_prolink::MAGIC { + if &data[..10] != autoblink_core_prolink::MAGIC { return; } let pkt_type = data[10]; match port { - xdj_core_prolink::PORT_DISCOVERY => self.handle_discovery_packet(addr, pkt_type, data), - xdj_core_prolink::PORT_BEAT => self.handle_beat_packet(addr, pkt_type, data), - xdj_core_prolink::PORT_STATUS => self.handle_status_packet(addr, pkt_type, data), + autoblink_core_prolink::PORT_DISCOVERY => self.handle_discovery_packet(addr, pkt_type, data), + autoblink_core_prolink::PORT_BEAT => self.handle_beat_packet(addr, pkt_type, data), + autoblink_core_prolink::PORT_STATUS => self.handle_status_packet(addr, pkt_type, data), _ => {} } @@ -249,9 +249,9 @@ impl EmulatorState { } fn handle_discovery_packet(&mut self, addr: SocketAddr, pkt_type: u8, data: &[u8]) { - use xdj_core_prolink::parse as packets; + use autoblink_core_prolink::parse as packets; match pkt_type { - xdj_core_prolink::PKT_KEEPALIVE => { + autoblink_core_prolink::PKT_KEEPALIVE => { if let Some(ka) = packets::parse_keepalive(data) { let type_name = if ka.device_number == 33 { "Mixer" @@ -284,7 +284,7 @@ impl EmulatorState { } } } - xdj_core_prolink::PKT_ANNOUNCE => { + autoblink_core_prolink::PKT_ANNOUNCE => { if data.len() >= 12 { let device_num = data[11]; self.add_log(2, "DEBUG", format!("Announce #{}", device_num)); @@ -304,9 +304,9 @@ impl EmulatorState { } fn handle_beat_packet(&mut self, _addr: SocketAddr, pkt_type: u8, data: &[u8]) { - use xdj_core_prolink::parse as packets; + use autoblink_core_prolink::parse as packets; match pkt_type { - xdj_core_prolink::PKT_BEAT => { + autoblink_core_prolink::PKT_BEAT => { if let Some(beat) = packets::parse_beat(data) { if let Some(idx) = self.find_deck(beat.device_number) { self.decks[idx].bpm = beat.effective_bpm; @@ -338,7 +338,7 @@ impl EmulatorState { self.add_log(2, "DEBUG", format!("Beat #{} bpm={:.2} beat={}/4", beat.device_number, beat.effective_bpm, beat.beat_in_bar)); } } - xdj_core_prolink::PKT_ABS_POSITION => { + autoblink_core_prolink::PKT_ABS_POSITION => { if let Some(abs) = packets::parse_abs_position(data) { if let Some(idx) = self.find_deck(abs.device_number) { self.decks[idx].bpm = abs.effective_bpm; @@ -360,9 +360,9 @@ impl EmulatorState { } fn handle_status_packet(&mut self, _addr: SocketAddr, pkt_type: u8, data: &[u8]) { - use xdj_core_prolink::parse as packets; + use autoblink_core_prolink::parse as packets; match pkt_type { - xdj_core_prolink::PKT_CDJ_STATUS => { + autoblink_core_prolink::PKT_CDJ_STATUS => { if let Some(s) = packets::parse_cdj_status(data) { if let Some(idx) = self.find_deck(s.device_number) { self.decks[idx].is_playing = s.play_state.is_playing() || s.is_playing_flag; @@ -390,7 +390,7 @@ impl EmulatorState { self.add_log(0, "INFO", format!("Status #{} {} bpm={:.2}{} (beat={})", s.device_number, playing_str, s.effective_bpm, master_str, s.beat_in_bar)); } } - xdj_core_prolink::PKT_MIXER_STATUS => { + autoblink_core_prolink::PKT_MIXER_STATUS => { if let Some(s) = packets::parse_mixer_status(data) { if let Some(idx) = self.find_deck(s.device_number) { self.decks[idx].last_seen = std::time::Instant::now(); @@ -635,7 +635,7 @@ impl EmulatorState { pub async fn run_emulator() -> Result<()> { println!("╔══════════════════════════════════════════╗"); - println!("║ xdj-clock ESP32 Emulator v1.1.2 ║"); + println!("║ AUTOBLINK ESP32 Emulator v1.1.2 ║"); println!("╚══════════════════════════════════════════╝"); fn create_reuse_socket(port: u16, broadcast: bool) -> Result { @@ -681,8 +681,8 @@ pub async fn run_emulator() -> Result<()> { if let Ok((len, addr)) = sock.recv_from(&mut buf).await { let data = &buf[..len]; state_net2.write().await.handle_packet(addr, 50001, data); - if data.len() > 10 && data[10] == xdj_core_prolink::PKT_BEAT { - if let Some(beat) = xdj_core_prolink::parse::parse_beat(data) { + if data.len() > 10 && data[10] == autoblink_core_prolink::PKT_BEAT { + if let Some(beat) = autoblink_core_prolink::parse::parse_beat(data) { let _ = beat_tx_net.send((beat.device_number, beat.beat_in_bar)); } } @@ -705,10 +705,10 @@ pub async fn run_emulator() -> Result<()> { let state_part = Arc::clone(&state); let sock_part = Arc::clone(&sock_50000); tokio::spawn(async move { - use xdj_core_prolink::build; + use autoblink_core_prolink::build; let mut interval = interval(Duration::from_millis(1500)); let mac = [0x02, 0xAB, 0xCD, 0xEF, 0x01, 0x02]; - let name = build::pad_name("xdj-clock-emu"); + let name = build::pad_name("autoblink-emu"); loop { interval.tick().await; diff --git a/esp32-emulator/src/verify_stopped.rs b/esp32-emulator/src/verify_stopped.rs index 5829c26..dd9056f 100644 --- a/esp32-emulator/src/verify_stopped.rs +++ b/esp32-emulator/src/verify_stopped.rs @@ -1,6 +1,6 @@ use std::net::UdpSocket; use std::time::Duration; -use xdj_core_prolink::{MAGIC, PKT_ABS_POSITION, PKT_MIXER_STATUS}; +use autoblink_core_prolink::{MAGIC, PKT_ABS_POSITION, PKT_MIXER_STATUS}; fn main() -> anyhow::Result<()> { let sock = UdpSocket::bind("0.0.0.0:0")?; diff --git a/esp32/Cargo.lock b/esp32/Cargo.lock index 34a5483..4f8673f 100644 --- a/esp32/Cargo.lock +++ b/esp32/Cargo.lock @@ -56,6 +56,37 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autoblink-core-dbserver" +version = "1.22.1" + +[[package]] +name = "autoblink-core-midi" +version = "1.22.1" +dependencies = [ + "serde", +] + +[[package]] +name = "autoblink-core-prolink" +version = "1.22.1" + +[[package]] +name = "autoblink-esp32" +version = "1.22.1" +dependencies = [ + "anyhow", + "autoblink-core-dbserver", + "autoblink-core-midi", + "autoblink-core-prolink", + "embuild", + "esp-idf-svc", + "flate2", + "log", + "serde", + "serde_json", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -1726,37 +1757,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "xdj-clock-esp32" -version = "1.22.0" -dependencies = [ - "anyhow", - "embuild", - "esp-idf-svc", - "flate2", - "log", - "serde", - "serde_json", - "xdj-core-dbserver", - "xdj-core-midi", - "xdj-core-prolink", -] - -[[package]] -name = "xdj-core-dbserver" -version = "1.22.0" - -[[package]] -name = "xdj-core-midi" -version = "1.22.0" -dependencies = [ - "serde", -] - -[[package]] -name = "xdj-core-prolink" -version = "1.22.0" - [[package]] name = "zlib-rs" version = "0.6.8" diff --git a/esp32/Cargo.toml b/esp32/Cargo.toml index 884d275..1888fe9 100644 --- a/esp32/Cargo.toml +++ b/esp32/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "xdj-clock-esp32" +name = "autoblink-esp32" # The firmware is not in the workspace. Keep this version the same as the workspace version. -version = "1.22.0" +version = "1.22.1" edition = "2021" license = "Proprietary" -authors = ["xdj-midi team"] +authors = ["AUTOBLINK team"] rust-version = "1.82" [[bin]] -name = "xdj-clock-esp32" +name = "autoblink-esp32" harness = false [profile.release] @@ -32,15 +32,15 @@ esp-idf-svc = "0.53" log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" -xdj-core-dbserver = { path = "../crates/xdj-core-dbserver" } -xdj-core-midi = { path = "../crates/xdj-core-midi" } -xdj-core-prolink = { path = "../crates/xdj-core-prolink" } +autoblink-core-dbserver = { path = "../crates/autoblink-core-dbserver" } +autoblink-core-midi = { path = "../crates/autoblink-core-midi" } +autoblink-core-prolink = { path = "../crates/autoblink-core-prolink" } [build-dependencies] embuild = "0.33.5" # build.rs compresses the web page with gzip. flate2 = "1" -# ESP-IDF 5 has mDNS as an external component. It gives the name xdj-clock.local. +# ESP-IDF 5 has mDNS as an external component. It gives the name autoblink.local. [[package.metadata.esp-idf-sys.extra_components]] remote_component = { name = "espressif/mdns", version = "1.8.2" } diff --git a/esp32/src/main.rs b/esp32/src/main.rs index 5ff7fc0..499ca2d 100644 --- a/esp32/src/main.rs +++ b/esp32/src/main.rs @@ -1,4 +1,4 @@ -//! xdj-clock firmware for the WT32-ETH01 (ESP32 + LAN8720 Ethernet). +//! AUTOBLINK firmware for the WT32-ETH01 (ESP32 + LAN8720 Ethernet). //! //! The firmware joins the Pro DJ Link network on the Ethernet port as a virtual CDJ. It reads //! the beats, the player status and the track data from the players. The MIDI output on IO17 @@ -6,7 +6,7 @@ //! state of the box (see `led.rs`). The firmware serves the web //! page on its own WiFi network at http://192.168.1.1. When the settings have a join network, //! the page is also on that network, at the address from its DHCP server and at -//! http://xdj-clock.local. +//! http://autoblink.local. #[cfg(feature = "dmx")] mod dmx; @@ -36,7 +36,7 @@ use esp_idf_svc::hal::peripherals::Peripherals; use esp_idf_svc::mdns::EspMdns; use esp_idf_svc::nvs::EspDefaultNvsPartition; use esp_idf_svc::sys; -use xdj_core_prolink::Tracker; +use autoblink_core_prolink::Tracker; use crate::midi::MidiStatus; use crate::net::{EthPins, AP_IP}; @@ -94,7 +94,7 @@ fn main() { fn run() -> anyhow::Result<()> { // SAFETY: esp_reset_reason has no preconditions. let reset_reason = reset_reason(unsafe { sys::esp_reset_reason() }); - log::info!("xdj-clock {} starts. Reason for the restart: {reset_reason}", env!("CARGO_PKG_VERSION")); + log::info!("AUTOBLINK {} starts. Reason for the restart: {reset_reason}", env!("CARGO_PKG_VERSION")); logring::report_core_dump(); let p = Peripherals::take()?; let sysloop = EspSystemEventLoop::take()?; @@ -107,7 +107,7 @@ fn run() -> anyhow::Result<()> { let mut mac = [0u8; 6]; // SAFETY: the buffer has the 6 bytes that a MAC address needs. sys::esp!(unsafe { sys::esp_read_mac(mac.as_mut_ptr(), sys::esp_mac_type_t_ESP_MAC_WIFI_SOFTAP) })?; - let default_ssid = format!("xdj-clock-{:02X}{:02X}", mac[4], mac[5]); + let default_ssid = format!("autoblink-{:02X}{:02X}", mac[4], mac[5]); let settings = Settings::new(nvs, default_ssid)?; let ssid = settings.ssid(); let join = settings.join(); diff --git a/esp32/src/midi.rs b/esp32/src/midi.rs index 3d6bf00..4f97e4e 100644 --- a/esp32/src/midi.rs +++ b/esp32/src/midi.rs @@ -1,6 +1,6 @@ //! MIDI output on UART2, TX on IO17, at 31250 baud. //! -//! One thread runs the clock and the note and CC mapper of `xdj-core-midi`. The receive thread +//! One thread runs the clock and the note and CC mapper of `autoblink-core-midi`. The receive thread //! sends it each master beat through a FreeRTOS queue. The thread sleeps until 1 ms before the //! next clock pulse, then waits in a loop, so the pulse goes out on time. //! @@ -28,9 +28,9 @@ use esp_idf_svc::hal::task::queue::Queue; use esp_idf_svc::hal::uart::{config, UartDriver, UART2}; use esp_idf_svc::hal::units::Hertz; use esp_idf_svc::sys; -use xdj_core_midi::clock::Master; -use xdj_core_dbserver::{current_phrase_idx, phrase_info}; -use xdj_core_midi::{Clock, Mapper, MidiConfig, Out, Phrase, Smoother, Snapshot, CLOCK}; +use autoblink_core_midi::clock::Master; +use autoblink_core_dbserver::{current_phrase_idx, phrase_info}; +use autoblink_core_midi::{Clock, Mapper, MidiConfig, Out, Phrase, Smoother, Snapshot, CLOCK}; use crate::State; diff --git a/esp32/src/net.rs b/esp32/src/net.rs index 5ce3764..f789e16 100644 --- a/esp32/src/net.rs +++ b/esp32/src/net.rs @@ -21,8 +21,8 @@ use esp_idf_svc::wifi::{ /// The address of the firmware on its own WiFi network. The web page is at http://192.168.1.1. pub const AP_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 1); -/// The mDNS name of the firmware. The web page is at http://xdj-clock.local. -pub const MDNS_NAME: &str = "xdj-clock"; +/// The mDNS name of the firmware. The web page is at http://autoblink.local. +pub const MDNS_NAME: &str = "autoblink"; /// Binds the socket `fd` to the network interface `netif` (lwIP `SO_BINDTODEVICE`). An empty /// `netif` removes the binding. diff --git a/esp32/src/ota.rs b/esp32/src/ota.rs index 2491099..d53b620 100644 --- a/esp32/src/ota.rs +++ b/esp32/src/ota.rs @@ -33,7 +33,7 @@ use crate::State; // ponytail: the page has no password, so each client on the networks of the box can start an // update. The update comes only from the GitHub release over HTTPS, and the image has no // signature. Add secure boot and a signed image if the box must refuse a changed release. -const BASE_URL: &str = "https://github.com/strikeout/xdj-midi-beat/releases/download/esp32-latest"; +const BASE_URL: &str = "https://github.com/strikeout/autoblink/releases/download/esp32-latest"; /// The NVS namespace for the install request and the error of the last install. const NVS_NAMESPACE: &str = "ota"; @@ -220,7 +220,7 @@ fn install(state: &Mutex) -> anyhow::Result<()> { } let latest = state.lock().unwrap().latest.clone(); set(state, "downloading", latest.clone(), 0); - let mut conn = get(&format!("{BASE_URL}/xdj-clock-esp32-ota.bin"))?; + let mut conn = get(&format!("{BASE_URL}/autoblink-esp32-ota.bin"))?; let total: usize = conn .header("Content-Length") .and_then(|l| l.parse().ok()) diff --git a/esp32/src/prolink.rs b/esp32/src/prolink.rs index 3c00e74..516ccd6 100644 --- a/esp32/src/prolink.rs +++ b/esp32/src/prolink.rs @@ -27,7 +27,7 @@ use std::time::{Duration, Instant}; use esp_idf_svc::hal::cpu::Core; use esp_idf_svc::sys; -use xdj_core_prolink::{ +use autoblink_core_prolink::{ build_announce, build_claim1, build_claim2, build_claim_final, build_keepalive, build_status_packet, has_magic, pad_name, parse_abs_position, parse_beat, parse_cdj_status, parse_keepalive, TrackRef, PKT_CONFLICT, PORT_BEAT, PORT_DISCOVERY, PORT_STATUS, @@ -36,7 +36,7 @@ use xdj_core_prolink::{ use crate::midi::Beat; use crate::State; -const NAME: &str = "xdj-clock"; +const NAME: &str = "autoblink"; const CLAIM_INTERVAL: Duration = Duration::from_millis(300); const CLAIM_ROUNDS: u8 = 3; const KEEPALIVE_INTERVAL: Duration = Duration::from_millis(1500); diff --git a/esp32/src/settings.rs b/esp32/src/settings.rs index 914f5c9..85e7c58 100644 --- a/esp32/src/settings.rs +++ b/esp32/src/settings.rs @@ -1,6 +1,6 @@ //! The settings that the web page can change. NVS keeps them across a restart. //! -//! The MIDI rows and "Device Number" use the host indices (`xdj_core_midi::settings`). The WiFi +//! The MIDI rows and "Device Number" use the host indices (`autoblink_core_midi::settings`). The WiFi //! rows use 100 and up, so that they do not collide with the host settings. Rows 100 and 101 set //! the own access point. Rows 102 and 103 set an existing network that the firmware joins. @@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvs, NvsDefault}; -use xdj_core_midi::{settings as midi_settings, MidiConfig, SettingItem}; +use autoblink_core_midi::{settings as midi_settings, MidiConfig, SettingItem}; /// The host index of "Device Number": the number that the virtual CDJ asks for. const IDX_DEVICE: usize = 2; @@ -18,7 +18,7 @@ const IDX_SSID: usize = 100; const IDX_PASSWORD: usize = 101; const IDX_JOIN_SSID: usize = 102; const IDX_JOIN_PASSWORD: usize = 103; -const DEFAULT_PASSWORD: &str = "xdjclock123"; +const DEFAULT_PASSWORD: &str = "autoblink123"; /// The MIDI configuration goes to NVS when it has not changed for this time. const SAVE_DELAY: Duration = Duration::from_secs(2); diff --git a/esp32/src/tracks.rs b/esp32/src/tracks.rs index a47c2c6..13329b3 100644 --- a/esp32/src/tracks.rs +++ b/esp32/src/tracks.rs @@ -13,8 +13,8 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use xdj_core_dbserver::{asking_player, fetch_with, SongStructure, TrackMetadata, TrackOverview}; -use xdj_core_prolink::TrackRef; +use autoblink_core_dbserver::{asking_player, fetch_with, SongStructure, TrackMetadata, TrackOverview}; +use autoblink_core_prolink::TrackRef; use esp_idf_svc::sys; diff --git a/esp32/src/webui.rs b/esp32/src/webui.rs index 3cec61f..c06af57 100644 --- a/esp32/src/webui.rs +++ b/esp32/src/webui.rs @@ -29,8 +29,8 @@ use crate::ota::{self, OtaCmd, OtaStatus}; use crate::settings::Settings; use crate::tracks::TrackData; use crate::State; -use xdj_core_dbserver::{phrase_status, PhraseKind, TrackOverview}; -use xdj_core_prolink::{Deck, PlayState}; +use autoblink_core_dbserver::{phrase_status, PhraseKind, TrackOverview}; +use autoblink_core_prolink::{Deck, PlayState}; /// The page, compressed with gzip by `build.rs`. const INDEX_HTML_GZ: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/index.html.gz")); diff --git a/host/Cargo.lock b/host/Cargo.lock index 26461d7..fde3827 100644 --- a/host/Cargo.lock +++ b/host/Cargo.lock @@ -2565,7 +2565,7 @@ dependencies = [ ] [[package]] -name = "xdj-clock" +name = "autoblink" version = "1.0.0" dependencies = [ "anyhow", diff --git a/host/Cargo.toml b/host/Cargo.toml index 83ab153..6e98a7e 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "xdj-clock-host" +name = "autoblink-host" version.workspace = true edition.workspace = true -default-run = "xdj-clock" +default-run = "autoblink" license.workspace = true [[bin]] -name = "xdj-clock" +name = "autoblink" path = "src/main.rs" [dependencies] @@ -25,7 +25,7 @@ axum = { version = "0.8", default-features = false, features = ["http1", "tokio" serde_json = "1" rusty_link = "0.2" -xdj-core-prolink = { path = "../crates/xdj-core-prolink" } +autoblink-core-prolink = { path = "../crates/autoblink-core-prolink" } [dev-dependencies] tokio-tungstenite = "0.29" diff --git a/host/src/app.rs b/host/src/app.rs index 46d0f92..715f521 100644 --- a/host/src/app.rs +++ b/host/src/app.rs @@ -27,7 +27,7 @@ use crate::{midi::soak::SoakArgs, midi::soak::SoakMode}; // ── CLI ─────────────────────────────────────────────────────────────────────────── #[derive(Parser, Debug)] -#[command(name = "xdj-clock", version, about = "Pioneer CDJ/XDJ → MIDI bridge")] +#[command(name = "autoblink", version, about = "Pioneer CDJ/XDJ → MIDI bridge")] pub struct Cli { /// Path to TOML config file. #[arg(short, long, default_value = "config.toml")] @@ -239,7 +239,7 @@ pub fn init() -> anyhow::Result { device_number = startup_cfg.device_number, device_name = %startup_cfg.device_name, midi_port = %startup_cfg.midi.output, - "xdj-clock starting" + "AUTOBLINK starting" ); // The Pro DJ Link supervisor detects the network interface (runtime/prolink.rs). @@ -571,7 +571,7 @@ pub fn interface_priority(name: &str, ip: &Ipv4Addr) -> u8 { } pub fn list_midi_ports() -> anyhow::Result<()> { - let midi_out = MidiOutput::new("xdj-clock")?; + let midi_out = MidiOutput::new("autoblink")?; let ports = midi_out.ports(); if ports.is_empty() { println!("No MIDI output ports found."); diff --git a/host/src/config.rs b/host/src/config.rs index 5100330..fda6838 100644 --- a/host/src/config.rs +++ b/host/src/config.rs @@ -46,7 +46,7 @@ impl Default for Config { source: Source::default(), interface: "auto".into(), device_number: 7, - device_name: "xdj-clock".into(), + device_name: "autoblink".into(), midi: MidiConfig::default(), link: LinkConfig::default(), web: WebConfig::default(), diff --git a/host/src/midi/input.rs b/host/src/midi/input.rs index a8f5bad..5ce2546 100644 --- a/host/src/midi/input.rs +++ b/host/src/midi/input.rs @@ -108,7 +108,7 @@ fn run(dj_state: SharedState) { } fn find_port() -> Option<(MidiInput, MidiInputPort)> { - let midi_in = MidiInput::new("xdj-clock-in").ok()?; + let midi_in = MidiInput::new("autoblink-in").ok()?; let port = midi_in .ports() .into_iter() @@ -125,7 +125,7 @@ fn connect( midi_in .connect( port, - "xdj-clock-mixer", + "autoblink-mixer", move |_, msg, _| { // A panic on the CoreMIDI thread aborts the process. let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { diff --git a/host/src/midi/ports.rs b/host/src/midi/ports.rs index 9f17d0d..65ff588 100644 --- a/host/src/midi/ports.rs +++ b/host/src/midi/ports.rs @@ -4,7 +4,7 @@ /// /// The call blocks (CoreMIDI, ALSA). Call it from `spawn_blocking`. pub fn list_output_ports() -> Vec { - let Ok(midi_out) = midir::MidiOutput::new("xdj-clock-ports") else { + let Ok(midi_out) = midir::MidiOutput::new("autoblink-ports") else { return Vec::new(); }; midi_out @@ -24,12 +24,12 @@ pub fn open_output( index: Option, ) -> anyhow::Result<(midir::MidiOutputConnection, String)> { if let Some(i) = index { - let midi_out = midir::MidiOutput::new("xdj-clock")?; + let midi_out = midir::MidiOutput::new("autoblink")?; let ports = midi_out.ports(); if let Some(port) = ports.get(i) { if midi_out.port_name(port).ok().as_deref() == Some(name) { return midi_out - .connect(port, "xdj-clock") + .connect(port, "autoblink") .map(|conn| (conn, name.to_string())) .map_err(|e| anyhow::anyhow!("{e}")); } diff --git a/host/src/midi/transport.rs b/host/src/midi/transport.rs index 1a14622..e70e983 100644 --- a/host/src/midi/transport.rs +++ b/host/src/midi/transport.rs @@ -43,7 +43,7 @@ pub trait MidiTransport: Send + Sync { /// /// Returns the connection and the full name of the selected port. pub fn open_midi_output(port_name: &str) -> anyhow::Result<(MidiOutputConnection, String)> { - let midi_out = MidiOutput::new("xdj-clock")?; + let midi_out = MidiOutput::new("autoblink")?; let ports = midi_out.ports(); if ports.is_empty() { anyhow::bail!("No MIDI output ports available"); @@ -54,7 +54,7 @@ pub fn open_midi_output(port_name: &str) -> anyhow::Result<(MidiOutputConnection let name = midi_out.port_name(port)?; tracing::info!(%name, "Auto-selected MIDI output port"); return midi_out - .connect(port, "xdj-clock") + .connect(port, "autoblink") .map(|conn| (conn, name)) .map_err(|e| anyhow::anyhow!("{}", e)); } @@ -77,7 +77,7 @@ pub fn open_midi_output(port_name: &str) -> anyhow::Result<(MidiOutputConnection let name = names[i].clone(); tracing::info!(%name, "Selected MIDI output port"); return midi_out - .connect(&ports[i], "xdj-clock") + .connect(&ports[i], "autoblink") .map(|conn| (conn, name)) .map_err(|e| anyhow::anyhow!("{}", e)); } diff --git a/host/src/prolink/mod.rs b/host/src/prolink/mod.rs index f72d72b..e8ab8a0 100644 --- a/host/src/prolink/mod.rs +++ b/host/src/prolink/mod.rs @@ -1,6 +1,6 @@ //! Pro DJ Link protocol — constants, packet types, and zero-copy parsing. //! -//! Protocol constants and math helpers are shared via `xdj_core_prolink`. +//! Protocol constants and math helpers are shared via `autoblink_core_prolink`. //! Listener tasks, packet builders, and metadata fetching remain host-specific. pub mod beat_listener; diff --git a/scripts/macos-run.sh b/scripts/macos-run.sh index 7d0fdde..cd7aa62 100755 --- a/scripts/macos-run.sh +++ b/scripts/macos-run.sh @@ -4,7 +4,7 @@ # macOS blocks local network access for a command-line binary that has no # permission. A TCP connection or a UDP unicast to a player then fails with # "No route to host". System Settings shows no Local Network switch for a bare -# binary. This script copies xdj-clock into an app bundle, signs the bundle and +# binary. This script copies the autoblink binary into an app bundle, signs the bundle and # runs the copy. The bundle then gets its own switch. # # The script signs with the identity in XDJ_CODESIGN_IDENTITY. If that variable @@ -16,26 +16,26 @@ set -eu bin=$1 shift -[ "$(basename "$bin")" = xdj-clock ] || exec "$bin" "$@" +[ "$(basename "$bin")" = autoblink ] || exec "$bin" "$@" -app="$(dirname "$bin")/xdj-clock.app" +app="$(dirname "$bin")/autoblink.app" mkdir -p "$app/Contents/MacOS" -cp "$bin" "$app/Contents/MacOS/xdj-clock" +cp "$bin" "$app/Contents/MacOS/autoblink" cat > "$app/Contents/Info.plist" <<'EOF' CFBundleIdentifier - com.strikeout.xdj-clock + com.strikeout.autoblink CFBundleName - xdj-clock + AUTOBLINK CFBundleExecutable - xdj-clock + autoblink CFBundlePackageType APPL NSLocalNetworkUsageDescription - xdj-clock reads the beat, the track and the phrase data from the Pro DJ Link players. + AUTOBLINK reads the beat, the track and the phrase data from the Pro DJ Link players. EOF @@ -49,4 +49,4 @@ fi codesign --force --sign "$identity" "$app" 2>/dev/null || echo "macos-run.sh: codesign failed. The app runs with the old signature." >&2 -exec "$app/Contents/MacOS/xdj-clock" "$@" +exec "$app/Contents/MacOS/autoblink" "$@" diff --git a/webui/index.html b/webui/index.html index d4d112b..73ea7e6 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3,9 +3,9 @@ -xdj-clock +AUTOBLINK