Dan Quinn

Code Sketch - Audio Flow


Mostly taken from this feedback.rs nannou example, but uses a channel to send audio output to the view.

Nannou source code:

use nannou::noise::*;
use std::sync::mpsc;
use nannou::prelude::*;
use nannou_audio as audio;
use nannou_audio::Buffer;
use ringbuf::{Consumer, Producer, RingBuffer};

fn main() {
    nannou::app(model)
        .update(update)
        .run();
}

struct InputModel {
    producer: Producer<f32>,
}

struct OutputModel {
    tx: mpsc::Sender<f32>,
    consumer: Consumer<f32>,
}

struct Particle {
    pos: Vec2,
    vel: Vec2,
}

impl Particle {
    fn new(x: f32, y: f32) -> Particle {
        Particle {
            pos: vec2(x, y),
            vel: vec2(0.0, 0.0),
        }
    }

    fn update(&mut self, dir: Vec2, max: f32) {
        self.pos += self.vel;
        self.vel += dir / 50.0 * (max * 100.0);
        self.vel *= 0.95;
    }
}

struct Model {
    scale: u32,
    cols: u32,
    rows: u32,
    noise: Perlin,
    particles: Vec<Particle>,
    vectors: Vec<Vec2>,
    max: f32,
    amplitudes: Vec<f32>,
    rx: mpsc::Receiver<f32>,
    in_stream: audio::Stream<InputModel>,
    out_stream: audio::Stream<OutputModel>,
}

fn model(app: &App) -> Model {
    // Create a new window! Store the ID so we can refer to it later.
    let width = 200;
    let height = 200;
    let scale = 10;
    app.new_window()
        .size(width, height)
        .view(view)
        .build()
        .unwrap();
    let win_r = app.main_window().rect();
    let cols = width / scale;
    let rows = height / scale;
    let mut noise = Perlin::new();
    noise = noise.set_seed(1);
    let mut vectors = vec![];
    for _i in 0..(rows * cols) {
        vectors.push(vec2(0.1, 0.1));
    }
    let mut particles = vec![];
    for _i in 0..500 {
        let x = map_range(random(), 0.0, 1.0, win_r.left(), win_r.right());
        let y = map_range(random(), 0.0, 1.0, win_r.bottom(), win_r.top());
        particles.push(Particle::new(x, y));
    }

    // Initialise the audio host so we can spawn an audio stream.
    let audio_host = audio::Host::new();

    // Create a ring buffer and split it into producer and consumer
    let latency_samples = 1024;
    let ring_buffer = RingBuffer::<f32>::new(latency_samples * 2); // Add some latency
    let (mut prod, cons) = ring_buffer.split();
    for _ in 0..latency_samples {
        // The ring buffer has twice as much space as necessary to add latency here,
        // so this should never fail
        prod.push(0.0).unwrap();
    }

    let (tx, rx) = mpsc::channel();

    // Create input model and input stream using that model
    let in_model = InputModel { producer: prod };
    let in_stream = audio_host
        .new_input_stream(in_model)
        .capture(pass_in)
        .build()
        .unwrap();

    // Create output model and output stream using that model
    let out_model = OutputModel { consumer: cons, tx };
    let out_stream = audio_host
        .new_output_stream(out_model)
        .render(pass_out)
        .build()
        .unwrap();

    in_stream.play().unwrap();
    out_stream.play().unwrap();

    Model {
        scale,
        cols,
        rows,
        noise,
        particles,
        vectors,
        max: 0.0,
        amplitudes: [0.0; 10].to_vec(),
        rx,
        in_stream,
        out_stream,
    }
}

fn pass_in(model: &mut InputModel, buffer: &Buffer) {
    for frame in buffer.frames() {
        for sample in frame {
            model.producer.push(*sample).ok();
        }
    }
}

fn pass_out(model: &mut OutputModel, buffer: &mut Buffer) {
    for frame in buffer.frames_mut() {
        for sample in frame {
            let recorded_sample = match model.consumer.pop() {
                Some(f) => f,
                None => 0.0,
            };
            *sample = recorded_sample;
            if recorded_sample > 0.0 {
                model.tx.send(recorded_sample).unwrap();
            }
        }
    }
}

