Skip to content
 
 

Repository files navigation

AcousticsLab

AcousticsLab Next

A private, multi-backend, fully-local AI/ML toolkit for the seamless development and deployment of real-time sound event detection on embedded Linux systems.

Rust Platform Backends License: Apache 2.0
AcousticsLab Console

The next generation of AcousticsLab, powered by a self-contained Rust backend with hybrid NPU/CPU inference engine and an intuitive Web console, delivering a fully-local, lightweight, efficient, end-to-end platform for acoustics analysis.

Why AcousticsLab

  • Fully-local & Private - Zero telemetry and no cloud dependency, audio and datasets remain strictly on your hardware, ~99% accuracy for causal tasks.
  • Low latency & Efficient - Heterogeneous inference pipeline, processes 1-second data in ~4.9 ms on RV1126B, keeping CPU free for other tasks.
  • Tiny footprint - On-device finetuning with 0.1GB RAM (~60 samples) and NPU-accelerated feature extraction at near-zero CPU load; inference consumes only ~18 MB RSS and < 5% CPU, packaged as a single, dependency-free binary.
  • Browser-based edge lab - Manage datasets, fine-tune models, deploy directly from an intuitive web UI—no cloud GPU needed.
  • Universal compatibility - Pairs native NPU acceleration with automatic CPU fallback to run seamlessly across practically any Linux SBC.
  • Seamless integration - Easy export, import & distributed deploy across different architectures and NPU capabilities with same weights, zero-downtime by model hot-swapping, integrates with your stack via WebSockets, Unix sockets, and a standard HTTP/JSON + SSE control plane.

Quick start

Install on a device

Grab the .deb (Debian/Ubuntu) or .rpm (Fedora/RHEL) for arm64/amd64 from the releases page, verify the checksum, and install.

That installs the daemon, the web console, and the acousticslab management CLI, and starts them under systemd. The console is on http://127.0.0.1:8080 (loopback by default). See docs/INSTALL.md for picking a microphone, exposing the console, and NPU setup.

Build from source

Prerequisites: Rust 1.94+ (edition 2024), protoc 3.21+, cmake 3.5+.

  1. Run the acousticslabd daemon (host/dev build, uses a mock tone if no real mic is configured):
cargo run --release --bin acousticslabd -- --workspace misc/workspace --config misc/etc/launch.toml

The bundled misc/etc/launch.toml binds the HTTP/WebSocket API to 127.0.0.1:8787.

  1. Open the console (in another shell):
cd web && npm install && npm run dev

Open the URL Vite prints; it proxies the API and streams to the daemon.

How it works

The acousticslabd is a single binary that owns the real-time audio path, hybrid CPU/NPU pipeline and exposes the HTTP/SSE control plane, the browser SPA is a pure client.

