Crate klingt

Crate klingt 

Source
Expand description

§Klingt

A lock-free audio graph library with message-passing parameter control.

§Quick Start

The simplest way to play audio is with [Klingt::default_output]:

use klingt::{Klingt, nodes::Sine};

// Create engine with default audio device
let mut klingt = Klingt::default_output().expect("No audio device");

// Add a sine oscillator and connect to output
let sine = klingt.add(Sine::new(440.0));
klingt.output(&sine);

// Main audio loop - call process() repeatedly
loop {
    klingt.process();
    std::thread::sleep(std::time::Duration::from_micros(500));
}

§Core Concepts

§Nodes and Handles

Audio processing is done by nodes that implement AudioNode. When you add a node to Klingt, you get back a Handle that lets you:

let mut sine = klingt.add(Sine::new(440.0));
let gain = klingt.add(Gain::new(0.5));

klingt.connect(&sine, &gain);
klingt.output(&gain);

// Change frequency at runtime (lock-free!)
sine.send(SineMessage::SetFrequency(880.0)).ok();

§Automatic Sample Rate Conversion

Klingt automatically handles sample rate mismatches. If you add a node that has a different native sample rate (like a pre-decoded audio file), Klingt creates a sub-graph at that rate and resamples to match the output device:

// Audio file at 48000Hz + device at 44100Hz = automatic resampling
let player = SamplePlayer::new(samples, 2, 48000);
let handle = klingt.add(player); // Sub-graph created automatically
klingt.output(&handle);          // Routed through resampler

§Message Passing (No Locks!)

All parameter updates use lock-free ring buffers. The audio thread never blocks waiting for the main thread. Messages are processed at the start of each audio block (64 samples by default).

§Built-in Nodes

See the nodes module for available nodes:

§Custom Nodes

Implement AudioNode to create your own nodes. Here’s a complete example of a square wave oscillator with message-based parameter control:

use klingt::{AudioNode, ProcessContext};
use dasp_graph::{Buffer, Input};

// Define messages for runtime parameter control
#[derive(Clone, Copy, Debug)]
pub enum SquareMessage {
    SetFrequency(f32),
    SetPulseWidth(f32),  // 0.0 to 1.0, where 0.5 is a standard square
    SetAmplitude(f32),
}

pub struct Square {
    frequency: f32,
    pulse_width: f32,
    amplitude: f32,
    phase: f32,
}

impl Square {
    pub fn new(frequency: f32) -> Self {
        Self {
            frequency,
            pulse_width: 0.5,
            amplitude: 0.25,
            phase: 0.0,
        }
    }
}

impl AudioNode for Square {
    type Message = SquareMessage;

    fn process(
        &mut self,
        ctx: &ProcessContext,
        messages: impl Iterator<Item = SquareMessage>,
        _inputs: &[Input],
        outputs: &mut [Buffer],
    ) {
        // 1. Handle messages first (parameter updates)
        for msg in messages {
            match msg {
                SquareMessage::SetFrequency(f) => self.frequency = f.max(0.0),
                SquareMessage::SetPulseWidth(pw) => self.pulse_width = pw.clamp(0.0, 1.0),
                SquareMessage::SetAmplitude(a) => self.amplitude = a.clamp(0.0, 1.0),
            }
        }

        // 2. Generate audio
        let phase_inc = self.frequency / ctx.sample_rate as f32;
         
        for sample in outputs[0].iter_mut() {
            // Square wave: high when phase < pulse_width, low otherwise
            *sample = if self.phase < self.pulse_width {
                self.amplitude
            } else {
                -self.amplitude
            };

            // Advance and wrap phase
            self.phase += phase_inc;
            if self.phase >= 1.0 {
                self.phase -= 1.0;
            }
        }
    }

    fn num_outputs(&self) -> usize { 1 }
}

Then use it like any built-in node:

let mut square = klingt.add(Square::new(440.0));
klingt.output(&square);

// Modulate pulse width for PWM synthesis
square.send(SquareMessage::SetPulseWidth(0.25)).ok();

§Node Types

The three types of nodes differ by their input/output counts:

TypeInputsOutputsExamples
Source01+Oscillators, sample players
Effect1+1+Gain, filters, delays
Sink1+0Audio output, recorders

Override num_inputs and num_outputs to define your node’s channel configuration.

§Feature Flags

  • cpal_sink - Enable CPAL audio output (adds [CpalDevice] and CpalSink)
  • std - Enable standard library (enabled by default)

§Design Principles

  • Lock-free audio thread: No allocations, no Arc/Mutex on the hot path
  • Automatic resampling: Nodes at different sample rates just work
  • Fixed block size: 64 samples per block (from dasp_graph)

Modules§

nodes
Built-in audio nodes.

Structs§

Handle
A handle for sending messages to a node in the audio graph.
Klingt
The main audio engine - manages nodes, connections, and audio processing.
NodeId
Unique identifier for a node within a graph.
ProcessContext
Information available during audio processing.

Traits§

AudioNode
The core trait for audio processing nodes.