fn key_pressed(_app: &App, model: &mut Model, key: Key) {
    match key {
        Key::Space => {
            if model.in_stream.is_paused() {
                model.in_stream.play().unwrap();
                model.out_stream.play().unwrap();
            } else {
                model.in_stream.pause().unwrap();
                model.out_stream.pause().unwrap();
            }
        }
        _ => {}
    }
}

fn update(app: &App, model: &mut Model, _update:Update) {
    let win = app.main_window();
    let win_r = win.rect();
    let width = win_r.right() - win_r.left();
    let height = win_r.top() - win_r.bottom();
    let mut yoff = 0.0;
    let zoff = app.time / 5.0;

    model.max *= 0.9;

    for x in model.rx.try_iter() {
        if x > model.max {
            model.max = x;
        }
    }

    for col in 0..model.cols {
        let mut xoff = 0.0;
        for row in 0..model.rows {
            let random = model.noise.get([xoff as f64, yoff as f64, zoff as f64]);
            let angle = map_range(random, 0.0, 1.0, 0.0, 360.0).to_radians();
            model.vectors[(row * model.cols + col) as usize] = vec2(0.0, 1.0).rotate(angle as f32);
            xoff += 0.1;
        }
        yoff += 0.1;
    }

    for particle in &mut model.particles {
        if particle.pos[0] <= win_r.left() {
            particle.pos[0] = win_r.right() - 1.0;
        } else if particle.pos[0] >= win_r.right() {
            particle.pos[0] = win_r.left();
        }
        if particle.pos[1] <= win_r.bottom() {
            particle.pos[1] = win_r.top() - 1.0;
        } else if particle.pos[1] >= win_r.top() {
            particle.pos[1] = win_r.bottom();
        }

        let mapped_x = map_range(particle.pos[0], win_r.left(), win_r.right(), 0.0, width);
        let mapped_y = map_range(particle.pos[1], win_r.bottom(), win_r.top(), 0.0, height);
        let row = (mapped_y / model.scale as f32).floor();
        let col = (mapped_x / model.scale as f32).floor();
        let vec = model.vectors[(row * model.cols as f32 + col) as usize];
        particle.update(vec, model.max);
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let win = app.main_window();
    let win_r = win.rect();
    let draw = app.draw();
    let width = win_r.right() - win_r.left();
    let height = win_r.top() - win_r.bottom();
    let line_length = width / model.cols as f32;

    if frame.nth() == 0 {
        draw.background().color(BLACK);
    }
    
    draw.rect()
        .w_h(width, height)
        .color(rgba(0.0, 0.0, 0.0, 0.05));

    for col in 0..model.cols {
        for row in 0..model.rows {
            let vec = model.vectors[(row * model.cols + col) as usize];
            let start_x = map_range(col as f32, 0.0, model.cols as f32, win_r.left(), win_r.right());
            let start_y = map_range(row as f32, 0.0, model.rows as f32, win_r.bottom(), win_r.top());
            let start = pt2(start_x, start_y);
            let end = start + pt2(line_length as f32 * vec.angle().cos(), line_length as f32 * vec.angle().sin());
            draw.line()
                .start(start)
                .end(end)
                .weight(1.0)
                .color(hsl(model.max, 1.0, 0.05));
        }
    }

    for particle in &model.particles {
        draw.ellipse()
            .xy(particle.pos)
            .w_h(3.0, 3.0)
            .color(hsl(model.max, 1.0, 0.5));
    }

    let size = model.max * 100.0;

    draw.ellipse()
        .w_h(size, size)
        .color(WHITE);

    draw.to_frame(app, &frame).unwrap();
    
    if frame.nth() < 300 {
        let file_path = captured_frame_path(app, &frame);
        app.main_window().capture_frame(file_path);
    }
}

fn captured_frame_path(app: &App, frame: &Frame) -> std::path::PathBuf {
    app.project_path()
        .expect("failed to locate `project_path`")
        .join("frames")
        .join(format!("{:04}", frame.nth()))
        .with_extension("png")
}