flowchart TB
  daemon_note["<b>Backend (acousticslabd)</b><br/>single binary · composition root<br/>tokio · DrainRegistry · common / proto"]

  subgraph g_inputs["Inputs · hardware · boot config"]
    mic(["<b>Microphone</b><br/>ALSA capture device"])
    disk_toml["<b>launch.toml / config.toml</b><br/>boot manifest · hot-reload prefs"]
  end

  subgraph g_config["Configuration · config"]
    cfg_launch["<b>LaunchConfig</b><br/>immutable boot manifest"]
    cfg_cell["<b>ConfigCell</b><br/>hot-reload ArcSwap (Config)<br/>notify watcher · debounce"]
    cfg_mic["<b>MicSettingsCell</b><br/>VersionedSwap (MicSettings)<br/>catalogue · policy"]
    cfg_inf["<b>InferenceCfg</b><br/>live ArcSwap<br/>hop_samples · top_k"]
  end

  subgraph g_capture["Audio Capture · audio_io · audio_buffer · dsp"]
    arb["<b>MicArbitrator</b><br/>real-time thread · RMS arbitration · failover<br/>resample -> 44.1 kHz mono"]
    buf["<b>AudioBuffer</b><br/>wait-free seqlock ring (~5.94 s)"]
    anchor["<b>SharedTimingAnchor</b><br/>first-sample capture time"]
  end

  subgraph g_proc["Processing · inference · opus_stream · preproc"]
    opus["<b>OpusEngine</b><br/>44.1 -> 48 kHz · 20 ms Opus frames<br/>pauses at 0 subscribers"]
    engine["<b>InferenceEngine</b><br/>blocking worker · sliding window · hop<br/>emits InferenceFrame"]
    preproc["<b>Preproc</b><br/>CPU STFT (SIMD) -> 43 × 232 log-mag z-norm"]
    bb_dyn["<b>Backbone</b><br/>dyn trait seam · load_first_supported<br/>4-conv -> 2000-dim features"]
    head["<b>HotHead</b><br/>Linear 2000 -> N · softmax -> top-k<br/>hot-swappable · ACSTHEAD .mpk"]
    subgraph g_backbone["Backbone impls · exactly one active"]
      rknn_bb["<b>RknnBackbone</b><br/>NPU · fp16"]
      burn_bb["<b>BurnBackbone</b><br/>CPU · fp32"]
    end
    rknn["<b>rknn_runtime</b><br/>NPU FFI · dlopen librknnrt.so"]
  end

  npu(["<b>Rockchip NPU</b><br/>hardware accelerator"])

  subgraph g_transport["Streaming Transport · stream_io · proto"]
    router["<b>StreamRouter</b><br/>broadcast (Bytes) · audio · infer<br/>Envelope wire format (prost)"]
    ws["<b>WS Endpoints</b><br/>/stream/audio · /stream/infer"]
    udsout["<b>Inference UDS</b><br/>raw length-prefixed Envelope"]
  end

  uds_consumer(["<b>External Consumer</b><br/>local C / C++ · Envelope reader"])
  gw(["<b>acousticslab-webd</b><br/>serves the SPA · reverse-proxies to the daemon<br/>front with TLS/auth to expose off-device"])

  subgraph g_browser["Browser SPA · web/ (SvelteKit · adapter-static)"]
    fe_stream["<b>Stream Worker</b><br/>2× WebSocket · WebCodecs Opus decode"]
    fe_ui["<b>UI</b><br/>routes · pages · components · i18n"]
    fe_stores["<b>Rune Stores</b><br/>reactive client state"]
    fe_api["<b>API Client</b><br/>REST · SSE · model import / export"]
    fe_dsp["<b>Browser DSP</b><br/>record · slice · FFT spectrogram"]
    fe_idb[("<b>IndexedDB</b><br/>local dataset cache · sha256-addressed")]
  end

  subgraph g_api["Control Plane · API"]
    api_routes["<b>API Routes</b><br/>/api/v1/* · REST · SSE<br/>uniform error envelope"]
    api_state["<b>AppState</b><br/>shared trait-handle hub"]
  end

  subgraph g_jobs["Cold-path Jobs · training · converter"]
    training["<b>training</b><br/>fine-tune head · frozen backbone · SGD"]
    converter["<b>converter</b><br/>import TFJS / ALPKG -> head.mpk"]
  end

  subgraph g_storage["Persistence · file_mgr"]
    fs["<b>FsService</b><br/>atomic writes · per-workspace mutex"]
    jobreg["<b>JobRegistry</b><br/>admission · leases · SSE events"]
    rotation["<b>head_rotation / active_head_writer</b><br/>stage -> validate -> publish -> install"]
    recovery["<b>recovery / storage_reaper</b><br/>boot drain · orphan sweep"]
  end

  status["<b>StatusMonitor</b><br/>heartbeats · sysinfo -> GET /status"]

  subgraph g_disk["Daemon-owned filesystem"]
    disk_ws[("<b>workspaces/</b><br/>datasets · heads · logs · .tmp")]
    disk_active[("<b>active/</b><br/>current.json · generations/")]
    disk_back[("<b>backbone artifacts</b><br/>backbone.rknn · backbone.mpk (read-only)")]
  end

  mic ==>|"PCM periods"| arb
  arb ==>|"Writer::push"| buf
  buf ==>|"Reader::peek"| opus
  opus ==>|"audio_tx · Envelope (Opus)"| router
  buf ==>|"Reader::peek"| engine
  engine ==>|"PCM window"| preproc
  preproc ==>|"spectrogram 43 × 232"| bb_dyn
  bb_dyn ==>|"2000-dim features"| head
  head ==>|"top-k -> infer_tx"| router
  router ==>|"per subscriber"| ws
  ws ==>|"WS binary"| gw
  gw ==>|"ws /stream/*"| fe_stream
  router -->|"infer only"| udsout
  udsout -->|"length-prefixed Envelope"| uds_consumer
  rknn_bb -->|"Session::infer"| rknn
  bb_dyn -.->|"impl · NPU"| rknn_bb
  bb_dyn -.->|"impl · CPU (host / dev)"| burn_bb
  arb -.->|"store"| anchor
  anchor -.->|"capture_us"| engine
  cfg_inf -.->|"load() per frame"| engine
  cfg_mic -.->|"snapshot() · wait-free"| arb
  router -.->|"audio_subscribers · pause @ 0"| opus
  cfg_cell -.->|"store on reload"| cfg_inf
  cfg_cell -.-> cfg_mic
  cfg_launch -.->|"catalogue"| cfg_mic
  disk_toml <-->|"load · atomic write · watch"| cfg_cell
  cfg_launch -.->|"backbone catalogue"| bb_dyn
  fe_api -->|"HTTP · SSE"| gw
  gw -->|"HTTP (proxied)"| api_routes
  api_routes --- api_state
  api_state -->|"workspaces · assets · heads"| fs
  api_state -->|"POST /train"| training
  api_state -->|"POST /convert"| converter
  api_state -->|"admission · GET /jobs"| jobreg
  api_state -->|"POST /inference · /mic"| cfg_cell
  api_state -->|"GET /status"| status
  training ==>|"publish_trained_head"| rotation
  converter ==>|"publish_imported_head"| rotation
  rotation ==>|"install_prevalidated · POST /active"| head
  training -.->|"lifecycle events"| jobreg
  jobreg -.->|"SSE job events"| api_routes
  fs <-->|"atomic workspace I/O"| disk_ws
  rotation <-->|"generations · current.json"| disk_active
  recovery <-->|"boot drain · orphan sweep"| disk_ws
  training -->|"read datasets · write logs"| disk_ws
  bb_dyn -->|"load @ boot (.rknn / .mpk)"| disk_back
  rknn <-->|"dlopen · rknn_run"| npu
  engine -.->|"heartbeat (every subsystem)"| status
  fe_stream -->|"topics"| fe_stores
  fe_ui <-->|"state · actions"| fe_stores
  fe_stores -->|"REST · SSE"| fe_api
  fe_stores <-->|"cache"| fe_idb
  fe_dsp -.->|"WAV slices"| fe_stores
  fe_dsp -->|"spectrogram PNG"| fe_idb
  daemon_note -.->|"composition root"| api_state

  classDef ext fill:#f2f4f4,stroke:#7f8c8d,color:#2c3e50;
  classDef note fill:#fcf3cf,stroke:#b7950b,color:#7e5109;
  classDef cfg fill:#d4efdf,stroke:#1e8449,color:#145a32;
  classDef cap fill:#fadbd8,stroke:#c0392b,color:#922b21;
  classDef proc fill:#fae5d3,stroke:#ca6f1e,color:#9c640c;
  classDef bb fill:#fdebd0,stroke:#ca6f1e,color:#9c640c;
  classDef trans fill:#fae5d3,stroke:#ca6f1e,color:#9c640c;
  classDef ui fill:#dbe9f6,stroke:#2471a3,color:#1b4f72;
  classDef api fill:#d4e6f1,stroke:#2471a3,color:#1b4f72;
  classDef jobs fill:#e8daef,stroke:#7d3c98,color:#5b2c6f;
  classDef store fill:#fae5c3,stroke:#b9770e,color:#7e5109;
  classDef swap fill:#e8daef,stroke:#6c3483,color:#5b2c6f;
  classDef obs fill:#eaecee,stroke:#839192,color:#2c3e50;
  classDef disk fill:#fdf2e3,stroke:#b9770e,color:#7e5109;

  class mic,npu,gw,uds_consumer ext;
  class daemon_note,disk_toml note;
  class cfg_launch,cfg_cell,cfg_mic,cfg_inf,anchor,fe_idb cfg;
  class arb,buf,opus cap;
  class engine,preproc,bb_dyn,rknn proc;
  class rknn_bb,burn_bb bb;
  class router,ws,udsout trans;
  class fe_stream,fe_ui,fe_stores,fe_api,fe_dsp ui;
  class api_routes,api_state api;
  class training,converter jobs;
  class fs,jobreg,recovery store;
  class head,rotation swap;
  class status obs;
  class disk_ws,disk_active,disk_back disk;

  linkStyle 0,1,2,3 stroke:#c0392b,stroke-width:2px
  linkStyle 4,5,6,7,8,9,10,11 stroke:#d35400,stroke-width:2px
  linkStyle 12,13,14 stroke:#d35400
  linkStyle 15,16 stroke:#566573
  linkStyle 17,18,19,20,21,22,23,24 stroke:#1e8449
  linkStyle 25,26 stroke:#7d6608
  linkStyle 27,28,29,30,31,32,33,34,35 stroke:#2471a3
  linkStyle 36,37,38 stroke:#6c3483,stroke-width:2px
  linkStyle 39,40 stroke:#95a5a6
  linkStyle 41,42,43,44,45,46 stroke:#7d6608
  linkStyle 47 stroke:#95a5a6
Loading

Note: thick edges are the real-time data plane (red = audio -> Opus, orange = inference -> top-k); solid blue is the HTTP/SSE control plane; dotted edges are config/shared state (green), job events (gray) and trait realizations; brown is durable disk I/O; purple is the classifier-head lifecycle (publish -> atomic hot-swap). Node colour groups by subsystem.

License

This software is licensed under the Apache License 2.0, see LICENSE for more information.

About

A private, multi-backend, fully-local AI/ML toolkit for the seamless development and deployment of real-time sound event detection on embedded Linux systems.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages