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")
}