Skip to main content

ceres/
engine.rs

1// build_synth.rs is the entrypoint for the audio engine
2// the AudioEngine object owns and manages the audio engine thread, and contains all the cpal logic.
3use crate::core::*;
4use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
5use crossbeam::channel::Sender;
6
7pub struct Engine<E: Clone + Copy + Send + 'static> {
8    pub tx: Sender<E>,
9    stream: cpal::platform::Stream,
10}
11
12impl<E> Engine<E> 
13where 
14    E: Clone + Copy + Send + 'static,
15{
16    pub fn new<F>(f: F) -> Self 
17    where
18        F: for<'a> FnOnce(Builder<E>) -> Runtime<E>,
19    {
20        let (event_bus, builder) = new::<E>();
21        let EventBus{tx, rx} = event_bus;
22        
23        // cpal setup
24        let host = cpal::default_host();
25        let device = host.default_output_device()
26            .ok_or("no output device available").unwrap();
27        let config = device.default_output_config().unwrap();
28        let sample_rate = config.sample_rate().0 as f32;
29        let mut runtime = f(builder);
30
31        Engine {
32            tx,
33            stream: device.build_output_stream(
34                &config.into(),
35                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
36
37                    let input = vec![0.0; data.len()];
38
39                    for (input_chunk, output_chunk) in input.chunks(256).zip(data.chunks_mut(256)) {
40                        if let Ok(event) = rx.try_recv() {
41                        runtime.tick(sample_rate, Some(event), &input_chunk, output_chunk);
42                        } else {
43                            runtime.tick(sample_rate, None, &input_chunk, output_chunk)
44                        }
45                    }
46                },
47                |err| eprintln!("Audio stream error: {}", err),
48                None,
49            ).unwrap(),
50        }
51    }
52
53    pub fn run(&self) {
54        self.stream.play().unwrap();
55    }
56}