Files
doux-copy/doux-sova
2026-01-18 15:39:46 +01:00
..
2026-01-18 15:39:46 +01:00
2026-01-18 15:39:46 +01:00
2026-01-18 15:39:46 +01:00

doux-sova

Integration layer connecting Doux audio engine to Sova's AudioEngineProxy.

Quick Start with DouxManager

use doux_sova::{DouxManager, DouxConfig, audio};

// 1. List available devices (for UI)
let devices = audio::list_output_devices();
for dev in &devices {
    let default_marker = if dev.is_default { " [default]" } else { "" };
    println!("{}: {} (max {} ch){}", dev.index, dev.name, dev.max_channels, default_marker);
}

// 2. Create configuration
let config = DouxConfig::default()
    .with_output_device("Built-in Output")
    .with_channels(2)
    .with_buffer_size(512)
    .with_sample_path("/path/to/samples");

// 3. Create and start manager
let mut manager = DouxManager::new(config)?;
let proxy = manager.start(clock.micros())?;

// 4. Register with Sova
device_map.connect_audio_engine("doux", proxy)?;

// 5. Control during runtime
manager.hush();                // Release all voices
manager.panic();               // Immediately stop all voices
manager.add_sample_path(path); // Add more samples
manager.rescan_samples();      // Rescan all sample directories
let state = manager.state();   // Get AudioEngineState (voices, cpu, etc)
let scope = manager.scope_capture(); // Get oscilloscope buffer

// 6. Stop or restart
manager.stop();              // Stop audio streams
let new_config = DouxConfig::default().with_channels(4);
let new_proxy = manager.restart(new_config, clock.micros())?;

Low-Level API

For direct engine access without lifecycle management:

use std::sync::{Arc, Mutex};
use doux::Engine;
use doux_sova::create_integration;

// 1. Create the doux engine
let engine = Arc::new(Mutex::new(Engine::new(44100.0)));

// 2. Get initial sync time from Sova's clock
let initial_time = clock.micros();

// 3. Create integration - returns thread handle and proxy
let (handle, proxy) = create_integration(engine.clone(), initial_time);

// 4. Register proxy with Sova's device map
device_map.connect_audio_engine("doux", proxy)?;

// 5. Start your audio output loop using the engine
// (see doux/src/main.rs for cpal example)

Architecture

Sova Scheduler
    │
    ▼
AudioEngineProxy::send(AudioEnginePayload)
    │
    ▼ (crossbeam channel)
    │
SovaReceiver thread
    │ converts HashMap → command string
    ▼
Engine::evaluate("/s/sine/freq/440/gain/0.5/...")

Time Synchronization

Pass clock.micros() at engine startup. Sova's timetags (microseconds) are converted to engine time (seconds) relative to this initial value. Events with timetags go to Doux's scheduler for sample-accurate playback.

DouxConfig Methods

Method Description
with_output_device(name) Set output device by name
with_input_device(name) Set input device by name
with_channels(n) Set number of output channels
with_buffer_size(n) Set audio buffer size in frames
with_sample_path(path) Add a single sample directory
with_sample_paths(paths) Add multiple sample directories

DouxManager Methods

Method Description
start(time) Start audio streams, returns proxy
stop() Stop audio streams
restart(config, time) Restart with new configuration
hush() Release all voices gracefully
panic() Immediately stop all voices
add_sample_path(path) Add sample directory
rescan_samples() Rescan all sample directories
clear_samples() Clear sample pool
state() Returns AudioEngineState
is_running() Check if streams are active
sample_rate() Get current sample rate
channels() Get number of channels
config() Get current configuration
engine_handle() Access engine for telemetry
scope_capture() Get oscilloscope buffer

Re-exported Types

AudioEngineState

pub struct AudioEngineState {
    pub active_voices: u32,
    pub cpu_percent: f32,
    pub output_level: f32,
}

DouxError

Error type for manager operations (device not found, stream errors, etc).

ScopeCapture

Buffer containing oscilloscope data from scope_capture().

Parameters

All Sova Dirt parameters map directly to Doux event fields via Event::parse(). No manual mapping required - add new parameters to Doux and they work automatically.