use std::{
collections::HashMap,
fs,
sync::{
mpsc::{self, Receiver, SendError, SyncSender},
Arc,
},
time::{SystemTime, UNIX_EPOCH},
};
use crate::{
grid::Grid,
postproc::{Effect, FIRBuilder, Reverb},
sampler::{Sample, SampleSet},
util::FromNode,
};
const PLAYABLES: [&str; 1] = ["grid"];
#[derive(Debug)]
pub enum Playable {
Grid(Grid),
}
pub struct Pipeline {
pub playables: HashMap<String, Playable>,
pub mix: HashMap<String, f32>,
pub time: u128,
sample_rate: u32,
sink: SyncSender<f32>,
effects: Vec<Effect>,
}
pub struct PipelineConfig {
pub samples_dir: String,
}
fn get_samples(config: &PipelineConfig) -> HashMap<String, Arc<Sample>> {
let paths = fs::read_dir(config.samples_dir.clone()).unwrap();
let mut samples = HashMap::new();
for path in paths {
let path = path.unwrap();
let name = path.path();
let name = name.file_stem().unwrap().to_str().unwrap();
let Some(sample) = Sample::try_new(&path.path()) else {
continue;
};
let sample = Arc::new(sample);
samples.insert(name.to_string(), sample);
}
samples
}
fn rescale_mix(mix: &mut HashMap<String, f32>) {
let total: f32 = mix.values().sum();
for (_name, value) in mix.iter_mut() {
*value /= total;
}
}
impl Pipeline {
pub fn from_tree(
tree: &tree_sitter::Tree,
source: &str,
config: Option<&PipelineConfig>,
) -> (Self, Receiver<f32>) {
let mut playables: HashMap<String, Playable> = HashMap::new();
let mut samples = HashMap::new();
if let Some(config) = config {
samples = get_samples(config);
}
let samples = SampleSet { samples };
let mut cursor = tree.root_node().walk();
for node in tree.root_node().children(&mut cursor) {
if PLAYABLES.iter().any(|&p| p == node.kind()) {
let name = node.child_by_field_name("name").unwrap();
let name = name.utf8_text(source.as_bytes()).unwrap();
let playable = match node.kind() {
"grid" => {
let grid = Grid::from_node(&node, source).unwrap();
Playable::Grid(grid)
}
_ => panic!("Unknown playable"),
};
playables.insert(name.to_string(), playable);
}
}
let mut mix = playables
.keys()
.map(|i| (i.to_string(), 1.0))
.collect::<HashMap<String, f32>>();
let mut cursor = tree.root_node().walk();
for node in tree.root_node().children(&mut cursor) {
if node.kind() == "map" {
let target = node.child_by_field_name("name").unwrap();
let target = target.utf8_text(source.as_bytes()).unwrap();
let playable = playables.get_mut(target).unwrap();
match playable {
Playable::Grid(g) => g.map_from_node(&node, source, &samples),
}
} else if node.kind() == "tempo" {
let bpm = node.child_by_field_name("bpm").unwrap();
let bpm = bpm.utf8_text(source.as_bytes()).unwrap();
let count = node.child_by_field_name("count").unwrap();
let count = count.utf8_text(source.as_bytes()).unwrap();
let note = node.child_by_field_name("note").unwrap();
let note = note.utf8_text(source.as_bytes()).unwrap();
playables
.iter_mut()
.filter(|x| match x.1 {
Playable::Grid(_) => true,
})
.for_each(|(_, x)| match x {
Playable::Grid(g) => g.set_tempo_and_time(
bpm.parse().unwrap(),
(count.parse().unwrap(), note.parse().unwrap()),
),
});
} else if node.kind() == "speed" {
let target = node.child_by_field_name("name").unwrap();
let target = target.utf8_text(source.as_bytes()).unwrap();
let playable = playables.get_mut(target).unwrap();
let sign = node.child(2).unwrap();
let numer = sign.child_by_field_name("numer").unwrap();
let numer = numer.utf8_text(source.as_bytes()).unwrap();
let denom = sign.child_by_field_name("denom").unwrap();
let denom = denom.utf8_text(source.as_bytes()).unwrap();
let numer: i16 = numer.parse().unwrap();
let denom: i16 = denom.parse().unwrap();
match playable {
Playable::Grid(g) => g.set_note_length((numer as u32, denom as u32)),
}
} else if node.kind() == "mix" {
let target = node.child_by_field_name("name").unwrap();
let target = target.utf8_text(source.as_bytes()).unwrap();
let value = node.child_by_field_name("value").unwrap();
let value = value.utf8_text(source.as_bytes()).unwrap();
let value = value.parse().unwrap();
mix.insert(target.to_string(), value);
} else if node.kind() == "setter" {
let target = node.child_by_field_name("name").unwrap();
let target = target.utf8_text(source.as_bytes()).unwrap();
let _playable = playables.get_mut(target).unwrap();
let property = node.child_by_field_name("property").unwrap();
let _property = property.utf8_text(source.as_bytes()).unwrap();
let value = node.child_by_field_name("value").unwrap();
let _value = value.utf8_text(source.as_bytes()).unwrap();
}
}
rescale_mix(&mut mix);
let (s_tx, rx) = mpsc::sync_channel(2048);
let mut effects: Vec<Effect> = vec![];
let fir = FIRBuilder::new().low_pass(2800.0, 44100.0).build();
effects.push(Effect::FIR(fir));
let rev = Reverb::new();
effects.push(Effect::Reverb(rev));
(
Self {
playables,
mix,
time: 0,
sink: s_tx,
effects,
sample_rate: 44100,
},
rx,
)
}
pub fn set_output_config(&mut self, config: &cpal::SupportedStreamConfig) {
self.sample_rate = config.sample_rate().0;
}
pub fn update(&mut self, other: Pipeline) {
self.playables = other.playables;
self.mix = other.mix;
}
pub fn send_sample(&mut self) -> Result<(), SendError<f32>> {
let mut sample: f32 = 0.0;
for playable in self.playables.iter_mut() {
let dry = match playable.1 {
Playable::Grid(g) => {
let s = g.get_sample(self.time, self.sample_rate);
s * self.mix[playable.0]
}
};
sample += dry
}
self.time += 1;
for effect in self.effects.iter_mut() {
sample = match effect {
Effect::FIR(f) => f.process(sample),
Effect::Reverb(r) => r.process(sample),
}
}
let res = self.sink.send(sample);
log::trace!(
"pipeline, {}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
);
res
}
}
#[cfg(test)]
mod tests {
use super::*;
use tree_sitter::Parser;
fn get_test_tree() -> (String, tree_sitter::Tree) {
let source = include_str!("../testdata/pipeline_test.br");
let mut parser = Parser::new();
parser
.set_language(tree_sitter_breaker::language())
.unwrap();
let tree = parser.parse(source, None).unwrap();
(source.to_string(), tree)
}
#[test]
fn named_grid() {
let (source, tree) = get_test_tree();
let playables = Pipeline::from_tree(&tree, &source, None).0.playables;
assert!(
playables.len() == 1,
"Playables length is not 1, but {}",
playables.len()
);
assert!(playables.contains_key("veryfunname"));
}
}