| Crates.io | av-mumu |
| lib.rs | av-mumu |
| version | 0.1.0-rc.6 |
| created_at | 2025-10-11 16:29:20.620803+00 |
| updated_at | 2025-10-15 23:14:11.873102+00 |
| description | Audio/Video (AV) tools plugin for the Lava / MuMu language |
| homepage | https://lava.nu11.uk |
| repository | https://gitlab.com/tofo/av-mumu |
| max_upload_size | |
| id | 1878340 |
| size | 1,238,431 |
av-mumu is a native audio plugin that adds high‑quality output and MP3 decoding to the Lava / MuMu runtime. It is built for cooperative, non‑blocking streaming: decoders never sleep, drains work in micro‑bursts, and the interpreter stays responsive.
Repository:
https://gitlab.com/tofo/av-mumu
Crate:av-mumu(library +cdylib)
We deliberately removed the old
av:play_mp3helper to keep the API format‑agnostic. Compose withmp3_decode(or future decoders) andout_*primitives.
av:out_open([device?:string, sample_rate?:int, channels?:int, buffer_ms?:int]) -> Ref(KeyedArray)
Returns a handler (as a Ref(KeyedArray)) containing public fields, e.g. id, device, channels, etc.
av:out_start(handler) -> 0 — starts the device (idempotent).
av:out_pause(handler) -> 0 — pauses the device; handler state becomes "paused".
av:out_resume(handler) -> 0 — resumes the device; state becomes "started".
av:out_stop(handler) -> 0 — stops (pauses) the device.
av:out_close(handler) -> 0 — closes the device; handler state is updated.
av:mp3_decode([path:string, chunk_frames?:int, channels?:int, start_ms?:long]) -> () => IntArray(i32) | "AGAIN" | "NO_MORE_DATA"i32.start_ms allows starting decoding at a millisecond offset (implemented as a conservative decode‑then‑skip).av:out_drain(handler, source[, on_done:Function(long)]) -> 0source into the output ring. The optional on_done is called exactly once with total frames when EOF is reached.frames_written (device‑side)position_frames (drain‑side count)position_ms (derived if sample_rate known)draining / ended flagsav:out_seek(handler, ms[, source_or_opts]) -> 0
Seeks to a new offset (ms) by restarting with a fresh source:
start_ms := ms and call av:mp3_decode(...) to build the transform.av:out_restart(handler, source_or_opts) -> 0
Restarts playback from the beginning or a new offset by providing either a new transform or decoder options (same semantics as above, without forcing start_ms).
av:out_status(handler) -> KeyedArray — richer status snapshot:
{
"state": "created" | "started" | "paused" | "stopped" | "closed" | "ended",
"draining": bool,
"paused": bool,
"ended": bool,
"frames_written": long, // device‑side total frames rendered
"position_frames": long, // drain‑side progress (best effort)
"position_ms": long, // derived from sample_rate
"sample_rate": int,
"channels": int,
"buffer_ms": int
}
av:devices([suppress?:bool=true]) -> StrArray — lists output device names (host only).extend("av")
handler = av:out_open([buffer_ms: 60, channels: 2])
decode = av:mp3_decode([path: "examples/mp3/01.mp3", chunk_frames: 2048, channels: 2])
av:out_start(handler)
av:out_drain(handler, decode)
extend("av")
extend("event")
handler = av:out_open([buffer_ms: 60, channels: 2])
decode = av:mp3_decode([path: "examples/mp3/01.mp3", chunk_frames: 2048, channels: 2])
av:out_start(handler)
av:out_drain(handler, decode)
event:timeout(2000, () => av:out_pause(handler)) // pause at ~2s
event:timeout(4000, () => av:out_resume(handler)) // resume at ~4s
extend("av")
h = av:out_open([buffer_ms: 60, channels: 2])
src = av:mp3_decode([path: "examples/mp3/01.mp3", chunk_frames: 2048, channels: 2])
av:out_start(h)
av:out_drain(h, src)
// Later, seek to 30s by providing decoder opts (we set start_ms under the hood)
av:out_seek(h, 30_000, [path:"examples/mp3/01.mp3", chunk_frames:2048, channels:2])
extend("av")
h = av:out_open([buffer_ms: 60, channels: 2])
src = av:mp3_decode([path: "examples/mp3/01.mp3", chunk_frames: 2048, channels: 2])
av:out_start(h)
av:out_drain(h, src)
fresh = av:mp3_decode([path: "examples/mp3/01.mp3", chunk_frames: 2048, channels: 2])
av:out_restart(h, fresh)
extend("av")
// ...after starting a drain
info = av:out_status(h)
slog(info)
"AGAIN" and the interpreter keeps ticking.i16 samples at the source channel count. The device callback safely up/down‑mixes to the actual device layout.IntArray(i32) for ergonomics with the type system; drains clamp to i16 when pushing to the ring.start_ms uses a conservative decode‑then‑skip approach; true container seeking will be wired as Symphonia seeking is exposed and proven across formats. The public API (av:out_seek) is stable and forwards to restart with a positioned transform.Host (native) playback is provided under the host feature.
make
make release
libmumuav.so/.dylib into /usr/local/lib and runs ldconfig when applicable):
make install
Cross‑compiles can use
TARGET_TRIPLE=<triple> make release(e.g.x86_64-unknown-linux-gnu).
Web/WASM builds are stubbed — functions report “unavailable” in that mode.
Drain micro‑bursts (defaults in parentheses)
LAVA_AV_BURST (64) — max chunks per poll tick
LAVA_AV_BUDGET_US (500) — per‑tick time budget in microseconds
Verbose bridge logs
LAVA_VERBOSE=1 — additional logs during registration & operations
av:play_mp3 (use mp3_decode + out_* primitives instead).av:out_pause, av:out_resume, av:out_restart, av:out_seek, av:out_status.av:mp3_decode now accepts start_ms for offset starts.Dual‑licensed under MIT and Apache‑2.0. See LICENSE.