timing in jack audio is unstable (jack bindings for rust)

59 Views Asked by At

I'm having some problems using the jack bindings for Rust.
I tried to make a simple synthesizer. It works decent at startup but the notes are out of tune and they change over time. Furthermore, the audio just stops working after about 5 minutes. Adding some debug code shows that my global time variable is a bit out of whack. Every time I run the code it starts off fine, then delays, then runs ahead, then grinds to a halt and the audio stops. I made a minimal example of simply playing a single tone and it has the same problem (but possibly to a lesser extent). My best guess is that the sample rate might be changing and jack doesn't realize because I'm using pipewire?
htop shows normal resource use.
Here's my minimal example:

use jack;
use std::f32::consts::TAU;
use std::io::stdin;

fn main() {
    start_synth();
}

fn start_synth() {
    // Define the jack device
    let (client, _status) =
        jack::Client::new("SimpleSynth", jack::ClientOptions::NO_START_SERVER).unwrap();

    // Define audio output
    let mut audio_out = client
        .register_port("audio", jack::AudioOut::default())
        .unwrap();

    // The global time
    let mut time: f32 = 0.0;

    // Define the frequency
    let frequency: f32 = 440.0;

    // Create a jack process handler
    let handler = jack::ClosureProcessHandler::new(
        move |client: &jack::Client, process_scope: &jack::ProcessScope| -> jack::Control {
            
            // I think the sample rate can dynamically change so I put it
            // inside the process handler.
            // Monitoring always shows 48000 so it's probably not necessary.
            let time_per_sample: f32 = 1.0 / (client.sample_rate() as f32);

            // Put notes in buffer
            let buffer = audio_out.as_mut_slice(process_scope);

            for sample in buffer.iter_mut() {
                *sample = (time * frequency * TAU).sin();
                time += time_per_sample;
            }

            // Tell jack to continue
            jack::Control::Continue
        },
    );

    // Activate the jack process
    let _active_client = client.activate_async((), handler).unwrap();

    // Print messages
    loop {
        let mut input_text = String::new();
        stdin().read_line(&mut input_text).expect("error");
    }
}
1

There are 1 best solutions below

0
iHnR On

I figured it out!

And of course it was floating point numbers.

Here's an updated version of the example that uses integer time and runs perfectly fine.

use jack;
use std::f32::consts::TAU;
use std::io::stdin;

fn main() {
    start_synth();
}

fn start_synth() {
    // Define the jack device
    let (client, _status) =
        jack::Client::new("SimpleSynth", jack::ClientOptions::NO_START_SERVER).unwrap();

    // Define audio output
    let mut audio_out = client
        .register_port("audio", jack::AudioOut::default())
        .unwrap();

    // The global time
    let mut time: usize = 0;

    // Frequency of the single note.
    let frequency: f32 = 440.0;

    // The client's sample rate. I assume it will stay contant.
    let buffer_size = client.buffer_size();
    let mut sample_rate = client.sample_rate();

    // Create a jack process handler
    let handler = jack::ClosureProcessHandler::new(
        move |client: &jack::Client, process_scope: &jack::ProcessScope| -> jack::Control {
            if sample_rate != client.sample_rate() {
                sample_rate = client.sample_rate();
                println!("Sample rate changed to {sample_rate}");
            }

            let angular_frequency = frequency * TAU / sample_rate as f32;

            // Put notes in buffer
            let buffer = audio_out.as_mut_slice(process_scope); // Get the buffer
            for sample in buffer.iter_mut() {
                *sample = (time as f32 * angular_frequency).sin();
                time += 1;
            }

            // Tell jack to continue
            jack::Control::Continue
        },
    );

    // Activate the jack process
    let _active_client = client.activate_async((), handler).unwrap();

    // Print messages
    loop {
        let mut input_text = String::new();
        stdin().read_line(&mut input_text).expect("error");
    }
}