Rust Audio

Combining dsp-chain and nannou_audio

Hello there,

i would like to combine the dsp-chain and nannou_audio crate for a flexible slimlined rustisch audio-dsp playground.

But im not shure what would be the best way to pass/convert the buffer from the nannou_audio callback to the dsp.chain request_audio method. I found this solution but I am not satisfied with it.

let buffer = unsafe { &mut *(buffer.as_mut() as *mut [f32] as *mut [[f32; CHANNELS]]) };

Especially when dealing with more then one channel it doesnt work properly. Has someone an idea how to address this in a more robust and reliable way?

And one other thing is that i would like to use an InputStream as well. How would you combine both streams to use with a dsp-chain Graph?

Here is the the full code:

use dsp::{Frame, FromSample, Graph, Node, Sample};
use nannou_audio as audio;
use nannou_audio::Buffer;

const CHANNELS: usize = 1;
type MyFrame = [f32; CHANNELS];

fn main() {
    let mut graph = Graph::new();
    let test_tone = graph.add_node(Oscillator::default());
    graph.set_master(Some(test_tone));

    let audio_host = audio::Host::new();
    let stream = audio_host
        .new_output_stream(graph)
        .channels(CHANNELS)
        .render(
            |graph: &mut Graph<MyFrame, Oscillator>, buffer: &mut Buffer| {
                let sample_rate = buffer.sample_rate() as f64;
                let buffer = unsafe { &mut *(buffer.as_mut() as *mut [f32] as *mut [[f32; CHANNELS]]) };
                graph.audio_requested(buffer, sample_rate);
            },
        )
        .build()
        .unwrap();
    stream.play().unwrap();

    std::thread::sleep(::std::time::Duration::from_secs(1));
}

struct Oscillator {
    phase: f64,
    frequency: f64,
    amplitude: f32,
}

impl Default for Oscillator {
    fn default() -> Self {
        Oscillator {
            phase: 0.0,
            frequency: 200.,
            amplitude: 1.0,
        }
    }
}

impl Node<MyFrame> for Oscillator {
    fn audio_requested(&mut self, buffer: &mut [MyFrame], sample_rate: f64) {
        dsp::slice::map_in_place(buffer, |_| {
            let value = sine_wave(self.phase, self.amplitude);
            self.phase += self.frequency / sample_rate;
            Frame::from_fn(|_| value)
        });
    }
}

fn sine_wave<S: Sample>(phase: f64, amplitude: f32) -> S
where
    S: Sample + FromSample<f32>,
{
    use std::f64::consts::PI;
    ((phase * PI * 2.0).sin() as f32 * amplitude).to_sample::<S>()
}

Hi @Bendrien, welcome to this forum!

For those interested: when changing CHANNELS to 2 and running the application, I got the following error:

*** Error in `target/debug/rustaudio-question': free(): corrupted unsorted chunks: 0x00005623bc414200 ***

Some links: nannou's Buffer, dsp-chain's `AudioRequested.

I separated the suspicious part in a minimal application as follows:

fn main() {
    let mut input = vec![1.0_f32, 2.0_f32, 3.0_f32, 4.0_f32];
    let buffer_in = input.as_mut_slice();
    let buffer = unsafe { &mut *((buffer_in as *mut [f32]) as *mut [[f32; 2]]) };
}

I then ran it under miri and got the following error:

 --> src/main.rs:4:27
  |
4 |     let buffer = unsafe { &mut *((buffer_in as *mut [f32]) as *mut [[f32; 2]]) };
  |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Miri evaluation error: Memory access failed: pointer must be in-bounds at offset 32, but is outside bounds of allocation 531 which has size 16

When i change the 2 (which corresponds to CHANNELS) into 4, the offset becomes 64. So I think it has to do with the fact that in Rust, slices are “fat pointers” that take into account their length. What the application tries to do is to transmute a slice of f32 to a slice of [f32; CHANNELS] with the same length, which is an error.

What you could do instead is use the from_raw_parts_mut method as follows:

use std::slice;
let buffer = unsafe { 
    slice::from_raw_parts_mut(
        buffer.as_mut().as_ptr() as *mut [f32; CHANNELS],
        buffer.len() / CHANNELS
    
};

This works on my machine. That being said, I’m for from an “unsafe specialist”, so I’m not very comfortable with this either. I fixed my minimal application in the same way and then miri doesn’t complain anymore, so there is at least a little confidence.

Both nannou-audio or dsp-chain are from @mindtree, so maybe there is already a solution (or there are plans for one), but I’m not familiar with either crate.

1 Like