Cosmakes158 downloadsA tabletop control console: a 4-channel Web Audio soundboard with loadouts, scenes and live meters.
A tabletop control console that lives in an Obsidian pane. Two tabs:
All sounds and maps live in your vault. Settings persist via data.json.
This plugin folder is already a working dev checkout.
npm install # if node_modules is missing
npm run dev # watch build → main.js
# or a one-off production build:
npm run build
Then enable DnDJay Soundboard & Battlemap in Obsidian → Settings → Community plugins, and open the console from the ribbon (the waveform icon) or the command palette → DnDJay: Open console.
Loadout row — pick the active loadout. Each loadout has two 12-pad banks — one for looping beds, one for one-shots — both mapped in settings.
Pads (12) — laid out 4 × 3. Behaviour depends on the active mode:
| Mode | Tap a pad… |
|---|---|
| A bed channel (1–3) selected | loads the bed bank sound onto that channel as a looping bed. |
| The Quick channel (4) selected | the pads flip to the one-shot bank; a tap fires that sound once through channel 4. |
| Prepare scene | stages the bed-bank sound as the selected bed channel's incoming sound. |
Whether a normally-loaded pad starts immediately or just arms (waiting for the channel's Resume) is the Autoplay setting.
Mixer — a left icon rail beside four channel strips. Click a strip to select it as the pad target. Channels are named Channel 1–3 and Effect by default; rename them in settings.
A ? button at the top-right of each tab opens a help popup listing every control.
Scenes & colour. In prepare mode, pads currently playing are tinted with their channel's colour; a pad you stage shows a lighter "incoming" shade of the target channel's colour. Trigger switch crossfades each staged channel (≈2 s for smooth, instant for sudden); channels you didn't stage are kept or stopped per the setting.
Hidden in the current release. The Battlemap tab is feature-complete in code but still being refined, so it's hidden from the view for now (set
BATTLEMAP_ENABLED = trueinsrc/view/ConsoleView.tsto bring it back). The rest of this section describes it for when it returns.
image/video badge, and an on air marker on the one showing.Configure the Pi host/IP and port in settings, and use Test connection to check reachability.
The plugin is the remote; a small agent on the Pi caches and renders what it's
sent. A reference agent lives in pi-agent/ and implements the
protocol in the plan's §6 (assets identified by content hash, so a map is never
re-uploaded once cached).
Quick start on a Pi (Raspberry Pi OS Lite, KMS enabled via
dtoverlay=vc4-kms-v3d):
sudo apt install mpv python3-pip
pip install -r pi-agent/requirements.txt
# 1) fullscreen mpv with an IPC socket (headless DRM)
mpv --idle=yes --force-window=yes --fullscreen --keep-open=yes \
--image-display-duration=inf --vo=gpu --gpu-context=drm \
--input-ipc-server=/tmp/mpv-dndjay.sock &
# 2) the agent
python3 pi-agent/agent.py --host 0.0.0.0 --port 8765
For boot-to-ready operation, install the two bundled systemd units
(pi-agent/mpv-dndjay.service, pi-agent/dndjay-agent.service) and
avahi-daemon so the Pi answers to raspberrypi.local.
The agent converts the plugin's intuitive wire units to mpv:
video-zoom = log2(scale), video-pan-x/y = x/y, video-rotate = rotate.
On Android/iOS, Obsidian runs the plugin in a webview tied to the host app's
lifecycle. When Obsidian is backgrounded, the webview (and its AudioContext)
is likely throttled or suspended, so audio may pause when you switch apps.
On desktop this does not happen. This is a platform constraint and is not worked
around; the battlemap/Pi half is unaffected.
The audio engine is platform-split (AudioEngine is a thin facade over two
backends):
Desktop (BufferBackend) decodes every sound to an AudioBuffer (cached
by path) and plays it through AudioBufferSourceNodes. This is deliberate: a
media element wrapped in a MediaElementAudioSourceNode is silenced on desktop
by Chromium's autoplay policy and by CORS taint, whereas decoded buffers always
play and feed the real VU meters. The trade-off is memory — a decoded buffer
is uncompressed PCM (~2× the file size), so a long bed costs tens to hundreds of
MB. Desktop has the headroom; buffers are cached so re-triggering is instant.
Mobile (ElementBackend) streams every sound from the vault resource path
through a bare <audio> element, played directly (not through Web Audio).
Decoding a multi-minute uncompressed bed would allocate hundreds of MB at once
and crash the tablet WebView, so mobile never decodes — memory stays flat
regardless of track length. Because streamed audio isn't in an analyser graph,
VU meters are inactive on mobile (and hidden in the UI); faders, mute,
stop/resume and scene crossfades all still work. One-shots stream too.
Note: decoded size scales with duration, not codec — re-encoding a long bed to MP3 shrinks the file but not the decoded buffer, so on desktop very long beds are still memory-heavy. Prefer reasonably short, looping beds where you can.
src/
main.ts Plugin: singletons (AudioEngine, ProjectorClient,
VaultMedia), view/command/ribbon/settings registration
model.ts Data interfaces, channel themes, defaults, normalisation
media/VaultMedia.ts Vault file listing + raw audio bytes / resource paths
audio/AudioEngine.ts Facade: picks a backend per platform; delegates calls
audio/types.ts PlaybackBackend interface + scene types
audio/BufferBackend.ts Desktop: Web Audio graph, decoded beds/one-shots, meters
audio/ElementBackend.ts Mobile: streamed <audio> playback (no decode, no meters)
audio/Meter.ts One rAF loop → per-channel meter levels (desktop)
projector/ProjectorClient.ts WebSocket client (§6), reconnect, upload, throttle
view/ConsoleView.ts ItemView: tab bar + bodies
view/MusicTab.ts Loadout row, pads, mixer, left icon rail
view/BattlemapTab.ts Library + framing stage + status bar
view/VerticalFader.ts Custom coloured vertical fader
view/modals.ts Help + scene-settings popups
view/help.ts Per-tab help content
settings/SettingsTab.ts Channel names, pad banks, loadouts, scene defaults, Pi
pi-agent/ Reference Python display agent + systemd units