From 9b0f93c92be9a8124f17a0bed100ba3cd7a7b46f Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Sun, 31 Jul 2022 08:15:48 +0200 Subject: [PATCH] Added TDD test for blocklang --- src/matrix.rs | 22 +++++++++++++++-- src/nodes/node_conf.rs | 54 +++++++++++++++++++++++++++++++++++++++++ tests/blocklang.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 tests/blocklang.rs diff --git a/src/matrix.rs b/src/matrix.rs index 1e1a408..26a70b8 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -10,6 +10,7 @@ pub use crate::nodes::MinMaxMonitorSamples; use crate::nodes::{NodeConfigurator, NodeGraphOrdering, NodeProg, MAX_ALLOCATED_NODES}; pub use crate::CellDir; use crate::ScopeHandle; +use crate::blocklang::BlockFun; use std::collections::{HashMap, HashSet}; @@ -578,20 +579,37 @@ impl Matrix { self.config.filtered_out_fb_for(ni, out) } + /// Retrieve the oscilloscope handle for the scope index `scope`. pub fn get_pattern_data(&self, tracker_id: usize) -> Option>> { self.config.get_pattern_data(tracker_id) } + /// Retrieve a handle to the tracker pattern data of the tracker `tracker_id`. pub fn get_scope_handle(&self, scope: usize) -> Option> { self.config.get_scope_handle(scope) } - /// Checks if pattern data updates need to be sent to the - /// DSP thread. + /// Checks if there are any updates to send for the pattern data that belongs to the + /// tracker `tracker_id`. Call this repeatedly, eg. once per frame in a GUI, in case the user + /// modified the pattern data. It will make sure that the modifications are sent to the + /// audio thread. pub fn check_pattern_data(&mut self, tracker_id: usize) { self.config.check_pattern_data(tracker_id) } + /// Checks the block function for the id `id`. If the block function did change, + /// updates are then sent to the audio thread. + /// See also [get_block_function]. + pub fn check_block_function(&mut self, id: usize) { + self.config.check_block_function(id) + } + + /// Retrieve a handle to the block function `id`. In case you modify the block function, + /// make sure to call [check_block_function]. + pub fn get_block_function(&mut self, id: usize) -> Option>> { + self.config.get_block_function(id) + } + /// Saves the state of the hexagonal grid layout. /// This is usually used together with [Matrix::check] /// and [Matrix::restore_matrix] to try if changes on diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index 99d7878..bd7bc21 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -6,6 +6,8 @@ use super::{ FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_CODE_ENGINES, MAX_AVAIL_TRACKERS, MAX_INPUTS, MAX_SCOPES, UNUSED_MONITOR_IDX, }; +use crate::blocklang::*; +use crate::blocklang_def; use crate::dsp::tracker::{PatternData, Tracker}; use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom}; use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT}; @@ -185,6 +187,10 @@ pub struct NodeConfigurator { /// Holding the WBlockDSP code engine backends: #[cfg(feature = "synfx-dsp-jit")] pub(crate) code_engines: Vec, + /// Holds the block functions that are JIT compiled to DSP code + /// for the `Code` nodes. The code is then sent via the [CodeEngine] + /// in [check_block_function]. + pub(crate) block_functions: Vec<(u64, Arc>)>, /// The shared parts of the [NodeConfigurator] /// and the [crate::nodes::NodeExecutor]. pub(crate) shared: SharedNodeConf, @@ -274,6 +280,12 @@ impl NodeConfigurator { let mut scopes = vec![]; scopes.resize_with(MAX_SCOPES, || ScopeHandle::new_shared()); + let lang = blocklang_def::setup_hxdsp_block_language(); + let mut block_functions = vec![]; + block_functions.resize_with(MAX_AVAIL_CODE_ENGINES, || { + (0, Arc::new(Mutex::new(BlockFun::new(lang.clone())))) + }); + ( NodeConfigurator { nodes, @@ -292,6 +304,7 @@ impl NodeConfigurator { trackers: vec![Tracker::new(); MAX_AVAIL_TRACKERS], #[cfg(feature = "synfx-dsp-jit")] code_engines: vec![CodeEngine::new(); MAX_AVAIL_CODE_ENGINES], + block_functions, scopes, }, shared_exec, @@ -652,10 +665,12 @@ impl NodeConfigurator { } } + /// Retrieve the oscilloscope handle for the scope index `scope`. pub fn get_scope_handle(&self, scope: usize) -> Option> { self.scopes.get(scope).cloned() } + /// Retrieve a handle to the tracker pattern data of the tracker `tracker_id`. pub fn get_pattern_data(&self, tracker_id: usize) -> Option>> { if tracker_id >= self.trackers.len() { return None; @@ -664,6 +679,10 @@ impl NodeConfigurator { Some(self.trackers[tracker_id].data()) } + /// Checks if there are any updates to send for the pattern data that belongs to the + /// tracker `tracker_id`. Call this repeatedly, eg. once per frame in a GUI, in case the user + /// modified the pattern data. It will make sure that the modifications are sent to the + /// audio thread. pub fn check_pattern_data(&mut self, tracker_id: usize) { if tracker_id >= self.trackers.len() { return; @@ -672,6 +691,41 @@ impl NodeConfigurator { self.trackers[tracker_id].send_one_update(); } + /// Checks the block function for the id `id`. If the block function did change, + /// updates are then sent to the audio thread. + /// See also [get_block_function]. + pub fn check_block_function(&mut self, id: usize) { + if let Some((generation, block_fun)) = self.block_functions.get_mut(id) { + if let Ok(block_fun) = block_fun.lock() { + if *generation != block_fun.generation() { + *generation = block_fun.generation(); + // let ast = block_compiler::compile(block_fun); + if let Some(cod) = self.code_engines.get_mut(id) { + use synfx_dsp_jit::build::*; + cod.upload(stmts(&[ + assign( + "*phase", + op_add(var("*phase"), op_mul(literal(440.0), var("israte"))), + ), + _if( + op_gt(var("*phase"), literal(1.0)), + assign("*phase", op_sub(var("*phase"), literal(1.0))), + None, + ), + var("*phase"), + ])); + } + } + } + } + } + + /// Retrieve a handle to the block function `id`. In case you modify the block function, + /// make sure to call [check_block_function]. + pub fn get_block_function(&mut self, id: usize) -> Option>> { + self.block_functions.get(id).map(|pair| pair.1.clone()) + } + pub fn delete_nodes(&mut self) { self.node2idx.clear(); self.nodes.fill_with(|| (NodeInfo::from_node_id(NodeId::Nop), None)); diff --git a/tests/blocklang.rs b/tests/blocklang.rs new file mode 100644 index 0000000..6e02cd9 --- /dev/null +++ b/tests/blocklang.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2022 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +mod common; +use common::*; + +#[test] +fn check_blocklang_1() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain + .node_out("code", "sig1") + .set_denorm("in1", 0.5) + .set_denorm("in2", -0.6) + .node_inp("out", "ch1") + .place(&mut matrix, 0, 0) + .unwrap(); + matrix.sync().unwrap(); + + let code = NodeId::Code(0); + + let block_fun = matrix.get_block_function(0).expect("block fun exists"); + { + let mut block_fun = block_fun.lock().expect("matrix lock"); + + block_fun.instanciate_at(0, 0, 0, "get", Some("in1".to_string())); + block_fun.instanciate_at(0, 0, 1, "get", Some("in1".to_string())); + block_fun.instanciate_at(0, 1, 0, "+", None); + block_fun.instanciate_at(0, 0, 1, "set", Some("&sig1".to_string())); + } + + matrix.check_block_function(0); + + let res = run_for_ms(&mut node_exec, 25.0); + assert_decimated_feq!( + res.0, + 50, + vec![ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + ] + ); +}