Added TDD test for blocklang
This commit is contained in:
parent
f5f242041a
commit
9b0f93c92b
3 changed files with 129 additions and 2 deletions
|
@ -10,6 +10,7 @@ pub use crate::nodes::MinMaxMonitorSamples;
|
||||||
use crate::nodes::{NodeConfigurator, NodeGraphOrdering, NodeProg, MAX_ALLOCATED_NODES};
|
use crate::nodes::{NodeConfigurator, NodeGraphOrdering, NodeProg, MAX_ALLOCATED_NODES};
|
||||||
pub use crate::CellDir;
|
pub use crate::CellDir;
|
||||||
use crate::ScopeHandle;
|
use crate::ScopeHandle;
|
||||||
|
use crate::blocklang::BlockFun;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
@ -578,20 +579,37 @@ impl Matrix {
|
||||||
self.config.filtered_out_fb_for(ni, out)
|
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<Arc<Mutex<PatternData>>> {
|
pub fn get_pattern_data(&self, tracker_id: usize) -> Option<Arc<Mutex<PatternData>>> {
|
||||||
self.config.get_pattern_data(tracker_id)
|
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<Arc<ScopeHandle>> {
|
pub fn get_scope_handle(&self, scope: usize) -> Option<Arc<ScopeHandle>> {
|
||||||
self.config.get_scope_handle(scope)
|
self.config.get_scope_handle(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if pattern data updates need to be sent to the
|
/// Checks if there are any updates to send for the pattern data that belongs to the
|
||||||
/// DSP thread.
|
/// 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) {
|
pub fn check_pattern_data(&mut self, tracker_id: usize) {
|
||||||
self.config.check_pattern_data(tracker_id)
|
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<Arc<Mutex<BlockFun>>> {
|
||||||
|
self.config.get_block_function(id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Saves the state of the hexagonal grid layout.
|
/// Saves the state of the hexagonal grid layout.
|
||||||
/// This is usually used together with [Matrix::check]
|
/// This is usually used together with [Matrix::check]
|
||||||
/// and [Matrix::restore_matrix] to try if changes on
|
/// and [Matrix::restore_matrix] to try if changes on
|
||||||
|
|
|
@ -6,6 +6,8 @@ use super::{
|
||||||
FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_CODE_ENGINES,
|
FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_CODE_ENGINES,
|
||||||
MAX_AVAIL_TRACKERS, MAX_INPUTS, MAX_SCOPES, UNUSED_MONITOR_IDX,
|
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::tracker::{PatternData, Tracker};
|
||||||
use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom};
|
use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom};
|
||||||
use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT};
|
use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT};
|
||||||
|
@ -185,6 +187,10 @@ pub struct NodeConfigurator {
|
||||||
/// Holding the WBlockDSP code engine backends:
|
/// Holding the WBlockDSP code engine backends:
|
||||||
#[cfg(feature = "synfx-dsp-jit")]
|
#[cfg(feature = "synfx-dsp-jit")]
|
||||||
pub(crate) code_engines: Vec<CodeEngine>,
|
pub(crate) code_engines: Vec<CodeEngine>,
|
||||||
|
/// 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<Mutex<BlockFun>>)>,
|
||||||
/// The shared parts of the [NodeConfigurator]
|
/// The shared parts of the [NodeConfigurator]
|
||||||
/// and the [crate::nodes::NodeExecutor].
|
/// and the [crate::nodes::NodeExecutor].
|
||||||
pub(crate) shared: SharedNodeConf,
|
pub(crate) shared: SharedNodeConf,
|
||||||
|
@ -274,6 +280,12 @@ impl NodeConfigurator {
|
||||||
let mut scopes = vec![];
|
let mut scopes = vec![];
|
||||||
scopes.resize_with(MAX_SCOPES, || ScopeHandle::new_shared());
|
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 {
|
NodeConfigurator {
|
||||||
nodes,
|
nodes,
|
||||||
|
@ -292,6 +304,7 @@ impl NodeConfigurator {
|
||||||
trackers: vec![Tracker::new(); MAX_AVAIL_TRACKERS],
|
trackers: vec![Tracker::new(); MAX_AVAIL_TRACKERS],
|
||||||
#[cfg(feature = "synfx-dsp-jit")]
|
#[cfg(feature = "synfx-dsp-jit")]
|
||||||
code_engines: vec![CodeEngine::new(); MAX_AVAIL_CODE_ENGINES],
|
code_engines: vec![CodeEngine::new(); MAX_AVAIL_CODE_ENGINES],
|
||||||
|
block_functions,
|
||||||
scopes,
|
scopes,
|
||||||
},
|
},
|
||||||
shared_exec,
|
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<Arc<ScopeHandle>> {
|
pub fn get_scope_handle(&self, scope: usize) -> Option<Arc<ScopeHandle>> {
|
||||||
self.scopes.get(scope).cloned()
|
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<Arc<Mutex<PatternData>>> {
|
pub fn get_pattern_data(&self, tracker_id: usize) -> Option<Arc<Mutex<PatternData>>> {
|
||||||
if tracker_id >= self.trackers.len() {
|
if tracker_id >= self.trackers.len() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -664,6 +679,10 @@ impl NodeConfigurator {
|
||||||
Some(self.trackers[tracker_id].data())
|
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) {
|
pub fn check_pattern_data(&mut self, tracker_id: usize) {
|
||||||
if tracker_id >= self.trackers.len() {
|
if tracker_id >= self.trackers.len() {
|
||||||
return;
|
return;
|
||||||
|
@ -672,6 +691,41 @@ impl NodeConfigurator {
|
||||||
self.trackers[tracker_id].send_one_update();
|
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<Arc<Mutex<BlockFun>>> {
|
||||||
|
self.block_functions.get(id).map(|pair| pair.1.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete_nodes(&mut self) {
|
pub fn delete_nodes(&mut self) {
|
||||||
self.node2idx.clear();
|
self.node2idx.clear();
|
||||||
self.nodes.fill_with(|| (NodeInfo::from_node_id(NodeId::Nop), None));
|
self.nodes.fill_with(|| (NodeInfo::from_node_id(NodeId::Nop), None));
|
||||||
|
|
55
tests/blocklang.rs
Normal file
55
tests/blocklang.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
|
||||||
|
// 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,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue