audio_midi_shell/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::{collections::VecDeque, sync::mpsc};
5
6use midir::{MidiInput, MidiInputConnection};
7use tinyaudio::{OutputDevice, OutputDeviceParameters, run_output_device};
8
9/// Shell running the audio and MIDI processing.
10pub struct AudioMidiShell {
11    /// MIDI connections.
12    pub midi_connections: MidiConnections,
13
14    /// Output device:
15    pub output_device: OutputDevice,
16}
17
18impl AudioMidiShell {
19    /// Initializes the MIDI inputs, the output device and runs the generator in a callback.
20    /// It returns a shell object that must be kept alive.
21    /// - `sample_rate` is the sampling frequency in Hz.
22    /// - `buffer_size` is the number of frames used by the system buffer.
23    ///   This setting determines the latency.
24    /// - `process_chunk_size` is the number of frames passed to the `process` function.
25    pub fn spawn(
26        sample_rate: u32,
27        buffer_size: usize,
28        process_chunk_size: usize,
29        mut generator: impl AudioGenerator + Send + 'static,
30    ) -> Self {
31        let (midi_sender, midi_receiver) = mpsc::channel();
32        let midi_connections = init_midi(midi_sender);
33
34        generator.init(process_chunk_size);
35
36        let params = OutputDeviceParameters {
37            channels_count: 2,
38            sample_rate: sample_rate as usize,
39            channel_sample_count: buffer_size,
40        };
41
42        let mut out_samples = VecDeque::with_capacity(process_chunk_size);
43
44        let output_device = run_output_device(params, move |data| {
45            for samples in data.chunks_mut(params.channels_count) {
46                if out_samples.is_empty() {
47                    while let Ok(message) = midi_receiver.try_recv() {
48                        generator.process_midi(message.1.as_ref(), message.0);
49                    }
50
51                    let mut frames = vec![[0.0; 2]; process_chunk_size];
52                    generator.process(&mut frames);
53
54                    for frame in frames.iter().take(process_chunk_size) {
55                        out_samples.push_back(*frame);
56                    }
57                }
58
59                if let Some(s) = out_samples.pop_front() {
60                    samples[0] = s[0];
61                    samples[1] = s[1];
62                }
63            }
64        })
65        .unwrap();
66
67        Self {
68            midi_connections,
69            output_device,
70        }
71    }
72
73    /// Spawns the shell and keeps it alive forever.
74    /// - `sample_rate` is the sampling frequency in Hz.
75    /// - `buffer_size` is the number of samples used by the system buffer.
76    ///   This setting determines the latency.
77    /// - `process_chunk_size` is the number of samples passed to the `process` function.
78    pub fn run_forever(
79        sample_rate: u32,
80        buffer_size: usize,
81        process_chunk_size: usize,
82        generator: impl AudioGenerator + Send + 'static,
83    ) -> ! {
84        let _shell = Self::spawn(sample_rate, buffer_size, process_chunk_size, generator);
85
86        loop {
87            std::thread::sleep(std::time::Duration::from_millis(100));
88        }
89    }
90}
91
92/// Trait to be implemented by structs that are passed as generator to the shell.
93pub trait AudioGenerator {
94    /// Initializes the generator. Called once on invocation.
95    /// - `process_chunk_size` is the number of frames passed to the `process` function.
96    fn init(&mut self, _process_chunk_size: usize) {}
97
98    /// Generates a chunk of samples.
99    /// - `frames` is a buffer of `process_chunk_size` elements.
100    ///   It is initialized to `[0.0; 2]` and must be filled with sample data.
101    ///   Index `0` of each element is the left channel, index `1` the right channel.
102    fn process(&mut self, frames: &mut [[f32; 2]]);
103
104    /// Processes a MIDI message.
105    fn process_midi(&mut self, _message: &[u8], _timestamp: u64) {}
106}
107
108/// Vector of MIDI connections with an attached mpsc sender.
109type MidiConnections = Vec<MidiInputConnection<mpsc::Sender<(u64, Vec<u8>)>>>;
110
111/// Connects all available MIDI inputs to an mpsc sender and returns them in a vector.
112fn init_midi(sender: mpsc::Sender<(u64, Vec<u8>)>) -> MidiConnections {
113    let mut connections = MidiConnections::new();
114
115    let input = MidiInput::new(&(env!("CARGO_PKG_NAME").to_owned() + " scan input"))
116        .expect("MIDI Input error");
117
118    for port in input.ports().iter() {
119        let input = MidiInput::new(&(env!("CARGO_PKG_NAME").to_owned() + " input"))
120            .expect("MIDI Input error");
121        let port_name = input.port_name(port).unwrap();
122        log::info!("Connecting to MIDI input {}", port_name);
123        let conn = input
124            .connect(
125                port,
126                port_name.as_str(),
127                |timestamp, message, sender| {
128                    sender.send((timestamp, Vec::from(message))).ok();
129                },
130                sender.clone(),
131            )
132            .ok();
133        connections.push(conn.unwrap());
134    }
135
136    connections
137}