Shared mutable reference/ownership in realtime contexts. Mutex/RwLock always necessary?

I wanted to try out the sample crate to implement a very basic jack-audio application: an oscillator that reads MIDI messages and changes the frequency accordingly.

The Signal-Trait has some very useful functions to generate a stream of samples, e.g. a 440 Hz sine wave:

let sine = signal::rate(44100.0).const_hz(440.0).sine();
// sine iterator yields: [0.0] ... [0.5] ...

It is possible to change the frequency on the fly with a closure:

let sine = signal::rate(44100.0).hz(signal::gen(|| 440.0)).sine();

Using the show-midi example from the jack crate I came up with a simple implementation. Just share a frequency variable freq, read midi messages from jack, change frequency accordingly and render the samples.

The problem is that the signal implementation always takes ownership and I don't really know how to solve this, as I'd like to avoid the runtime costs of a Mutex/RwLock due to realtime constraints (and I'd like to port this to bare metal ARM in the future).

As the jack/sample crates are not available at the playground I tried to nail this down to a simple example, please see here: Playground

The example should print 3 and 4 (simulating the note change after printing 3).

Full code of the jack application below:

full code
use std::convert::TryFrom;
use sample::signal;
use sample::signal::Signal;

use wmidi::MidiMessage;

fn main() {
    let (client, _status) =
        jack::Client::new("rust_jack_test", jack::ClientOptions::NO_START_SERVER)
        .unwrap();
    let midi = client
        .register_port("midi_in", jack::MidiIn::default())
        .unwrap();
    let mut out_port = client
        .register_port("out", jack::AudioOut::default())
        .unwrap();
    let sample_rate = client.sample_rate();

    let process = jack::ClosureProcessHandler::new({
        let mut freq = 440.0;
        let ref_freq = &mut freq;
        let mut signal = signal::rate(sample_rate as f64)
            .hz(signal::gen(move || [*ref_freq]))
            .saw();
        move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control {
            // get midi messages from jack
            let midi_in = midi.iter(ps);
            for e in midi_in {
                let res = MidiMessage::try_from(e.bytes).unwrap();
                match res {
                    MidiMessage::NoteOn(_, n, _) => {
                        // set new frequency
                        *ref_freq = n.to_freq_f64();
                    },
                    _ => {}
                }
            }
            // send samples to jack
            let out = out_port.as_mut_slice(ps);
            for v in out.iter_mut() {
                let [sig] = signal.next();
                *v = sig as f32;
            }
            jack::Control::Continue
        }
    });
    let active_client = client.activate_async((), process).unwrap();

    println!("Press any key to quit");
    let mut user_input = String::new();
    std::io::stdin().read_line(&mut user_input).ok();
}

If the type you're dealing with is an integer, you can use an atomic type.

Ah ok, while I chose an integer for the example implementation, the Signal-Trait is using f64.

Just convert the float to an integer using to_bits.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.