Skip to content

Implement Audio support for native and Emscripten #658

@drslebedev

Description

@drslebedev

Summary

Add cross-platform audio that runs on native and WebAssembly (Emscripten) using OpenAL for audio I/O. The first iteration focuses on WAV playback via a WavSource block and must support both local files and HTTP(S) URLs. This requires extending FileIO to present a unified local/remote interface.

Goals — Audio

  • Implement audio playback with play / pause / stop.
  • Use OpenAL as the backend.
  • Ship WavSource first (other formats later).
  • Support local and remote inputs via the updated FileIO.

Goals — FileIO

  • Provide a single API for Native & WASM and local filesystem & HTTP(S):
    • WASM local filesystem: handle required user UI interactions to grant browser FS access.
    • WASM file sink using download mechanism (user UI interactions).
    • Support Long-polling and WebSocket inputs.
  • Single entry point: accept a path or URL and choose the backend based on the scheme.

Technology

  • WASM: Emscripten Fetch API.
  • Native: libcurl / cpr.

Already in GR4

  • HttpBlock (native via cpp-httplib, WASM via Fetch API).
  • BasicFileIo (BasicFileSource / BasicFileSink) — local filesystem only.

Design Discussion

Two options were considered.

Option A - IO-thread callback (discouraged)

Subscribe and receive data chunks via a callback. Data is valid only during the callback; copy if you need to keep it.

Subscription subscribe(std::string urlOrPath, StreamOptions opts, StreamCallback callback);

Note: Running user code on the IO thread is risky; misuse can cause multithreading issues. This is the main reason to avoid exposing a general callback API.

Option B — CircularBuffer wrapper (recommended)

Subscription wraps a CircularBuffer<gr::Message> and offers convenience methods.

Subscription subscribe(std::string urlOrPath, StreamOptions opts);
  • When used inside a gr::Block, the Subscription can be polled and consumed in processBulk.
  • Note: gr::Message is used to align with OpenCMW’s Majordomo MDP protocol frame structure.

Open questions:

  • Should Subscription also implement a lightweight notification/wakeup (e.g. using std::condition_variable and std::mutex) instead of pure polling?
  • Problem: the current CircularBuffer implementation does not support notifications.

Performance optimisation:

  • Consider CircularBuffer<std::byte> internally and have Subscription expose gr::Messages. This can reduce copying between threads.

Error Handling

Use std::expected where applicable instead of exceptions.

Using cpr

  • Build-time feature flags: detect libcurl/cpr; if missing, build without web support and throw a clear runtime error on use.

  • Integrate via FetchContent:

include(FetchContent)
FetchContent_Declare(cpr
  GIT_REPOSITORY https://github.com/libcpr/cpr.git
  GIT_TAG 1.10.5) # pick a release: https://github.com/libcpr/cpr/releases
FetchContent_MakeAvailable(cpr)

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

🏗 In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions