2022-07-19 03:54:26 +00:00
|
|
|
|
// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
|
2021-08-04 01:58:43 +00:00
|
|
|
|
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
// See README.md and COPYING for details.
|
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
/*!
|
|
|
|
|
|
|
|
|
|
# HexoDSP DSP nodes and DSP code
|
|
|
|
|
|
|
|
|
|
## How to Add a New DSP Node
|
|
|
|
|
|
|
|
|
|
When adding a new node to HexoDSP, I recommend working through the following checklist:
|
|
|
|
|
|
2022-07-19 04:02:13 +00:00
|
|
|
|
- [ ] Implement boilerplate in node_yourname.rs
|
|
|
|
|
- [ ] Add input parameter and output signal definition to dsp/mod.rs
|
|
|
|
|
- [ ] Document boilerplate in node_yourname.rs
|
2022-07-19 03:54:26 +00:00
|
|
|
|
- [ ] DSP implementation
|
|
|
|
|
- [ ] Parameter fine tuning
|
2022-07-19 04:02:13 +00:00
|
|
|
|
- [ ] DSP tests for all (relevant) params
|
2022-07-19 03:54:26 +00:00
|
|
|
|
- [ ] Ensure Documentation is properly formatted for the GUI
|
2022-07-19 04:10:24 +00:00
|
|
|
|
- [ ] Format the source using `cargo fmt`
|
2022-07-19 03:54:26 +00:00
|
|
|
|
- [ ] Add CHANGELOG.md entry in HexoSynth
|
|
|
|
|
- [ ] Add CHANGELOG.md entry in HexoDSP
|
|
|
|
|
- [ ] Add table entry in README.md in HexoSynth
|
|
|
|
|
- [ ] Add table entry in README.md in HexoDSP
|
|
|
|
|
|
2022-07-19 04:02:13 +00:00
|
|
|
|
The boilerplate can be a bit daunting. But it pays off, because HexoSynth will give
|
|
|
|
|
you a GUI for your DSP code for free at the end.
|
|
|
|
|
|
|
|
|
|
Generally I recommend starting out small. Define your new node with minimal parameters
|
|
|
|
|
until you get the hang of all the things involved to make it compile in the first place.
|
|
|
|
|
|
2022-07-19 04:07:14 +00:00
|
|
|
|
**Be aware that new DSP nodes need to meet these quality guidelines to be included:**
|
|
|
|
|
|
2022-07-19 04:10:24 +00:00
|
|
|
|
- Clean Rust code that I can understand and maintain. You can use `cargo fmt` (rustfmt) to
|
|
|
|
|
format the code.
|
2022-07-19 04:07:14 +00:00
|
|
|
|
- Does not drag in huge dependency trees. One rationale here is,
|
|
|
|
|
that I don't want the sound of a HexoSynth patch to change (significantly) because
|
|
|
|
|
some upstream crate decided to change their DSP code. To have optimal
|
|
|
|
|
control over this, I would love to have all the DSP code
|
|
|
|
|
contained in HexoDSP. Make sure to link the repository the code comes
|
2022-07-19 04:10:24 +00:00
|
|
|
|
from though. If you add dependencies for your DSP node, make sure that it's
|
|
|
|
|
characteristics are properly covered by the automated tests. So that problems become
|
|
|
|
|
visible in case upstream breaks or changes it's DSP code. If DSP code changes just slightly,
|
|
|
|
|
the test cases of course need to be changed accordingly.
|
2022-07-19 04:07:14 +00:00
|
|
|
|
- Come with automated smoke tests like all the other nodes, most test
|
|
|
|
|
signal min/max/rms over time, as well as the frequency spectrum
|
|
|
|
|
where applicable.
|
|
|
|
|
- It's parameters have proper denormalized mappings, like `0.5 => 4000 Hz` or `0.3 => 200ms`.
|
|
|
|
|
- Provide short descriptions for the node and it's parameters.
|
|
|
|
|
- Provide a comprehensive longer help text with (more details further down in this guide):
|
|
|
|
|
- What this node is about
|
|
|
|
|
- How to use it
|
|
|
|
|
- How the parameters work in combination
|
|
|
|
|
- Suggestions which combinations with other nodes might be interesting
|
|
|
|
|
- If applicable: provide a graph function for visualizing what it does.
|
2022-07-19 03:54:26 +00:00
|
|
|
|
|
|
|
|
|
### Boilerplate
|
|
|
|
|
|
|
|
|
|
- I recommend copying an existing node code, like `node_ad.rs` for instance.
|
|
|
|
|
- In this file `mod.rs` copy it's entry in the `node_list` macro definition.
|
|
|
|
|
- Copy the `tests/node_ad.rs` file to have a starting point for the automated testing.
|
|
|
|
|
Also keep in mind looking in other tests, about how they test things. Commonly used
|
|
|
|
|
macros are found in the ´tests/common/´ module.
|
|
|
|
|
|
|
|
|
|
A `node_list` macro entry looks like this:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
// node_id => node_label UIType UICategory
|
|
|
|
|
// | | / |
|
|
|
|
|
// / /------/ / |
|
|
|
|
|
// / | / |
|
|
|
|
|
xxx => Xxx UIType::Generic UICategory::Signal
|
|
|
|
|
// node_param_idx
|
|
|
|
|
// name denorm round format steps norm norm denorm
|
|
|
|
|
// norm_fun fun fun fun def min max default
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 gain n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
(2 att n_att d_att r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
// node_param_idx UI widget type (mode, knob, sample)
|
|
|
|
|
// | atom_idx | format fun
|
|
|
|
|
// | | name constructor| | min max
|
|
|
|
|
// | | | | def|ult_v|lue | /
|
|
|
|
|
// | | | | | | | | |
|
|
|
|
|
{3 0 mono setting(0) mode fa_out_mono 0 1},
|
|
|
|
|
[0 sig],
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The first entries, encapsulated in ´( )´ are the input ports or also called input parameters.
|
|
|
|
|
Input parameters can be connected to outputs of other DSP nodes. In contrast to the ´{ }´ encapsulated
|
|
|
|
|
so called _Atom parameters_. The data type for these is the [SAtom] datatype. And these parameters
|
|
|
|
|
can not be automated.
|
|
|
|
|
|
2022-07-20 04:26:28 +00:00
|
|
|
|
You can freely choose parameter names like eg. `inp` or `gain` and
|
|
|
|
|
pick names that suit the parameter semantics best. But I would like you to keep the naming
|
|
|
|
|
consistent with the rest of HexoDSP nodes if that is suitable to the DSP node.
|
|
|
|
|
|
|
|
|
|
There are some implicit conventions in HexoDSP for naming though:
|
|
|
|
|
|
|
|
|
|
- `inp` for single channel signal input
|
|
|
|
|
- `ch1`, `ch2`, ... for multiple channels
|
|
|
|
|
- `sig` for signal output
|
|
|
|
|
- `trig` for receiving a single trigger signal
|
|
|
|
|
- `t_*` if multiple trigger signals are expected
|
|
|
|
|
- If you have `freq` inputs, consider also adding `det` for detuning that frequency input.
|
|
|
|
|
But only if you think this makes sense in the context of the DSP node.
|
|
|
|
|
|
|
|
|
|
The macros in the node list definition like `n_gain`, `d_pit`, `r_fq` and so on
|
|
|
|
|
are all macros that are defined in the HexoDSP crate. You can create your own
|
|
|
|
|
normalization/denormalization, rounding, step and formatting function macros if
|
|
|
|
|
the existing ones don't suit the DSP node's needs.
|
|
|
|
|
|
2022-07-20 04:29:56 +00:00
|
|
|
|
### Signal Ranges in HexoDSP
|
|
|
|
|
|
|
|
|
|
The HexoDSP graph, or rather the nodes, operate with the raw normalized (audio)
|
|
|
|
|
signal range [-1, 1]. There is a second range that is also common in HexoDSP,
|
|
|
|
|
which is the control signal range [0, 1]. Following this convention will help combinding
|
|
|
|
|
HexoDSP nodes with each other. The existing normalization/denormalization functions for the
|
|
|
|
|
node list declaration already encode most of the conventions in HexoDSP, but here is a short
|
|
|
|
|
incomplete overview of common value mappings to the normalized signal ranges:
|
|
|
|
|
|
|
|
|
|
- Frequencies are usually using the `n_pit` and `d_pit` mappings. Where 0.0 is 440Hz
|
|
|
|
|
and the next octave is at 0.1 with 880Hz and the octave before that is at -0.1 with 220Hz.
|
|
|
|
|
This means one octave per 0.1 signal value.
|
|
|
|
|
- Triggers have to rise above the "high" threshold of 0.5 to be recognized, and the signal has to
|
|
|
|
|
fall below 0.25 to be detected as "low" again. Same works for gates.
|
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
### Node Documentation
|
|
|
|
|
|
2022-07-19 04:02:13 +00:00
|
|
|
|
**Attention: Defining the documentation for your DSP node is not optional. It's required to make
|
|
|
|
|
it compile in the first place!**
|
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
Every DSP node must come with online documentation. The online documentation is what makes the
|
|
|
|
|
node usable in the first place. It's the only hint for the user to learn how to use this node.
|
|
|
|
|
Keep in mind that the user is not an engineer, but usually a musician. They want to make music
|
|
|
|
|
and know how to use a parameter.
|
|
|
|
|
|
|
|
|
|
Every input parameter and atom parameter requires you to define a documenation entry in the
|
|
|
|
|
corresponding ´node_*.rs´ module/file. And also a _DESC_ and _HELP_ entry.
|
|
|
|
|
|
|
|
|
|
Here an example from ´node_ad.rs´:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
pub const inp: &'static str =
|
|
|
|
|
"Ad inp\nSignal input. If you don't connect this, and set this to 1.0 \
|
|
|
|
|
this will act as envelope signal generator. But you can also just \
|
|
|
|
|
route a signal directly through this of course.\nRange: (-1..1)\n";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The general format of the parameter documentation should be:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
"<Node name> <parameter name>\n
|
|
|
|
|
A short description what this paramter means/does and relates to others.\n
|
|
|
|
|
Range: <range>\n"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Keep the description of the paramter short and concise. Look at the space available
|
|
|
|
|
in HexoSynth where this is displayed. If you want to write more elaborate documentation
|
|
|
|
|
for a paramter, write it in the `HELP` entry.
|
|
|
|
|
|
|
|
|
|
The _range_ relates to the DSP signal range this paramter is supposed to receive.
|
|
|
|
|
This should either be the unipolar range (0..1) or the bipolar range (-1..1). Other
|
|
|
|
|
ranges should be avoided, because everything in HexoDSP is supposed to be fine with
|
|
|
|
|
receiving values in those ranges.
|
|
|
|
|
|
|
|
|
|
Next you need to document the node itself, how it works what it does and so on...
|
|
|
|
|
For this there are two entries:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
pub const DESC: &'static str = r#"Attack-Decay Envelope
|
|
|
|
|
|
|
|
|
|
This is a simple envelope offering an attack time and decay time with a shape parameter.
|
|
|
|
|
You can use it as envelope generator to modulate other inputs or process a signal with it directly.
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
pub const HELP: &'static str = r#"Ad - Attack-Decay Envelope
|
|
|
|
|
|
|
|
|
|
This simple two stage envelope with attack and decay offers shape parameters
|
|
|
|
|
...
|
|
|
|
|
"#;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
_DESC_ should only contain a short description of the node. It's space is as limited as the
|
|
|
|
|
space for the parameter description. It will be autowrapped.
|
|
|
|
|
|
|
|
|
|
_HELP_ can be a multiple pages long detailed description of the node. Keep the
|
|
|
|
|
width of the lines below a certain limit (below 80 usually). Or else it will be
|
|
|
|
|
truncated in the help text window in HexoSynth. As inspiration what should be in
|
|
|
|
|
the help documentation:
|
|
|
|
|
|
|
|
|
|
- What the node does (even if it repeats mostly what _DESC_ already says)
|
|
|
|
|
- How the input paramters relate to each other.
|
|
|
|
|
- What the different atom settings (if any) mean.
|
|
|
|
|
- Which other DSP nodes this node is commonly combined with.
|
|
|
|
|
- Interesting or even uncommon uses of this DSP node.
|
|
|
|
|
- Try to inspire the user to experiment.
|
|
|
|
|
|
|
|
|
|
### Node Code Structure
|
|
|
|
|
|
|
|
|
|
For non trivial DSP nodes, the DSP code itself should be separate from it's `dsp/node_*.rs`
|
|
|
|
|
file. That file should only care about interfacing the DSP code with HexoDSP, but not implement
|
|
|
|
|
all the complicated DSP code. It's good practice to factor out the DSP code into
|
|
|
|
|
a separate module or file.
|
|
|
|
|
|
|
|
|
|
Look at `node_tslfo.rs` for instance. It wires up the `TriSawLFO` from `dsp/helpers.rs`
|
|
|
|
|
to the HexoDSP node interface.
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
// node_tslfo.rs
|
|
|
|
|
use super::helpers::{TriSawLFO, Trigger};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct TsLFO {
|
|
|
|
|
lfo: Box<TriSawLFO<f64>>,
|
|
|
|
|
trig: Trigger,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
impl DspNode for TsLFO {
|
|
|
|
|
// ...
|
|
|
|
|
#[inline]
|
|
|
|
|
fn process<T: NodeAudioContext>(&mut self, /* ... */) {
|
|
|
|
|
// ...
|
|
|
|
|
let lfo = &mut *self.lfo;
|
|
|
|
|
|
|
|
|
|
for frame in 0..ctx.nframes() {
|
|
|
|
|
// ...
|
|
|
|
|
out.write(frame, lfo.next_unipolar() as f32);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The code for `TriSawLFO` in `dsp/helpers.rs` is then independent and reusable else where.
|
|
|
|
|
|
2022-07-19 04:00:40 +00:00
|
|
|
|
### Node Parameter/Inputs
|
|
|
|
|
|
|
|
|
|
When implementing your node, you want to access the parameters or inputs of your DSP node.
|
|
|
|
|
This is done using the buffer access modules in `dsp/mod.rs` that are defined using the
|
|
|
|
|
`node_list` macro. Let me give you a short overview using `node_sin.rs` as an example:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
#[inline]
|
|
|
|
|
fn process<T: NodeAudioContext>(
|
|
|
|
|
&mut self,
|
|
|
|
|
ctx: &mut T, // DSP execution context holding the DSP graph input and output buffers.
|
|
|
|
|
_ectx: &mut NodeExecContext, // Contains special stuff, like the FeedbackBuffers
|
|
|
|
|
_nctx: &NodeContext, // Holds context info about the node, for instance which ports
|
|
|
|
|
// are connected.
|
|
|
|
|
_atoms: &[SAtom], // An array holding the Atom parameters
|
|
|
|
|
inputs: &[ProcBuf], // An array holding the input parameter buffers, containing
|
|
|
|
|
// either outputs from other DSP nodes or smoothed parameter
|
|
|
|
|
// settings from the GUI/frontend.
|
|
|
|
|
outputs: &mut [ProcBuf], // An array holding the output buffers.
|
|
|
|
|
ctx_vals: LedPhaseVals, // Values for visual aids in the GUI (the hextile LED)
|
|
|
|
|
) {
|
|
|
|
|
use crate::dsp::{denorm_offs, inp, out};
|
|
|
|
|
|
|
|
|
|
let o = out::Sin::sig(outputs);
|
|
|
|
|
let freq = inp::Sin::freq(inputs);
|
|
|
|
|
let det = inp::Sin::det(inputs);
|
|
|
|
|
let isr = 1.0 / self.srate;
|
|
|
|
|
|
|
|
|
|
let mut last_val = 0.0;
|
|
|
|
|
for frame in 0..ctx.nframes() {
|
|
|
|
|
let freq = denorm_offs::Sampl::freq(freq, det.read(frame), frame);
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
There are three buffer/parameter function access modules loaded in this example:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
use crate::dsp::{denorm_offs, inp, out};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`inp` holds a sub module for each of the available nodes. That means: `inp::Sin`, `inp::Ad`, ...
|
|
|
|
|
Those submodules each have a function that returns the corresponding buffer from the `inputs`
|
|
|
|
|
vector of buffers. That means `inp::Sin::det(inputs)` gives you a reference to a [ProcBuf]
|
|
|
|
|
you can read the normalized signal inputs (range -1 to 1) from.
|
|
|
|
|
|
|
|
|
|
It works similarly with `out::Sin::sig`, which provides you with a [ProcBuf] reference to
|
|
|
|
|
write your output to.
|
|
|
|
|
|
|
|
|
|
`denorm_offs` is a special module, that offers you functions to access the denormalized
|
|
|
|
|
value of a specific input parameter with a modulation offset.
|
|
|
|
|
|
|
|
|
|
Commonly you want to use the `denorm` module to access the denormalized values. That means
|
|
|
|
|
values in human understandable form and that can be used in your DSP arithmetics more easily.
|
|
|
|
|
For instance `denorm::TsLFO::time` from `node_tslfo.rs`:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
use crate::dsp::{denorm, inp, out};
|
|
|
|
|
|
|
|
|
|
let time = inp::TsLFO::time(inputs);
|
|
|
|
|
for frame in 0..ctx.nframes() {
|
|
|
|
|
let time_ms = denorm::TsLFO::time(time, frame).clamp(0.1, 300000.0);
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`denorm::TsLFO::time` is a function that takes the [ProcBuf] with the raw normalized
|
|
|
|
|
input signal samples and returns the denormalized values in milliseconds for a specific
|
|
|
|
|
frame.
|
|
|
|
|
|
|
|
|
|
To get a hang of all the possibilities I suggest diving a bit into the other node source code
|
|
|
|
|
a bit.
|
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
### Node Beautification
|
|
|
|
|
|
|
|
|
|
To make nodes responsive in HexoSynth the `DspNode::process` function receives the [LedPhaseVals].
|
|
|
|
|
These should be written after the inner loop that calculates the samples. The first context value
|
|
|
|
|
is the _LED value_, it should be in the range between -1 and 1. The most easy way to set it is
|
|
|
|
|
by using the last written sample from your loop:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
ctx_vals[0].set(out.read(ctx.nframes() - 1));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
But consider giving it a more meaningful value if possible. The `node_ad.rs` sets the LED value
|
|
|
|
|
to the internal phase value of the envelope instead of it's output.
|
|
|
|
|
|
|
|
|
|
The second value in [LedPhaseVals] is the _Phase value_. It usually has special meaning for the
|
|
|
|
|
node specific visualizations. Such as TSeq emits the position of the playhead for instance.
|
|
|
|
|
The CQnt quantizer emits the most recently activated key to the GUI using the Phase value.
|
|
|
|
|
|
|
|
|
|
Consider also providing a visualization graph if possible. You can look eg at `node_ad.rs`
|
|
|
|
|
or `node_tslfo.rs` or many others how to provide a visualization graph function:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
impl DspNode for TsLFO {
|
|
|
|
|
fn graph_fun() -> Option<GraphFun> {
|
|
|
|
|
Some(Box::new(|gd: &dyn GraphAtomData, _init: bool, x: f32, xn: f32| -> f32 {
|
|
|
|
|
// ...
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Let me explain the callback function parameters quickly:
|
|
|
|
|
|
|
|
|
|
- `gd: &dyn GraphAtomData` this trait object gives you access to the input paramters of
|
|
|
|
|
this node. And also the LED and Phase values.
|
|
|
|
|
- `init: bool` allows you to detect when the first sample of the graph is being drawn/requested.
|
|
|
|
|
You can use this to reset any state that is carried with the callback.
|
|
|
|
|
- `x: f32` is the current X position of the graph. Use this to derive the Y value which
|
|
|
|
|
must be returned from the callback.
|
|
|
|
|
- `xn: f32` is the next value for X. This is useful for determining if your function might
|
|
|
|
|
reaches a min or max between _x_ and _xn_, so you could for instance return the min or max
|
|
|
|
|
value now instead of the next sample.
|
|
|
|
|
|
|
|
|
|
### Automated Testing of Your Node
|
|
|
|
|
|
2022-07-24 11:03:02 +00:00
|
|
|
|
First lets discuss shortly why automated tests are necessary. HexoDSP has an automated test
|
|
|
|
|
suite to check if any changes on the internal DSP helpers break something. Or if some
|
|
|
|
|
changes on some DSP node accidentally broke something. Or if a platform behaves weirdly.
|
|
|
|
|
Or even if upstream crates that are included broke or changed something essential.
|
|
|
|
|
|
|
|
|
|
A few things you can test your DSP code for:
|
|
|
|
|
|
|
|
|
|
- Is non 0.0 signal emitted?
|
|
|
|
|
- Is the signal inside the -1..1 or 0..1 range?
|
|
|
|
|
- Does the signal level change in expected ways if the input parameters are changed?
|
|
|
|
|
- Does the frequency spectrum peak at expected points in the FFT output?
|
|
|
|
|
- Does the frequency spectrum change to expected points in the FFT output when an input parameter
|
|
|
|
|
changed?
|
|
|
|
|
|
|
|
|
|
Try to nail down the characteristics of your DSP node with a few tests as well as possible.
|
|
|
|
|
|
|
|
|
|
For the FFT and other tests there are helper functions in `tests/common/mod.rs`
|
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
The start of your `tests/node_*.rs` file usually should look like this:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
mod common;
|
|
|
|
|
use common::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_node_ad_1() {
|
|
|
|
|
let (node_conf, mut node_exec) = new_node_engine();
|
|
|
|
|
let mut matrix = Matrix::new(node_conf, 3, 3);
|
|
|
|
|
|
2022-07-24 05:27:18 +00:00
|
|
|
|
let mut chain = MatrixCellChain::new(CellDir::B);
|
|
|
|
|
chain.node_out("ad", "sig")
|
|
|
|
|
.node_inp("out", "ch1")
|
|
|
|
|
.place(&mut matrix, 0, 0).unwrap();
|
2022-07-19 03:54:26 +00:00
|
|
|
|
matrix.sync().unwrap();
|
|
|
|
|
|
2022-07-24 05:27:18 +00:00
|
|
|
|
let ad = NodeId::Ad(0);
|
2022-07-19 03:54:26 +00:00
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Lets dissect this a bit. The beginning of each test case should setup an instance of the DSP engine
|
|
|
|
|
of HexoDSP using [crate::new_node_engine]. It returns a [crate::NodeConfigurator] and a [crate::NodeExecutor].
|
|
|
|
|
The first is responsible for setting up the DSP graph and modifying it at runtime.
|
|
|
|
|
The latter ([crate::NodeExecutor]) is responsible for executing the DSP graph and generate output samples.
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
let (node_conf, mut node_exec) = new_node_engine();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The [crate::Matrix] abstraction encapsulates the configurator and provides you an interface
|
|
|
|
|
to layout the nodes in a hexagonal grid. It is currently the easiest API for using HexoDSP.
|
|
|
|
|
The two parameters to _new_ are the width and height of the hex grid.
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
let mut matrix = Matrix::new(node_conf, 3, 3);
|
|
|
|
|
```
|
|
|
|
|
|
2022-07-24 05:27:18 +00:00
|
|
|
|
Next step is to create a DSP chain of nodes and place that onto the hexagonal matrix.
|
|
|
|
|
Luckily a simpler API has been created with the [crate::MatrixCellChain], that lets
|
|
|
|
|
you build DSP chains on the fly using only names of the nodes and the corresponding
|
|
|
|
|
input/output ports:
|
2022-07-19 03:54:26 +00:00
|
|
|
|
|
|
|
|
|
```ignore
|
2022-07-24 05:27:18 +00:00
|
|
|
|
// Create a new cell chain that points in to the given direction (CellDir::B => to bottom).
|
|
|
|
|
let mut chain = MatrixCellChain::new(CellDir::B);
|
|
|
|
|
chain.node_out("ad", "sig") // Add a Node::Ad(0) cell, with the "sig" output set
|
|
|
|
|
.node_inp("out", "ch1") // Add a Node::Out(0) cell, with the "ch1" input set
|
|
|
|
|
.place(&mut matrix, 0, 0).unwrap();
|
2022-07-19 03:54:26 +00:00
|
|
|
|
```
|
|
|
|
|
|
2022-07-24 05:27:18 +00:00
|
|
|
|
After placing the new cells, we need to synchronize it with the audio backend:
|
2022-07-19 03:54:26 +00:00
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
matrix.sync().unwrap();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `sync` is necessary to update the DSP graph.
|
2022-07-19 04:04:30 +00:00
|
|
|
|
|
2022-07-24 05:27:18 +00:00
|
|
|
|
Next you usually want to define short variable names for the [NodeId] that refer to the DSP
|
|
|
|
|
node instances:
|
2022-07-19 04:04:30 +00:00
|
|
|
|
|
|
|
|
|
```ignore
|
2022-07-24 05:27:18 +00:00
|
|
|
|
let ad = NodeId::Ad(0);
|
2022-07-19 04:04:30 +00:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The [NodeId] interface offers you functions to get the input parameter index from
|
|
|
|
|
a name like `out.inp("ch1")` or the output port index from a name: `ad.out("sig")`.
|
2022-07-24 05:27:18 +00:00
|
|
|
|
You can have multiple instances for a node. The number in the parenthesis are
|
|
|
|
|
the instance index of that node.
|
2022-07-19 04:04:30 +00:00
|
|
|
|
|
2022-07-19 03:54:26 +00:00
|
|
|
|
After you have setup everything for the test, you usually want to modify a paramter
|
|
|
|
|
and look at the values the graph returns.
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_node_ad_1() {
|
2022-07-24 05:27:18 +00:00
|
|
|
|
// ...
|
2022-07-19 03:54:26 +00:00
|
|
|
|
// matrix setup code above
|
2022-07-24 05:27:18 +00:00
|
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
let ad = NodeId::Ad(0);
|
2022-07-19 03:54:26 +00:00
|
|
|
|
|
|
|
|
|
// Fetch parameter id:
|
|
|
|
|
let trig_p = ad.inp_param("trig").unwrap();
|
|
|
|
|
|
|
|
|
|
// Set parameter:
|
|
|
|
|
matrix.set_param(trig_p, SAtom::param(1.0));
|
|
|
|
|
|
|
|
|
|
/// Run the DSP graph for 25 milliseconds of audio.
|
|
|
|
|
let res = run_for_ms(&mut node_exec, 25.0);
|
|
|
|
|
|
|
|
|
|
// `res` now contains two vectors. one for first channel "ch1"
|
|
|
|
|
// and one for the second channel "ch2".
|
|
|
|
|
assert_decimated_slope_feq!(
|
|
|
|
|
res.0,
|
|
|
|
|
// ....
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
***Attention: This is important to keep in mind:*** After using `matrix.set_param(...)` to
|
|
|
|
|
set a paramter, keep in mind that the parameter values will be smoothed. That means it will
|
|
|
|
|
take a few milliseconds until `trig_p` reaches the 1.0. In case of the Ad node that means
|
|
|
|
|
the trigger threshold won't be triggered at the first sample, but a few milliseconds
|
|
|
|
|
later!
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_ad;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_allp;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_amp;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_biqfilt;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_bosc;
|
2021-05-19 01:58:33 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_bowstri;
|
2021-06-03 03:10:29 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_comb;
|
2021-06-13 06:48:42 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_cqnt;
|
2021-06-19 08:50:39 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
|
mod node_delay;
|
2021-06-28 03:10:46 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_fbwr_fbrd;
|
2021-07-02 03:11:11 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
|
mod node_map;
|
2021-07-04 06:39:17 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_mix3;
|
2021-07-10 20:16:55 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_mux9;
|
2021-07-24 21:00:09 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_noise;
|
2021-07-25 11:32:16 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_out;
|
2021-08-04 04:51:31 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_pverb;
|
2021-08-05 03:20:47 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_quant;
|
2021-08-06 03:05:47 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_rndwk;
|
2021-08-06 03:49:03 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_sampl;
|
2021-08-07 14:56:59 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_sfilter;
|
2021-08-12 20:12:24 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_sin;
|
2021-08-23 04:57:35 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_smap;
|
2021-08-23 16:42:39 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_test;
|
2021-08-28 17:56:08 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_tseq;
|
2021-10-30 08:03:45 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod node_tslfo;
|
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
|
mod node_vosc;
|
2022-07-25 03:55:54 +00:00
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
|
mod node_scope;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2021-08-04 04:51:31 +00:00
|
|
|
|
pub mod biquad;
|
2021-08-06 04:05:13 +00:00
|
|
|
|
pub mod dattorro;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub mod helpers;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
mod satom;
|
|
|
|
|
pub mod tracker;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
use crate::nodes::NodeAudioContext;
|
2021-06-01 03:14:06 +00:00
|
|
|
|
use crate::nodes::NodeExecContext;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
use crate::util::AtomicFloat;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
pub type LedPhaseVals<'a> = &'a [Arc<AtomicFloat>];
|
|
|
|
|
|
|
|
|
|
pub use satom::*;
|
|
|
|
|
|
2021-06-13 06:48:42 +00:00
|
|
|
|
use crate::fa_ad_mult;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use crate::fa_amp_neg_att;
|
2021-08-05 03:20:47 +00:00
|
|
|
|
use crate::fa_biqfilt_ord;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use crate::fa_biqfilt_type;
|
|
|
|
|
use crate::fa_bosc_wtype;
|
2021-08-06 03:05:47 +00:00
|
|
|
|
use crate::fa_comb_mode;
|
2021-08-25 19:14:41 +00:00
|
|
|
|
use crate::fa_cqnt;
|
|
|
|
|
use crate::fa_cqnt_omax;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use crate::fa_cqnt_omin;
|
|
|
|
|
use crate::fa_delay_mode;
|
|
|
|
|
use crate::fa_distort;
|
|
|
|
|
use crate::fa_map_clip;
|
|
|
|
|
use crate::fa_mux9_in_cnt;
|
|
|
|
|
use crate::fa_noise_mode;
|
|
|
|
|
use crate::fa_out_mono;
|
2021-08-28 17:56:08 +00:00
|
|
|
|
use crate::fa_quant;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use crate::fa_sampl_dclick;
|
|
|
|
|
use crate::fa_sampl_dir;
|
|
|
|
|
use crate::fa_sampl_pmode;
|
|
|
|
|
use crate::fa_sfilter_type;
|
|
|
|
|
use crate::fa_smap_clip;
|
|
|
|
|
use crate::fa_smap_mode;
|
|
|
|
|
use crate::fa_test_s;
|
|
|
|
|
use crate::fa_tseq_cmode;
|
|
|
|
|
use crate::fa_vosc_ovrsmpl;
|
2021-06-04 03:06:41 +00:00
|
|
|
|
|
2021-06-13 06:48:42 +00:00
|
|
|
|
use node_ad::Ad;
|
2021-06-28 03:10:46 +00:00
|
|
|
|
use node_allp::AllP;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use node_amp::Amp;
|
2021-08-05 03:20:47 +00:00
|
|
|
|
use node_biqfilt::BiqFilt;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use node_bosc::BOsc;
|
|
|
|
|
use node_bowstri::BowStri;
|
2021-08-06 03:05:47 +00:00
|
|
|
|
use node_comb::Comb;
|
2021-08-25 19:14:41 +00:00
|
|
|
|
use node_cqnt::CQnt;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use node_delay::Delay;
|
|
|
|
|
use node_fbwr_fbrd::FbRd;
|
|
|
|
|
use node_fbwr_fbrd::FbWr;
|
|
|
|
|
use node_map::Map;
|
|
|
|
|
use node_mix3::Mix3;
|
|
|
|
|
use node_mux9::Mux9;
|
|
|
|
|
use node_noise::Noise;
|
|
|
|
|
use node_out::Out;
|
|
|
|
|
use node_pverb::PVerb;
|
2021-08-28 17:56:08 +00:00
|
|
|
|
use node_quant::Quant;
|
2022-07-17 09:58:28 +00:00
|
|
|
|
use node_rndwk::RndWk;
|
|
|
|
|
use node_sampl::Sampl;
|
|
|
|
|
use node_sfilter::SFilter;
|
|
|
|
|
use node_sin::Sin;
|
|
|
|
|
use node_smap::SMap;
|
|
|
|
|
use node_test::Test;
|
|
|
|
|
use node_tseq::TSeq;
|
|
|
|
|
use node_tslfo::TsLFO;
|
|
|
|
|
use node_vosc::VOsc;
|
2022-07-25 03:55:54 +00:00
|
|
|
|
use node_scope::Scope;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub const MIDI_MAX_FREQ: f32 = 13289.75;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub const MAX_BLOCK_SIZE: usize = 128;
|
2021-07-10 19:27:18 +00:00
|
|
|
|
|
|
|
|
|
/// A context structure that holds temporary information about the
|
|
|
|
|
/// currently executed node.
|
|
|
|
|
/// This structure is created by the [crate::nodes::NodeExecutor] on the fly.
|
|
|
|
|
pub struct NodeContext<'a> {
|
2021-07-10 19:29:58 +00:00
|
|
|
|
/// The bitmask that indicates which input ports are used/connected
|
|
|
|
|
/// to some output.
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub in_connected: u64,
|
2021-07-10 19:27:18 +00:00
|
|
|
|
/// The bitmask that indicates which output ports are used/connected
|
|
|
|
|
/// to some input.
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub out_connected: u64,
|
2021-07-10 19:27:18 +00:00
|
|
|
|
/// The node parameters, which are usually not accessed directly.
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub params: &'a [ProcBuf],
|
2021-07-10 19:27:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
/// This trait is an interface between the graph functions
|
|
|
|
|
/// and the AtomDataModel of the UI.
|
|
|
|
|
pub trait GraphAtomData {
|
2021-06-18 01:58:00 +00:00
|
|
|
|
fn get(&self, param_idx: u32) -> Option<SAtom>;
|
|
|
|
|
fn get_denorm(&self, param_idx: u32) -> f32;
|
|
|
|
|
fn get_norm(&self, param_idx: u32) -> f32;
|
2021-06-18 03:06:14 +00:00
|
|
|
|
fn get_phase(&self) -> f32;
|
|
|
|
|
fn get_led(&self) -> f32;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-11 14:59:32 +00:00
|
|
|
|
pub type GraphFun = Box<dyn FnMut(&dyn GraphAtomData, bool, f32, f32) -> f32>;
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
/// This trait represents a DspNode for the [crate::matrix::Matrix]
|
|
|
|
|
pub trait DspNode {
|
|
|
|
|
/// Number of outputs this node has.
|
|
|
|
|
fn outputs() -> usize;
|
|
|
|
|
|
|
|
|
|
/// Updates the sample rate for the node.
|
|
|
|
|
fn set_sample_rate(&mut self, _srate: f32);
|
|
|
|
|
|
|
|
|
|
/// Reset any internal state of the node.
|
|
|
|
|
fn reset(&mut self);
|
|
|
|
|
|
|
|
|
|
/// The code DSP function.
|
|
|
|
|
///
|
2021-06-02 01:59:21 +00:00
|
|
|
|
/// * `ctx` is the audio context, which informs the node about
|
|
|
|
|
/// the number of samples to process. It also provides input/output
|
|
|
|
|
/// ports for the in/out nodes.
|
|
|
|
|
/// * `ectx` is the execution context, which provides global stuff
|
|
|
|
|
/// for all nodes to potentially use. For instance it's used
|
|
|
|
|
/// by the `FbWr` and `FbRd` nodes to share an audio buffer.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
/// * `atoms` are un-smoothed parameters. they can hold integer settings,
|
|
|
|
|
/// samples or even strings.
|
|
|
|
|
/// * `params` are smoother paramters, those who usually have a knob
|
|
|
|
|
/// associated with them.
|
|
|
|
|
/// * `inputs` contain all the possible inputs. In contrast to `params`
|
|
|
|
|
/// these inputs might be overwritten by outputs of other nodes.
|
|
|
|
|
/// * `outputs` are the output buffers of this node.
|
|
|
|
|
fn process<T: NodeAudioContext>(
|
2022-07-17 09:58:28 +00:00
|
|
|
|
&mut self,
|
|
|
|
|
ctx: &mut T,
|
|
|
|
|
ectx: &mut NodeExecContext,
|
2021-07-10 19:27:18 +00:00
|
|
|
|
nctx: &NodeContext,
|
2022-07-17 09:58:28 +00:00
|
|
|
|
atoms: &[SAtom],
|
|
|
|
|
inputs: &[ProcBuf],
|
|
|
|
|
outputs: &mut [ProcBuf],
|
|
|
|
|
led: LedPhaseVals,
|
|
|
|
|
);
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
/// A function factory for generating a graph for the generic node UI.
|
2022-07-17 09:58:28 +00:00
|
|
|
|
fn graph_fun() -> Option<GraphFun> {
|
|
|
|
|
None
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A processing buffer with the exact right maximum size.
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// This is an unsafe abstraction, and should be used with a lot of care.
|
|
|
|
|
/// You will have to manually free the buffer, and take care if you
|
|
|
|
|
/// make copies of these.
|
|
|
|
|
///
|
|
|
|
|
/// This is an abstraction for the inner most DSP processing, where I
|
|
|
|
|
/// don't want to spend a nanosecond too much on accessing buffers.
|
|
|
|
|
///
|
|
|
|
|
/// The main user is [crate::nodes::NodeProg], which takes extra care
|
|
|
|
|
/// of allocating and managing the [ProcBuf] instances.
|
|
|
|
|
///
|
|
|
|
|
///```
|
|
|
|
|
/// let mut buf = hexodsp::dsp::ProcBuf::new();
|
|
|
|
|
///
|
|
|
|
|
/// buf.write(0, 0.42);
|
|
|
|
|
/// buf.write(1, 0.13);
|
|
|
|
|
/// buf.write(2, 0.37);
|
|
|
|
|
///
|
|
|
|
|
/// assert_eq!(((buf.read(0) * 100.0).floor()), 42.0);
|
|
|
|
|
/// assert_eq!(((buf.read(1) * 100.0).floor()), 13.0);
|
|
|
|
|
/// assert_eq!(((buf.read(2) * 100.0).floor()), 37.0);
|
|
|
|
|
///
|
|
|
|
|
/// buf.free(); // YOU MUST DO THIS!
|
|
|
|
|
///```
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
pub struct ProcBuf(*mut [f32; MAX_BLOCK_SIZE]);
|
|
|
|
|
|
|
|
|
|
impl ProcBuf {
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Creates a new ProcBuf with the size of [MAX_BLOCK_SIZE].
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
ProcBuf(Box::into_raw(Box::new([0.0; MAX_BLOCK_SIZE])))
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Create a new null ProcBuf, that can't be used.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn null() -> Self {
|
|
|
|
|
ProcBuf(std::ptr::null_mut())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl crate::monitor::MonitorSource for &ProcBuf {
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Copies the contents of this [ProcBuf] to the given `slice`.
|
|
|
|
|
///
|
|
|
|
|
/// * `len` - the number of samples to copy from this [ProcBuf].
|
|
|
|
|
/// * `slice` - the slice to copy to.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
fn copy_to(&self, len: usize, slice: &mut [f32]) {
|
|
|
|
|
unsafe { slice.copy_from_slice(&(*self.0)[0..len]) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe impl Send for ProcBuf {}
|
2022-06-26 21:21:32 +00:00
|
|
|
|
unsafe impl Sync for ProcBuf {}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
//unsafe impl Sync for HexoSynthShared {}
|
|
|
|
|
|
|
|
|
|
impl ProcBuf {
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Writes the sample `v` at `idx`.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[inline]
|
|
|
|
|
pub fn write(&mut self, idx: usize, v: f32) {
|
|
|
|
|
unsafe {
|
|
|
|
|
(*self.0)[idx] = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Writes the samples from `slice` to this [ProcBuf].
|
|
|
|
|
/// Be careful, the `slice` must not exceed [MAX_BLOCK_SIZE], or else
|
|
|
|
|
/// you will get UB.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[inline]
|
|
|
|
|
pub fn write_from(&mut self, slice: &[f32]) {
|
|
|
|
|
unsafe {
|
|
|
|
|
(*self.0)[0..slice.len()].copy_from_slice(slice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Reads a sample at `idx`. Be careful to not let the `idx`
|
|
|
|
|
/// land outside of [MAX_BLOCK_SIZE].
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[inline]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub fn read(&self, idx: usize) -> f32 {
|
|
|
|
|
unsafe { (*self.0)[idx] }
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Fills the [ProcBuf] with the sample `v`.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[inline]
|
|
|
|
|
pub fn fill(&mut self, v: f32) {
|
|
|
|
|
unsafe {
|
|
|
|
|
(*self.0).fill(v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Checks if this is a [ProcBuf::null].
|
2021-07-10 10:45:32 +00:00
|
|
|
|
#[inline]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
pub fn is_null(&self) -> bool {
|
|
|
|
|
self.0.is_null()
|
|
|
|
|
}
|
2021-07-10 10:45:32 +00:00
|
|
|
|
|
2021-08-18 03:11:20 +00:00
|
|
|
|
/// Deallocates the [ProcBuf]. If you still keep around
|
|
|
|
|
/// other copies of this [ProcBuf], you will most likely land in
|
|
|
|
|
/// UB land.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn free(&self) {
|
|
|
|
|
if !self.0.is_null() {
|
|
|
|
|
drop(unsafe { Box::from_raw(self.0) });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Debug for ProcBuf {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
unsafe {
|
|
|
|
|
write!(f, "ProcBuf(")?;
|
|
|
|
|
if self.0.is_null() {
|
|
|
|
|
write!(f, "NULL ")?;
|
|
|
|
|
} else {
|
|
|
|
|
for i in 0..MAX_BLOCK_SIZE {
|
|
|
|
|
write!(f, "{:5.4} ", (*self.0)[i])?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
write!(f, ")")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for ProcBuf {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
unsafe { write!(f, "ProcBuf(0: {})", (*self.0)[0]) }
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#[derive(Debug, Clone, Copy)]
|
|
|
|
|
//enum UIParamDesc {
|
|
|
|
|
// Knob { width: usize, prec: usize, unit: &'static str },
|
|
|
|
|
// Setting { labels: &'static [&'static str], unit: &'static str },
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq)]
|
|
|
|
|
pub enum UIType {
|
|
|
|
|
Generic,
|
|
|
|
|
LfoA,
|
|
|
|
|
EnvA,
|
|
|
|
|
OscA,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq)]
|
|
|
|
|
pub enum UICategory {
|
|
|
|
|
None,
|
|
|
|
|
Osc,
|
|
|
|
|
Mod,
|
|
|
|
|
NtoM,
|
|
|
|
|
Signal,
|
2021-08-31 03:00:27 +00:00
|
|
|
|
Ctrl,
|
2021-05-18 03:11:19 +00:00
|
|
|
|
IOUtil,
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 16:56:27 +00:00
|
|
|
|
impl UICategory {
|
|
|
|
|
pub fn default_color_idx(&self) -> u8 {
|
|
|
|
|
match self {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
UICategory::None => 17,
|
|
|
|
|
UICategory::Osc => 0,
|
|
|
|
|
UICategory::Mod => 7,
|
|
|
|
|
UICategory::NtoM => 4,
|
2021-09-01 16:56:27 +00:00
|
|
|
|
UICategory::Signal => 2,
|
2022-07-17 09:58:28 +00:00
|
|
|
|
UICategory::Ctrl => 12,
|
2021-09-01 16:56:27 +00:00
|
|
|
|
UICategory::IOUtil => 10,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
// The following macros define normalize/denormalize functions:
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! n_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
$x
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
macro_rules! d_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
$x
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
macro_rules! define_lin {
|
|
|
|
|
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! $n_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
(($x - $min) / ($max - $min) as f32).abs()
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! $d_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
$min * (1.0 - $x) + $max * $x
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! define_exp {
|
|
|
|
|
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! $n_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
(($x - $min) / ($max - $min) as f32).abs().sqrt()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
macro_rules! $d_id {
|
|
|
|
|
($x: expr) => {{
|
|
|
|
|
let x: f32 = $x * $x;
|
|
|
|
|
$min * (1.0 - x) + $max * x
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! define_exp4 {
|
|
|
|
|
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! $n_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
(($x - $min) / ($max - $min) as f32).abs().sqrt().sqrt()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
macro_rules! $d_id {
|
|
|
|
|
($x: expr) => {{
|
|
|
|
|
let x: f32 = $x * $x * $x * $x;
|
|
|
|
|
$min * (1.0 - x) + $max * x
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-06 03:49:03 +00:00
|
|
|
|
macro_rules! define_exp6 {
|
|
|
|
|
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! $n_id {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
(($x - $min) / ($max - $min) as f32).abs().powf(1.0 / 6.0)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
macro_rules! $d_id {
|
|
|
|
|
($x: expr) => {{
|
|
|
|
|
let x: f32 = ($x).powf(6.0);
|
|
|
|
|
$min * (1.0 - x) + $max * x
|
|
|
|
|
}};
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-08-06 03:49:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-28 06:09:17 +00:00
|
|
|
|
#[macro_export]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! n_pit {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
0.1 * (($x as f32).max(0.01) / 440.0).log2()
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2021-08-28 06:09:17 +00:00
|
|
|
|
#[macro_export]
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! d_pit {
|
|
|
|
|
($x: expr) => {{
|
|
|
|
|
let note: f32 = ($x as f32) * 10.0;
|
2021-07-02 18:08:11 +00:00
|
|
|
|
440.0 * (2.0_f32).powf(note.clamp(-10.0, 10.0))
|
2022-07-17 09:58:28 +00:00
|
|
|
|
}};
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
// The following macros define detune parameter behaviour:
|
2021-06-06 07:31:41 +00:00
|
|
|
|
// 0.2 => 24.0
|
|
|
|
|
// 0.1 => 12.0
|
|
|
|
|
// 0.008333333 => 1.0
|
|
|
|
|
// 0.000083333 => 0.001
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! n_det {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
$x / 120.0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
macro_rules! d_det {
|
|
|
|
|
($x: expr) => {
|
|
|
|
|
$x * 120.0
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// The rounding function for detune UI knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_det {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
n_det!((d_det!($x)).round())
|
|
|
|
|
} else {
|
|
|
|
|
n_det!((d_det!($x) * 100.0).round() / 100.0)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-05 07:13:42 +00:00
|
|
|
|
|
2021-07-02 18:14:02 +00:00
|
|
|
|
/// The rounding function for -1 to 1 signal knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_s {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
($x * 10.0).round() / 10.0
|
|
|
|
|
} else {
|
|
|
|
|
($x * 100.0).round() / 100.0
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-07-02 18:14:02 +00:00
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// The rounding function for milliseconds knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_dc_ms {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
n_declick!((d_declick!($x)).round())
|
|
|
|
|
} else {
|
|
|
|
|
n_declick!((d_declick!($x) * 10.0).round() / 10.0)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 16:09:21 +00:00
|
|
|
|
|
2021-06-13 06:48:42 +00:00
|
|
|
|
/// The rounding function for milliseconds knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_ems {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
n_env!((d_env!($x)).round())
|
|
|
|
|
} else {
|
|
|
|
|
n_env!((d_env!($x) * 10.0).round() / 10.0)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-13 06:48:42 +00:00
|
|
|
|
|
2021-06-19 08:50:39 +00:00
|
|
|
|
/// The rounding function for milliseconds knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_tms {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
if d_time!($x) > 1000.0 {
|
|
|
|
|
n_time!((d_time!($x) / 100.0).round() * 100.0)
|
|
|
|
|
} else if d_time!($x) > 100.0 {
|
|
|
|
|
n_time!((d_time!($x) / 10.0).round() * 10.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_time!((d_time!($x)).round())
|
|
|
|
|
}
|
2021-06-20 06:28:40 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
n_time!((d_time!($x) * 10.0).round() / 10.0)
|
2021-06-20 06:28:40 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-19 08:50:39 +00:00
|
|
|
|
|
2021-06-28 03:10:46 +00:00
|
|
|
|
/// The rounding function for milliseconds knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_fms {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
if d_ftme!($x) > 1000.0 {
|
|
|
|
|
n_ftme!((d_ftme!($x) / 100.0).round() * 100.0)
|
|
|
|
|
} else if d_ftme!($x) > 100.0 {
|
|
|
|
|
n_ftme!((d_ftme!($x) / 10.0).round() * 10.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_ftme!((d_ftme!($x)).round())
|
|
|
|
|
}
|
2021-06-28 03:10:46 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
n_ftme!((d_ftme!($x) * 10.0).round() / 10.0)
|
2021-06-28 03:10:46 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-19 08:50:39 +00:00
|
|
|
|
|
2021-08-12 20:12:24 +00:00
|
|
|
|
/// The rounding function for milliseconds knobs that also have a 0.0 setting
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_tmz {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
if d_timz!($x) > 1000.0 {
|
|
|
|
|
n_timz!((d_timz!($x) / 100.0).round() * 100.0)
|
|
|
|
|
} else if d_timz!($x) > 100.0 {
|
|
|
|
|
n_timz!((d_timz!($x) / 10.0).round() * 10.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_timz!((d_timz!($x)).round())
|
|
|
|
|
}
|
2021-08-12 20:12:24 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
n_timz!((d_timz!($x) * 10.0).round() / 10.0)
|
2021-08-12 20:12:24 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-12 20:12:24 +00:00
|
|
|
|
|
2021-07-02 18:08:11 +00:00
|
|
|
|
/// The rounding function for freq knobs (n_pit / d_pit)
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_fq {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
($x * 10.0).round() / 10.0
|
2021-07-02 18:08:11 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
let p = d_pit!($x);
|
|
|
|
|
if p < 10.0 {
|
|
|
|
|
n_pit!((p * 10.0).round() / 10.0)
|
|
|
|
|
} else if p < 100.0 {
|
|
|
|
|
n_pit!(p.round())
|
|
|
|
|
} else if p < 1000.0 {
|
|
|
|
|
n_pit!((p / 10.0).round() * 10.0)
|
|
|
|
|
} else if p < 10000.0 {
|
|
|
|
|
n_pit!((p / 100.0).round() * 100.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_pit!((p / 1000.0).round() * 1000.0)
|
|
|
|
|
}
|
2021-07-02 18:08:11 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2021-07-02 18:08:11 +00:00
|
|
|
|
|
2021-08-04 04:51:31 +00:00
|
|
|
|
/// The rounding function for vs (v scale) UI knobs
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_vps {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
n_vps!((d_vps!($x)).round())
|
2021-08-06 03:49:03 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
n_vps!((d_vps!($x) * 10.0).round() / 10.0)
|
2021-08-06 03:49:03 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
/// The rounding function for LFO time knobs
|
|
|
|
|
macro_rules! r_lfot {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
if $coarse {
|
|
|
|
|
let denv = d_lfot!($x);
|
|
|
|
|
|
|
|
|
|
if denv < 10.0 {
|
|
|
|
|
let hz = 1000.0 / denv;
|
|
|
|
|
let hz = (hz / 10.0).round() * 10.0;
|
|
|
|
|
n_lfot!(1000.0 / hz)
|
|
|
|
|
} else if denv < 250.0 {
|
|
|
|
|
n_lfot!((denv / 5.0).round() * 5.0)
|
|
|
|
|
} else if denv < 1500.0 {
|
|
|
|
|
n_lfot!((denv / 50.0).round() * 50.0)
|
|
|
|
|
} else if denv < 2500.0 {
|
|
|
|
|
n_lfot!((denv / 100.0).round() * 100.0)
|
|
|
|
|
} else if denv < 5000.0 {
|
|
|
|
|
n_lfot!((denv / 500.0).round() * 500.0)
|
|
|
|
|
} else if denv < 60000.0 {
|
|
|
|
|
n_lfot!((denv / 1000.0).round() * 1000.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_lfot!((denv / 5000.0).round() * 5000.0)
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
} else {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
let denv = d_lfot!($x);
|
|
|
|
|
|
|
|
|
|
let o = if denv < 10.0 {
|
|
|
|
|
let hz = 1000.0 / denv;
|
|
|
|
|
let hz = hz.round();
|
|
|
|
|
n_lfot!(1000.0 / hz)
|
|
|
|
|
} else if denv < 100.0 {
|
|
|
|
|
n_lfot!(denv.round())
|
|
|
|
|
} else if denv < 1000.0 {
|
|
|
|
|
n_lfot!((denv / 5.0).round() * 5.0)
|
|
|
|
|
} else if denv < 2500.0 {
|
|
|
|
|
n_lfot!((denv / 10.0).round() * 10.0)
|
|
|
|
|
} else if denv < 25000.0 {
|
|
|
|
|
n_lfot!((denv / 100.0).round() * 100.0)
|
|
|
|
|
} else {
|
|
|
|
|
n_lfot!((denv / 500.0).round() * 500.0)
|
|
|
|
|
};
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
o
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// The default steps function:
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! stp_d {
|
|
|
|
|
() => {
|
|
|
|
|
(20.0, 100.0)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// The UI steps to control parameters with a finer fine control:
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! stp_m {
|
|
|
|
|
() => {
|
|
|
|
|
(20.0, 200.0)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// The UI steps to control parameters with a very fine fine control:
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! stp_f {
|
|
|
|
|
() => {
|
|
|
|
|
(20.0, 1000.0)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-06 16:09:21 +00:00
|
|
|
|
|
2021-06-04 03:06:41 +00:00
|
|
|
|
// Rounding function that does nothing
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! r_id {
|
|
|
|
|
($x: expr, $coarse: expr) => {
|
|
|
|
|
$x
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-04 03:06:41 +00:00
|
|
|
|
|
|
|
|
|
// Default formatting function
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_def {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
write!($formatter, "{:6.3}", $denorm_v)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-06-04 03:06:41 +00:00
|
|
|
|
|
2021-08-05 04:12:50 +00:00
|
|
|
|
// Default formatting function with low precision
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_deflp {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
write!($formatter, "{:5.2}", $denorm_v)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-05 04:12:50 +00:00
|
|
|
|
|
2021-08-04 04:51:31 +00:00
|
|
|
|
// Default formatting function with very low precision
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_defvlp {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
write!($formatter, "{:4.1}", $denorm_v)
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_freq {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
if ($denorm_v >= 1000.0) {
|
|
|
|
|
write!($formatter, "{:6.0}Hz", $denorm_v)
|
|
|
|
|
} else if ($denorm_v >= 100.0) {
|
|
|
|
|
write!($formatter, "{:6.1}Hz", $denorm_v)
|
|
|
|
|
} else {
|
|
|
|
|
write!($formatter, "{:6.2}Hz", $denorm_v)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:58:59 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_ms {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
if $denorm_v >= 1000.0 {
|
|
|
|
|
write!($formatter, "{:6.0}ms", $denorm_v)
|
|
|
|
|
} else if $denorm_v >= 100.0 {
|
|
|
|
|
write!($formatter, "{:5.1}ms", $denorm_v)
|
|
|
|
|
} else {
|
|
|
|
|
write!($formatter, "{:5.2}ms", $denorm_v)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_lfot {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|
|
|
|
if $denorm_v < 10.0 {
|
|
|
|
|
write!($formatter, "{:5.1}Hz", 1000.0 / $denorm_v)
|
|
|
|
|
} else if $denorm_v < 250.0 {
|
|
|
|
|
write!($formatter, "{:4.1}ms", $denorm_v)
|
|
|
|
|
} else if $denorm_v < 1500.0 {
|
|
|
|
|
write!($formatter, "{:4.0}ms", $denorm_v)
|
|
|
|
|
} else if $denorm_v < 10000.0 {
|
|
|
|
|
write!($formatter, "{:5.2}s", $denorm_v / 1000.0)
|
|
|
|
|
} else {
|
|
|
|
|
write!($formatter, "{:5.1}s", $denorm_v / 1000.0)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-08-06 03:49:03 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
macro_rules! f_det {
|
|
|
|
|
($formatter: expr, $v: expr, $denorm_v: expr) => {{
|
|
|
|
|
let sign = if $denorm_v < 0.0 { -1.0 } else { 1.0 };
|
2021-06-06 11:36:53 +00:00
|
|
|
|
let semitones = $denorm_v.trunc().abs();
|
2022-07-17 09:58:28 +00:00
|
|
|
|
let cents = ($denorm_v.fract() * 100.0).round().abs();
|
2021-06-05 07:13:42 +00:00
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
if (cents > 0.1) {
|
2021-06-05 07:13:42 +00:00
|
|
|
|
write!($formatter, "{:2.0}s {:3.0}c", sign * semitones, cents)
|
|
|
|
|
} else {
|
|
|
|
|
write!($formatter, "{:2.0}s", sign * semitones)
|
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
}};
|
|
|
|
|
}
|
2021-08-04 04:51:31 +00:00
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
// norm-fun denorm-min
|
|
|
|
|
// denorm-fun denorm-max
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_exp! {n_gain d_gain 0.0, 2.0}
|
|
|
|
|
define_exp! {n_att d_att 0.0, 1.0}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_exp! {n_declick d_declick 0.0, 50.0}
|
2021-05-29 13:22:32 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_exp! {n_env d_env 0.0, 1000.0}
|
2021-06-13 06:48:42 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_exp6! {n_lfot d_lfot 0.1,300000.0}
|
|
|
|
|
define_exp! {n_time d_time 0.5, 5000.0}
|
|
|
|
|
define_exp! {n_ftme d_ftme 0.1, 1000.0}
|
|
|
|
|
define_exp! {n_timz d_timz 0.0, 5000.0}
|
2021-06-19 10:17:22 +00:00
|
|
|
|
|
2021-06-15 03:12:53 +00:00
|
|
|
|
// Special linear gain factor for the Out node, to be able
|
|
|
|
|
// to reach more exact "1.0".
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_lin! {n_ogin d_ogin 0.0, 2.0}
|
2021-06-13 06:48:42 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_lin! {n_pgin d_pgin 1.0, 10.0}
|
2021-08-05 04:12:50 +00:00
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
define_lin! {n_vps d_vps 0.0, 20.0}
|
2021-08-04 04:51:31 +00:00
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
// A note about the input-indicies:
|
|
|
|
|
//
|
|
|
|
|
// Atoms and Input parameters share the same global ID space
|
|
|
|
|
// because thats how the client of the Matrix API needs to refer to
|
|
|
|
|
// them. Beyond the Matrix API the atom data is actually split apart
|
|
|
|
|
// from the parameters, because they are not smoothed.
|
|
|
|
|
//
|
|
|
|
|
// The index there only matters for addressing the atoms in the global atom vector.
|
|
|
|
|
//
|
|
|
|
|
// But the actually second index here is for referring to the atom index
|
|
|
|
|
// relative to the absolute count of atom data a Node has.
|
|
|
|
|
// It is used by the [Matrix] to get the global ParamId for the atom data
|
|
|
|
|
// when iterating through the atoms of a Node and initializes the default data
|
|
|
|
|
// for new nodes.
|
|
|
|
|
macro_rules! node_list {
|
|
|
|
|
($inmacro: ident) => {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
$inmacro! {
|
2021-05-18 03:11:19 +00:00
|
|
|
|
nop => Nop,
|
|
|
|
|
amp => Amp UIType::Generic UICategory::Signal
|
|
|
|
|
// node_param_idx
|
2021-06-06 16:09:21 +00:00
|
|
|
|
// name denorm round format steps norm norm denorm
|
|
|
|
|
// norm_fun fun fun fun def min max default
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 gain n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
(2 att n_att d_att r_id f_def stp_d 0.0, 1.0, 1.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 0 neg_att setting(1) mode fa_amp_neg_att 0 1}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
[0 sig],
|
2021-07-24 21:00:09 +00:00
|
|
|
|
mix3 => Mix3 UIType::Generic UICategory::NtoM
|
|
|
|
|
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(2 ch3 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(3 gain1 n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
(4 gain2 n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
(5 gain3 n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
(6 ogain n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
[0 sig],
|
2021-08-23 04:57:35 +00:00
|
|
|
|
mux9 => Mux9 UIType::Generic UICategory::NtoM
|
|
|
|
|
( 0 slct n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
|
|
|
|
( 1 t_rst n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 2 t_up n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 3 t_down n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 4 in_1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 5 in_2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 6 in_3 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 7 in_4 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 8 in_5 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 9 in_6 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(10 in_7 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(11 in_8 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(12 in_9 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{13 0 in_cnt setting(3) mode fa_mux9_in_cnt 0 8}
|
2021-08-23 04:57:35 +00:00
|
|
|
|
[0 sig],
|
2021-08-31 03:00:27 +00:00
|
|
|
|
smap => SMap UIType::Generic UICategory::Ctrl
|
2021-07-04 06:39:17 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 min n_id d_id r_s f_def stp_d -1.0, 1.0, -1.0)
|
|
|
|
|
(2 max n_id d_id r_s f_def stp_d -1.0, 1.0, 1.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 1 mode setting(0) mode fa_smap_mode 0 3}
|
|
|
|
|
{4 0 clip setting(0) mode fa_smap_clip 0 1}
|
2021-07-04 06:39:17 +00:00
|
|
|
|
[0 sig],
|
2021-08-31 03:00:27 +00:00
|
|
|
|
map => Map UIType::Generic UICategory::Ctrl
|
2021-07-02 03:11:11 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-07-02 03:12:30 +00:00
|
|
|
|
(1 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
2021-07-02 18:14:02 +00:00
|
|
|
|
(2 offs n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(3 imin n_id d_id r_s f_def stp_d -1.0, 1.0, -1.0)
|
|
|
|
|
(4 imax n_id d_id r_s f_def stp_d -1.0, 1.0, 1.0)
|
|
|
|
|
(5 min n_id d_id r_s f_def stp_d -1.0, 1.0, -1.0)
|
|
|
|
|
(6 max n_id d_id r_s f_def stp_d -1.0, 1.0, 1.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{7 0 clip setting(0) mode fa_map_clip 0 1}
|
2021-07-02 03:11:11 +00:00
|
|
|
|
[0 sig],
|
2021-08-31 03:00:27 +00:00
|
|
|
|
quant => Quant UIType::Generic UICategory::Ctrl
|
2021-08-29 08:59:59 +00:00
|
|
|
|
(0 freq n_pit d_pit r_id f_freq stp_d -1.0, 0.5647131, 440.0)
|
2021-08-28 17:56:08 +00:00
|
|
|
|
(1 oct n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0)
|
2022-06-28 04:31:36 +00:00
|
|
|
|
{2 0 keys setting(0) keys fa_quant 0 0}
|
2021-08-30 01:55:42 +00:00
|
|
|
|
[0 sig]
|
|
|
|
|
[1 t],
|
2021-08-31 03:00:27 +00:00
|
|
|
|
cqnt => CQnt UIType::Generic UICategory::Ctrl
|
2021-08-24 01:59:41 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
2021-08-25 03:07:24 +00:00
|
|
|
|
(1 oct n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0)
|
2022-06-28 04:31:36 +00:00
|
|
|
|
{2 0 keys setting(0) keys fa_cqnt 0 0}
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 1 omin setting(0) mode fa_cqnt_omin 0 4}
|
|
|
|
|
{4 2 omax setting(0) mode fa_cqnt_omax 0 4}
|
2021-08-30 01:55:42 +00:00
|
|
|
|
[0 sig]
|
|
|
|
|
[1 t],
|
2021-08-15 10:49:39 +00:00
|
|
|
|
tseq => TSeq UIType::Generic UICategory::Mod
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
2021-06-27 21:38:14 +00:00
|
|
|
|
(1 trig n_id n_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{2 0 cmode setting(1) mode fa_tseq_cmode 0 2}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
[0 trk1]
|
|
|
|
|
[1 trk2]
|
|
|
|
|
[2 trk3]
|
|
|
|
|
[3 trk4]
|
|
|
|
|
[4 trk5]
|
2021-06-04 18:24:55 +00:00
|
|
|
|
[5 trk6]
|
|
|
|
|
[6 gat1]
|
|
|
|
|
[7 gat2]
|
|
|
|
|
[8 gat3]
|
|
|
|
|
[9 gat4]
|
|
|
|
|
[10 gat5]
|
|
|
|
|
[11 gat6],
|
2021-05-19 01:58:33 +00:00
|
|
|
|
sampl => Sampl UIType::Generic UICategory::Osc
|
2021-07-02 18:08:11 +00:00
|
|
|
|
(0 freq n_pit d_pit r_fq f_def stp_d -1.0, 0.564713133, 440.0)
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(1 trig n_id n_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(2 offs n_id n_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
|
|
|
|
(3 len n_id n_id r_id f_def stp_d 0.0, 1.0, 1.0)
|
2021-06-19 08:50:39 +00:00
|
|
|
|
(4 dcms n_declick d_declick r_dc_ms f_ms stp_m 0.0, 1.0, 3.0)
|
2021-06-11 03:05:42 +00:00
|
|
|
|
(5 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{6 0 sample audio_unloaded("") sample f_def 0 0}
|
|
|
|
|
{7 1 pmode setting(0) mode fa_sampl_pmode 0 1}
|
|
|
|
|
{8 2 dclick setting(0) mode fa_sampl_dclick 0 1}
|
|
|
|
|
{9 3 dir setting(0) mode fa_sampl_dir 0 1}
|
2021-05-19 01:58:33 +00:00
|
|
|
|
[0 sig],
|
2021-06-06 16:09:21 +00:00
|
|
|
|
// node_param_idx
|
|
|
|
|
// name denorm round format steps norm norm denorm
|
|
|
|
|
// norm_fun fun fun fun def min max default
|
2021-05-18 03:11:19 +00:00
|
|
|
|
sin => Sin UIType::Generic UICategory::Osc
|
2021-07-04 06:39:17 +00:00
|
|
|
|
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
2021-05-18 03:11:19 +00:00
|
|
|
|
[0 sig],
|
2021-07-25 11:32:16 +00:00
|
|
|
|
bosc => BOsc UIType::Generic UICategory::Osc
|
|
|
|
|
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
|
|
|
|
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
|
|
|
|
(2 pw n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 0 wtype setting(0) mode fa_bosc_wtype 0 3}
|
2021-07-25 11:32:16 +00:00
|
|
|
|
[0 sig],
|
2021-08-04 04:51:31 +00:00
|
|
|
|
vosc => VOsc UIType::Generic UICategory::Osc
|
|
|
|
|
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
|
|
|
|
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
|
|
|
|
(2 d n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(3 v n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2021-08-05 03:48:00 +00:00
|
|
|
|
(4 vs n_vps d_vps r_vps f_defvlp stp_d 0.0, 1.0, 0.0)
|
2021-08-05 04:12:50 +00:00
|
|
|
|
(5 damt n_id n_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{6 0 dist setting(0) mode fa_distort 0 3}
|
|
|
|
|
{7 1 ovrsmpl setting(1) mode fa_vosc_ovrsmpl 0 1}
|
2021-08-04 04:51:31 +00:00
|
|
|
|
[0 sig],
|
2021-10-30 08:03:45 +00:00
|
|
|
|
bowstri => BowStri UIType::Generic UICategory::Osc
|
|
|
|
|
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
|
|
|
|
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
|
|
|
|
(2 vel n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(3 force n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(4 pos n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
[0 sig],
|
2021-05-18 03:11:19 +00:00
|
|
|
|
out => Out UIType::Generic UICategory::IOUtil
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-06-15 03:12:53 +00:00
|
|
|
|
(2 gain n_ogin d_ogin r_id f_def stp_d 0.0, 1.0, 1.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
// node_param_idx UI widget type (mode, knob, sample)
|
|
|
|
|
// | atom_idx | format fun
|
|
|
|
|
// | | name constructor| | min max
|
|
|
|
|
// | | | | def|ult_v|lue | /
|
|
|
|
|
// | | | | | | | | |
|
|
|
|
|
{3 0 mono setting(0) mode fa_out_mono 0 1},
|
2021-06-03 03:10:29 +00:00
|
|
|
|
fbwr => FbWr UIType::Generic UICategory::IOUtil
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0),
|
2021-06-03 03:10:29 +00:00
|
|
|
|
fbrd => FbRd UIType::Generic UICategory::IOUtil
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(0 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
2021-06-03 03:10:29 +00:00
|
|
|
|
[0 sig],
|
2022-07-25 03:55:54 +00:00
|
|
|
|
scope => Scope UIType::Generic UICategory::IOUtil
|
2022-07-25 04:15:48 +00:00
|
|
|
|
(0 in1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(0 in2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(0 in3 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0),
|
2021-08-06 03:49:03 +00:00
|
|
|
|
ad => Ad UIType::Generic UICategory::Mod
|
2021-06-13 06:48:42 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
2021-06-15 03:12:53 +00:00
|
|
|
|
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(2 atk n_env d_env r_ems f_ms stp_m 0.0, 1.0, 3.0)
|
|
|
|
|
(3 dcy n_env d_env r_ems f_ms stp_m 0.0, 1.0, 10.0)
|
|
|
|
|
(4 ashp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(5 dshp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{6 0 mult setting(0) mode fa_ad_mult 0 2}
|
2021-06-15 03:12:53 +00:00
|
|
|
|
[0 sig]
|
|
|
|
|
[1 eoet],
|
2021-08-13 03:13:17 +00:00
|
|
|
|
tslfo => TsLFO UIType::Generic UICategory::Mod
|
2021-08-06 03:49:03 +00:00
|
|
|
|
(0 time n_lfot d_lfot r_lfot f_lfot stp_f 0.0, 1.0, 1000.0)
|
|
|
|
|
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(2 rev n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
[0 sig],
|
2021-08-13 03:12:42 +00:00
|
|
|
|
rndwk => RndWk UIType::Generic UICategory::Mod
|
2021-08-12 20:12:24 +00:00
|
|
|
|
(0 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-08-13 03:12:42 +00:00
|
|
|
|
(1 step n_id d_id r_id f_def stp_d 0.0, 1.0, 0.2)
|
2021-08-12 20:12:24 +00:00
|
|
|
|
(2 offs n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(3 min n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
2021-08-13 03:12:42 +00:00
|
|
|
|
(4 max n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
|
2021-08-13 18:27:47 +00:00
|
|
|
|
(5 slew n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 75.0)
|
2021-08-12 20:12:24 +00:00
|
|
|
|
[0 sig],
|
2021-06-19 08:50:39 +00:00
|
|
|
|
delay => Delay UIType::Generic UICategory::Signal
|
2021-06-22 03:04:11 +00:00
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-06-20 11:14:19 +00:00
|
|
|
|
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-06-22 03:04:11 +00:00
|
|
|
|
(2 time n_time d_time r_tms f_ms stp_m 0.0, 1.0, 250.0)
|
2021-06-23 03:07:32 +00:00
|
|
|
|
(3 fb n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-06-20 11:14:19 +00:00
|
|
|
|
(4 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{5 0 mode setting(0) mode fa_delay_mode 0 1}
|
2021-06-19 08:50:39 +00:00
|
|
|
|
[0 sig],
|
2021-06-28 03:10:46 +00:00
|
|
|
|
allp => AllP UIType::Generic UICategory::Signal
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 time n_ftme d_ftme r_fms f_ms stp_m 0.0, 1.0, 25.0)
|
|
|
|
|
(2 g n_id d_id r_id f_def stp_d -1.0, 1.0, 0.7)
|
|
|
|
|
[0 sig],
|
2021-08-06 03:05:47 +00:00
|
|
|
|
comb => Comb UIType::Generic UICategory::Signal
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 time n_ftme d_ftme r_fms f_ms stp_m 0.0, 1.0, 25.0)
|
|
|
|
|
(2 g n_id d_id r_id f_def stp_d -1.0, 1.0, 0.7)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 0 mode setting(0) mode fa_comb_mode 0 1}
|
2021-08-06 03:05:47 +00:00
|
|
|
|
[0 sig],
|
2021-06-30 01:55:26 +00:00
|
|
|
|
noise => Noise UIType::Generic UICategory::Osc
|
2021-07-17 07:47:57 +00:00
|
|
|
|
(0 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 0.5)
|
2021-07-02 18:14:02 +00:00
|
|
|
|
(1 offs n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{2 0 mode setting(0) mode fa_noise_mode 0 1}
|
2021-06-30 01:53:30 +00:00
|
|
|
|
[0 sig],
|
2021-07-10 20:16:55 +00:00
|
|
|
|
sfilter => SFilter UIType::Generic UICategory::Signal
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 1000.0)
|
2021-07-15 03:09:48 +00:00
|
|
|
|
(2 res n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{3 0 ftype setting(8) mode fa_sfilter_type 0 13}
|
2021-07-10 20:16:55 +00:00
|
|
|
|
[0 sig],
|
2021-08-05 03:20:47 +00:00
|
|
|
|
biqfilt => BiqFilt UIType::Generic UICategory::Signal
|
|
|
|
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
(1 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 1000.0)
|
|
|
|
|
(2 q n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(3 gain n_ogin d_ogin r_id f_def stp_d 0.0, 1.0, 1.0)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{4 0 ftype setting(0) mode fa_biqfilt_type 0 1}
|
|
|
|
|
{5 1 order setting(0) mode fa_biqfilt_ord 0 3}
|
2021-08-05 03:20:47 +00:00
|
|
|
|
[0 sig],
|
2021-08-07 14:56:59 +00:00
|
|
|
|
pverb => PVerb UIType::Generic UICategory::Signal
|
|
|
|
|
( 0 in_l n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
|
|
|
|
( 1 in_r n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
2021-08-13 18:27:47 +00:00
|
|
|
|
( 2 predly n_timz d_timz r_tmz f_ms stp_m 0.0, 1.0, 0.0)
|
2021-08-07 14:56:59 +00:00
|
|
|
|
( 3 size n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
( 4 dcy n_id d_id r_id f_def stp_d 0.0, 1.0, 0.25)
|
|
|
|
|
( 5 ilpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 22050.0)
|
|
|
|
|
( 6 ihpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 0.0)
|
2021-08-08 10:51:31 +00:00
|
|
|
|
( 7 dif n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
|
2021-08-07 14:56:59 +00:00
|
|
|
|
( 8 dmix n_id d_id r_id f_def stp_d 0.0, 1.0, 1.0)
|
|
|
|
|
( 9 mspeed n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
2021-08-13 18:27:47 +00:00
|
|
|
|
(10 mshp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
(11 mdepth n_id d_id r_id f_def stp_d 0.0, 1.0, 0.2)
|
2021-08-07 14:56:59 +00:00
|
|
|
|
(12 rlpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 22050.0)
|
|
|
|
|
(13 rhpf n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 0.0)
|
|
|
|
|
(14 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
|
|
|
|
[0 sig_l]
|
|
|
|
|
[1 sig_r],
|
2021-05-18 03:11:19 +00:00
|
|
|
|
test => Test UIType::Generic UICategory::IOUtil
|
2021-06-06 16:09:21 +00:00
|
|
|
|
(0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
2022-06-28 01:52:10 +00:00
|
|
|
|
{1 0 p param(0.0) knob fa_test_s 0 10}
|
|
|
|
|
{2 1 trig param(0.0) knob fa_test_s 0 0}
|
2021-06-23 03:07:32 +00:00
|
|
|
|
[0 sig]
|
2021-07-10 19:27:18 +00:00
|
|
|
|
[1 tsig]
|
|
|
|
|
[2 out2]
|
|
|
|
|
[3 out3]
|
|
|
|
|
[4 out4]
|
|
|
|
|
[5 outc],
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
2022-07-17 09:58:28 +00:00
|
|
|
|
};
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UICategory {
|
|
|
|
|
#[allow(unused_assignments)]
|
|
|
|
|
pub fn get_node_ids<F: FnMut(NodeId)>(&self, mut skip: usize, mut fun: F) {
|
|
|
|
|
macro_rules! make_cat_lister {
|
|
|
|
|
($s1: ident => $v1: ident,
|
|
|
|
|
$($str: ident => $variant: ident
|
|
|
|
|
UIType:: $gui_type: ident
|
|
|
|
|
UICategory:: $ui_cat: ident
|
|
|
|
|
$(($in_idx: literal $para: ident
|
2021-06-04 03:06:41 +00:00
|
|
|
|
$n_fun: ident $d_fun: ident $r_fun: ident $f_fun: ident
|
2021-06-06 16:09:21 +00:00
|
|
|
|
$steps: ident $min: expr, $max: expr, $def: expr))*
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$({$in_at_idx: literal $at_idx: literal $atom: ident
|
2022-06-28 01:52:10 +00:00
|
|
|
|
$at_fun: ident ($at_init: expr) $at_ui: ident $fa_fun: ident
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$amin: literal $amax: literal})*
|
|
|
|
|
$([$out_idx: literal $out: ident])*
|
|
|
|
|
,)+
|
|
|
|
|
) => {
|
|
|
|
|
$(if UICategory::$ui_cat == *self {
|
|
|
|
|
if skip == 0 {
|
|
|
|
|
fun(NodeId::$variant(0));
|
|
|
|
|
} else {
|
|
|
|
|
skip -= 1
|
|
|
|
|
}
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
node_list! {make_cat_lister};
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 04:21:04 +00:00
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum RandNodeSelector {
|
|
|
|
|
Any,
|
|
|
|
|
OnlyUseful,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn rand_node_satisfies_spec(nid: NodeId, sel: RandNodeSelector) -> bool {
|
|
|
|
|
if let NodeId::Nop = nid {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match sel {
|
|
|
|
|
RandNodeSelector::Any => true,
|
2022-07-17 09:58:28 +00:00
|
|
|
|
RandNodeSelector::OnlyUseful => match nid {
|
|
|
|
|
NodeId::Nop => false,
|
|
|
|
|
NodeId::Out(_) => false,
|
|
|
|
|
NodeId::FbRd(_) => false,
|
|
|
|
|
NodeId::Test(_) => false,
|
|
|
|
|
_ => true,
|
2021-08-03 04:21:04 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_rand_node_id(count: usize, sel: RandNodeSelector) -> Vec<NodeId> {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
let mut sm = crate::dsp::helpers::SplitMix64::new_time_seed();
|
2021-08-03 04:21:04 +00:00
|
|
|
|
let mut out = vec![];
|
|
|
|
|
|
|
|
|
|
let mut cnt = 0;
|
|
|
|
|
while cnt < 100 && out.len() < count {
|
|
|
|
|
let cur = ALL_NODE_IDS[sm.next_u64() as usize % ALL_NODE_IDS.len()];
|
|
|
|
|
|
|
|
|
|
if rand_node_satisfies_spec(cur, sel) {
|
|
|
|
|
out.push(cur);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cnt += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while out.len() < count {
|
|
|
|
|
out.push(NodeId::Nop);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
/// Holds information about the node type that was allocated.
|
|
|
|
|
/// It stores the names of inputs, output and atoms for uniform
|
|
|
|
|
/// access.
|
|
|
|
|
///
|
|
|
|
|
/// The [crate::NodeConfigurator] allocates and holds instances
|
|
|
|
|
/// of this type for access by [NodeId].
|
|
|
|
|
/// See also [crate::NodeConfigurator::node_by_id] and
|
|
|
|
|
/// [crate::Matrix::info_for].
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct NodeInfo {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
node_id: NodeId,
|
|
|
|
|
inputs: Vec<&'static str>,
|
|
|
|
|
atoms: Vec<&'static str>,
|
|
|
|
|
outputs: Vec<&'static str>,
|
|
|
|
|
input_help: Vec<&'static str>,
|
|
|
|
|
output_help: Vec<&'static str>,
|
|
|
|
|
node_help: &'static str,
|
|
|
|
|
node_desc: &'static str,
|
|
|
|
|
node_name: &'static str,
|
|
|
|
|
norm_v: std::rc::Rc<dyn Fn(usize, f32) -> f32>,
|
|
|
|
|
denorm_v: std::rc::Rc<dyn Fn(usize, f32) -> f32>,
|
2021-10-03 19:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Debug for NodeInfo {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
f.debug_struct("NodeInfo").field("node_id", &self.node_id).finish()
|
2021-10-03 19:27:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-03 04:21:04 +00:00
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
macro_rules! make_node_info_enum {
|
|
|
|
|
($s1: ident => $v1: ident,
|
|
|
|
|
$($str: ident => $variant: ident
|
|
|
|
|
UIType:: $gui_type: ident
|
|
|
|
|
UICategory:: $ui_cat: ident
|
|
|
|
|
$(($in_idx: literal $para: ident
|
2021-06-04 03:06:41 +00:00
|
|
|
|
$n_fun: ident $d_fun: ident $r_fun: ident $f_fun: ident
|
2021-06-06 16:09:21 +00:00
|
|
|
|
$steps: ident $min: expr, $max: expr, $def: expr))*
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$({$in_at_idx: literal $at_idx: literal $atom: ident
|
2022-06-28 01:52:10 +00:00
|
|
|
|
$at_fun: ident ($at_init: expr) $at_ui: ident $fa_fun: ident
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$amin: literal $amax: literal})*
|
|
|
|
|
$([$out_idx: literal $out: ident])*
|
|
|
|
|
,)+
|
|
|
|
|
) => {
|
|
|
|
|
impl NodeInfo {
|
|
|
|
|
/// Allocates a new [NodeInfo] from a [NodeId].
|
|
|
|
|
/// Usually you access [NodeInfo] in the UI thread via
|
|
|
|
|
/// [crate::NodeConfigurator::node_by_id]
|
|
|
|
|
/// or [crate::Matrix::info_for].
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn from_node_id(nid: NodeId) -> Self {
|
2021-05-18 03:11:19 +00:00
|
|
|
|
match nid {
|
2021-10-03 19:27:53 +00:00
|
|
|
|
NodeId::$v1 => NodeInfo {
|
|
|
|
|
node_id: crate::dsp::NodeId::Nop,
|
|
|
|
|
inputs: vec![],
|
|
|
|
|
atoms: vec![],
|
|
|
|
|
outputs: vec![],
|
|
|
|
|
input_help: vec![],
|
|
|
|
|
output_help: vec![],
|
|
|
|
|
node_help: "Nop Help",
|
|
|
|
|
node_desc: "Nop Desc",
|
|
|
|
|
node_name: "Nop",
|
|
|
|
|
|
2022-06-25 06:37:05 +00:00
|
|
|
|
norm_v: std::rc::Rc::new(|_i, x| x),
|
|
|
|
|
denorm_v: std::rc::Rc::new(|_i, x| x),
|
2021-10-03 19:27:53 +00:00
|
|
|
|
},
|
|
|
|
|
$(NodeId::$variant(_) => crate::dsp::ni::$variant(nid)),+
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Refers to an input paramter or atom of a specific
|
|
|
|
|
/// [Node] referred to by a [NodeId].
|
|
|
|
|
///
|
|
|
|
|
/// To obtain a [ParamId] you use one of these:
|
|
|
|
|
/// * [NodeId::atom_param_by_idx]
|
|
|
|
|
/// * [NodeId::inp_param_by_idx]
|
|
|
|
|
/// * [NodeId::param_by_idx]
|
|
|
|
|
/// * [NodeId::inp_param]
|
|
|
|
|
///
|
|
|
|
|
/// To obtain an input and output index for a port use:
|
|
|
|
|
/// * [NodeId::inp]
|
|
|
|
|
/// * [NodeId::out]
|
|
|
|
|
///
|
|
|
|
|
///```
|
|
|
|
|
/// use hexodsp::*;
|
|
|
|
|
/// let freq_param = NodeId::Sin(2).inp_param("freq").unwrap();
|
|
|
|
|
///
|
|
|
|
|
/// assert!(!freq_param.is_atom());
|
|
|
|
|
///
|
2021-06-06 16:09:21 +00:00
|
|
|
|
/// // Access the UI min/max and fine/coarse step values of this paramter:
|
2021-07-04 06:39:17 +00:00
|
|
|
|
/// assert_eq!(freq_param.param_min_max().unwrap(), ((-1.0, 0.5647131), (20.0, 100.0)));
|
2021-05-18 03:11:19 +00:00
|
|
|
|
///
|
|
|
|
|
/// // Access the default value:
|
|
|
|
|
/// assert_eq!(freq_param.as_atom_def().f(), 0.0);
|
|
|
|
|
///
|
|
|
|
|
/// // Normalize a value (convert frequency to the 0.0 to 1.0 range)
|
|
|
|
|
/// assert_eq!(freq_param.norm(220.0), -0.1);
|
|
|
|
|
///
|
|
|
|
|
/// // Denormalize a value (convert 0.0 to 1.0 range to frequency)
|
|
|
|
|
/// assert_eq!(freq_param.denorm(-0.1), 220.0);
|
|
|
|
|
///```
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Hash)]
|
|
|
|
|
pub struct ParamId {
|
|
|
|
|
name: &'static str,
|
|
|
|
|
node: NodeId,
|
|
|
|
|
idx: u8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ParamId {
|
|
|
|
|
pub fn none() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
name: "NOP",
|
|
|
|
|
node: NodeId::Nop,
|
|
|
|
|
idx: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn node_id(&self) -> NodeId { self.node }
|
|
|
|
|
pub fn inp(&self) -> u8 { self.idx }
|
|
|
|
|
pub fn name(&self) -> &'static str { self.name }
|
|
|
|
|
|
|
|
|
|
/// Returns true if the [ParamId] has been associated with
|
|
|
|
|
/// the atoms of a Node, and not the paramters. Even if the
|
|
|
|
|
/// Atom is a `param()`.
|
|
|
|
|
pub fn is_atom(&self) -> bool {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => false,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => false,)*
|
|
|
|
|
$($in_at_idx => true,)*
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-28 01:52:10 +00:00
|
|
|
|
pub fn atom_ui(&self) -> Option<&'static str> {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_at_idx => Some(stringify!($at_ui)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
pub fn param_steps(&self) -> Option<(f32, f32)> {
|
2021-05-18 03:11:19 +00:00
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => Some(($min, $max)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:09:21 +00:00
|
|
|
|
pub fn param_min_max(&self) -> Option<((f32, f32), (f32, f32))> {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => Some((($min, $max), $steps!())),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-04 18:24:55 +00:00
|
|
|
|
pub fn format(&self, f: &mut dyn std::io::Write, v: f32) -> Option<std::io::Result<()>> {
|
2021-06-04 03:06:41 +00:00
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => Some($f_fun!(f, v, $d_fun!(v))),)*
|
|
|
|
|
$($in_at_idx => Some($fa_fun!(f, v, v)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn setting_min_max(&self) -> Option<(i64, i64)> {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_at_idx => Some(($amin, $amax)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn as_atom_def(&self) -> SAtom {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => SAtom::param(0.0),
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => SAtom::param(crate::dsp::norm_def::$variant::$para()),)*
|
|
|
|
|
$($in_at_idx => SAtom::$at_fun($at_init),)*
|
|
|
|
|
_ => SAtom::param(0.0),
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn norm_def(&self) -> f32 {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => 0.0,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => crate::dsp::norm_def::$variant::$para(),)*
|
|
|
|
|
_ => 0.0,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 11:19:31 +00:00
|
|
|
|
pub fn round(&self, v: f32, coarse: bool) -> f32 {
|
2021-06-04 03:06:41 +00:00
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => 0.0,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
2021-06-06 11:19:31 +00:00
|
|
|
|
$($in_idx => crate::dsp::round::$variant::$para(v, coarse),)*
|
2021-06-04 03:06:41 +00:00
|
|
|
|
_ => 0.0,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn norm(&self, v: f32) -> f32 {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => 0.0,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => crate::dsp::norm_v::$variant::$para(v),)*
|
|
|
|
|
_ => 0.0,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn denorm(&self, v: f32) -> f32 {
|
|
|
|
|
match self.node {
|
|
|
|
|
NodeId::$v1 => 0.0,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match self.idx {
|
|
|
|
|
$($in_idx => crate::dsp::denorm_v::$variant::$para(v),)*
|
|
|
|
|
_ => 0.0,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This enum is a collection of all implemented modules (aka nodes)
|
|
|
|
|
/// that are implemented. The associated `u8` index is the so called
|
|
|
|
|
/// _instance_ of the corresponding [Node] type.
|
|
|
|
|
///
|
|
|
|
|
/// This is the primary way in this library to refer to a specific node
|
|
|
|
|
/// in the node graph that is managed by [crate::NodeConfigurator]
|
|
|
|
|
/// and executed by [crate::NodeExecutor].
|
|
|
|
|
///
|
|
|
|
|
/// To see how to actually use this, refer to the documentation
|
|
|
|
|
/// of [crate::Cell], where you will find an example.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Hash)]
|
|
|
|
|
pub enum NodeId {
|
|
|
|
|
$v1,
|
|
|
|
|
$($variant(u8)),+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for NodeId {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
2021-07-15 17:32:33 +00:00
|
|
|
|
NodeId::$v1 => write!(f, "{}", stringify!($v1)),
|
|
|
|
|
$(NodeId::$variant(i) => write!(f, "{} {}", stringify!($variant), i)),+
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl NodeId {
|
|
|
|
|
pub fn to_instance(&self, instance: usize) -> NodeId {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => NodeId::$v1,
|
|
|
|
|
$(NodeId::$variant(_) => NodeId::$variant(instance as u8)),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn graph_fun(&self) -> Option<GraphFun> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => crate::dsp::$variant::graph_fun()),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eq_variant(&self, other: &NodeId) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => *other == NodeId::$v1,
|
|
|
|
|
$(NodeId::$variant(_) =>
|
|
|
|
|
if let NodeId::$variant(_) = other { true }
|
|
|
|
|
else { false }),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn from_node_info(ni: &NodeInfo) -> NodeId { ni.to_id() }
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
2021-07-17 07:47:57 +00:00
|
|
|
|
pub fn label(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => stringify!($v1),
|
|
|
|
|
$(NodeId::$variant(_) => stringify!($variant)),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => stringify!($s1),
|
|
|
|
|
$(NodeId::$variant(_) => stringify!($str)),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn from_str(name: &str) -> Self {
|
|
|
|
|
match name {
|
|
|
|
|
stringify!($s1) => NodeId::$v1,
|
|
|
|
|
$(stringify!($str) => NodeId::$variant(0)),+,
|
|
|
|
|
_ => NodeId::Nop,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_type(&self) -> UIType {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => UIType::Generic,
|
|
|
|
|
$(NodeId::$variant(_) => UIType::$gui_type),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_category(&self) -> UICategory {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => UICategory::None,
|
|
|
|
|
$(NodeId::$variant(_) => UICategory::$ui_cat),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-25 20:49:33 +00:00
|
|
|
|
/// Consistently initialize the phase for oscillators.
|
|
|
|
|
/// This does some fixed phase offset for the first 3
|
|
|
|
|
/// instances, which is usually relied on by the automated
|
|
|
|
|
/// tests.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn init_phase(&self) -> f32 {
|
|
|
|
|
// The first 3 instances get a fixed predefined phase to
|
|
|
|
|
// not mess up the automated tests so easily.
|
|
|
|
|
match self.instance() {
|
|
|
|
|
0 => 0.0,
|
|
|
|
|
1 => 0.05,
|
|
|
|
|
2 => 0.1,
|
|
|
|
|
// 0.25 just to protect against sine cancellation
|
|
|
|
|
_ => crate::dsp::helpers::rand_01() * 0.25
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
/// This maps the atom index of the node to the absolute
|
|
|
|
|
/// ParamId in the GUI (and in the [crate::matrix::Matrix]).
|
|
|
|
|
/// The Atom/Param duality is a bit weird because they share
|
|
|
|
|
/// the same ID namespace for the UI. But in the actual
|
|
|
|
|
/// backend, they are split. So the actual splitting happens
|
|
|
|
|
/// in the [crate::matrix::Matrix].
|
|
|
|
|
pub fn atom_param_by_idx(&self, idx: usize) -> Option<ParamId> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match idx {
|
|
|
|
|
$($at_idx => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($atom),
|
|
|
|
|
idx: $in_at_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn inp_param_by_idx(&self, idx: usize) -> Option<ParamId> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match idx {
|
|
|
|
|
$($in_idx => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($para),
|
|
|
|
|
idx: $in_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn param_by_idx(&self, idx: usize) -> Option<ParamId> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match idx {
|
|
|
|
|
$($in_idx => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($para),
|
|
|
|
|
idx: $in_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
$($in_at_idx => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($atom),
|
|
|
|
|
idx: $in_at_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn inp_param(&self, name: &str) -> Option<ParamId> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match name {
|
|
|
|
|
$(stringify!($para) => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($para),
|
|
|
|
|
idx: $in_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
$(stringify!($atom) => Some(ParamId {
|
|
|
|
|
node: *self,
|
|
|
|
|
name: stringify!($atom),
|
|
|
|
|
idx: $in_at_idx,
|
|
|
|
|
}),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn inp(&self, name: &str) -> Option<u8> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match name {
|
|
|
|
|
$(stringify!($para) => Some($in_idx),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-24 21:00:09 +00:00
|
|
|
|
pub fn inp_name_by_idx(&self, idx: u8) -> Option<&'static str> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match idx {
|
|
|
|
|
$($in_idx => Some(stringify!($para)),)*
|
|
|
|
|
$($in_at_idx => Some(stringify!($atom)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
pub fn out_name_by_idx(&self, idx: u8) -> Option<&'static str> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match idx {
|
|
|
|
|
$($out_idx => Some(stringify!($out)),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn out(&self, name: &str) -> Option<u8> {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => None,
|
|
|
|
|
$(NodeId::$variant(_) => {
|
|
|
|
|
match name {
|
|
|
|
|
$(stringify!($out) => Some($out_idx),)*
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn instance(&self) -> usize {
|
|
|
|
|
match self {
|
|
|
|
|
NodeId::$v1 => 0,
|
|
|
|
|
$(NodeId::$variant(i) => *i as usize),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-01 18:35:57 +00:00
|
|
|
|
pub const ALL_NODE_IDS : &'static [NodeId] = &[$(NodeId::$variant(0)),+];
|
|
|
|
|
|
2021-06-07 03:03:29 +00:00
|
|
|
|
#[allow(non_snake_case, unused_variables)]
|
2021-06-04 03:06:41 +00:00
|
|
|
|
pub mod round {
|
|
|
|
|
$(pub mod $variant {
|
2021-06-06 11:19:31 +00:00
|
|
|
|
$(#[inline] pub fn $para(x: f32, coarse: bool) -> f32 { $r_fun!(x, coarse) })*
|
2021-06-04 03:06:41 +00:00
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod denorm_v {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(x: f32) -> f32 { $d_fun!(x) })*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod norm_def {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para() -> f32 { $n_fun!($def) })*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod norm_v {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(v: f32) -> f32 { $n_fun!(v) })*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod denorm {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(buf: &crate::dsp::ProcBuf, frame: usize) -> f32 {
|
|
|
|
|
$d_fun!(buf.read(frame))
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 07:24:41 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod denorm_offs {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(buf: &crate::dsp::ProcBuf, offs_val: f32, frame: usize) -> f32 {
|
|
|
|
|
$d_fun!(buf.read(frame) + offs_val)
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod inp_dir {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(buf: &crate::dsp::ProcBuf, frame: usize) -> f32 {
|
|
|
|
|
buf.read(frame)
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod inp {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(inputs: &[crate::dsp::ProcBuf]) -> &crate::dsp::ProcBuf {
|
|
|
|
|
&inputs[$in_idx]
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod at {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $atom(atoms: &[crate::dsp::SAtom]) -> &crate::dsp::SAtom {
|
|
|
|
|
&atoms[$at_idx]
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod out_dir {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $out(outputs: &mut [crate::dsp::ProcBuf], frame: usize, v: f32) {
|
|
|
|
|
outputs[$out_idx].write(frame, v);
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod out {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $out(outputs: &mut [crate::dsp::ProcBuf]) -> &mut crate::dsp::ProcBuf {
|
|
|
|
|
&mut outputs[$out_idx]
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-10 19:27:18 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod out_buf {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $out(outputs: &mut [crate::dsp::ProcBuf]) -> crate::dsp::ProcBuf {
|
|
|
|
|
outputs[$out_idx]
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-23 03:07:32 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod out_idx {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $out() -> usize { $out_idx })*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-10 19:27:18 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod is_out_con {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $out(nctx: &crate::dsp::NodeContext) -> bool {
|
|
|
|
|
nctx.out_connected & (1 << $out_idx) != 0x0
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-10 19:29:58 +00:00
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub mod is_in_con {
|
|
|
|
|
$(pub mod $variant {
|
|
|
|
|
$(#[inline] pub fn $para(nctx: &crate::dsp::NodeContext) -> bool {
|
|
|
|
|
nctx.in_connected & (1 << $in_idx) != 0x0
|
|
|
|
|
})*
|
|
|
|
|
})+
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-03 05:18:43 +00:00
|
|
|
|
#[allow(unused_mut)]
|
2021-05-18 03:11:19 +00:00
|
|
|
|
mod ni {
|
|
|
|
|
$(
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn $variant(node_id: crate::dsp::NodeId) -> crate::dsp::NodeInfo {
|
|
|
|
|
let mut input_help = vec![$(crate::dsp::$variant::$para,)*];
|
|
|
|
|
$(input_help.push(crate::dsp::$variant::$atom);)*
|
|
|
|
|
|
|
|
|
|
crate::dsp::NodeInfo {
|
|
|
|
|
node_id,
|
|
|
|
|
inputs: vec![$(stringify!($para),)*],
|
|
|
|
|
atoms: vec![$(stringify!($atom),)*],
|
|
|
|
|
outputs: vec![$(stringify!($out),)*],
|
|
|
|
|
|
|
|
|
|
input_help,
|
|
|
|
|
output_help: vec![$(crate::dsp::$variant::$out,)*],
|
|
|
|
|
node_help: crate::dsp::$variant::HELP,
|
|
|
|
|
node_desc: crate::dsp::$variant::DESC,
|
|
|
|
|
node_name: stringify!($variant),
|
|
|
|
|
|
|
|
|
|
norm_v:
|
|
|
|
|
std::rc::Rc::new(|i, x|
|
|
|
|
|
match i {
|
|
|
|
|
$($in_idx => crate::dsp::norm_v::$variant::$para(x),)+
|
|
|
|
|
_ => x,
|
|
|
|
|
}),
|
|
|
|
|
denorm_v:
|
|
|
|
|
std::rc::Rc::new(|i, x|
|
|
|
|
|
match i {
|
|
|
|
|
$($in_idx => crate::dsp::denorm_v::$variant::$para(x),)+
|
|
|
|
|
_ => x,
|
|
|
|
|
}),
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)+
|
2021-10-03 19:27:53 +00:00
|
|
|
|
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl NodeInfo {
|
|
|
|
|
pub fn from(s: &str) -> Self {
|
|
|
|
|
match s {
|
2021-10-03 19:27:53 +00:00
|
|
|
|
$(stringify!($str) => crate::dsp::ni::$variant(NodeId::$variant(0)),)+
|
|
|
|
|
_ => NodeInfo::from_node_id(NodeId::Nop),
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn name(&self) -> &'static str { self.node_name }
|
|
|
|
|
|
|
|
|
|
pub fn in_name(&self, in_idx: usize) -> Option<&'static str> {
|
|
|
|
|
if let Some(s) = self.inputs.get(in_idx) {
|
|
|
|
|
Some(*s)
|
|
|
|
|
} else {
|
|
|
|
|
Some(*(self.atoms.get(in_idx)?))
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn at_name(&self, in_idx: usize) -> Option<&'static str> {
|
|
|
|
|
Some(*(self.atoms.get(in_idx)?))
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn out_name(&self, out_idx: usize) -> Option<&'static str> {
|
|
|
|
|
Some(*(self.outputs.get(out_idx)?))
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn in_help(&self, in_idx: usize) -> Option<&'static str> {
|
|
|
|
|
Some(*self.input_help.get(in_idx)?)
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn out_help(&self, out_idx: usize) -> Option<&'static str> {
|
|
|
|
|
Some(*(self.output_help.get(out_idx)?))
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn norm(&self, in_idx: usize, x: f32) -> f32 {
|
|
|
|
|
(*self.norm_v)(in_idx, x)
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn denorm(&self, in_idx: usize, x: f32) -> f32 {
|
|
|
|
|
(*self.denorm_v)(in_idx, x)
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 19:27:53 +00:00
|
|
|
|
pub fn desc(&self) -> &'static str { self.node_desc }
|
|
|
|
|
pub fn help(&self) -> &'static str { self.node_help }
|
|
|
|
|
|
|
|
|
|
pub fn out_count(&self) -> usize { self.outputs.len() }
|
|
|
|
|
pub fn in_count(&self) -> usize { self.inputs.len() }
|
|
|
|
|
pub fn at_count(&self) -> usize { self.atoms.len() }
|
|
|
|
|
|
|
|
|
|
pub fn to_id(&self) -> NodeId { self.node_id }
|
|
|
|
|
|
2021-08-01 14:35:56 +00:00
|
|
|
|
pub fn default_output(&self) -> Option<u8> {
|
|
|
|
|
if self.out_count() > 0 {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn default_input(&self) -> Option<u8> {
|
|
|
|
|
if self.in_count() > 0 {
|
|
|
|
|
Some(0)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! make_node_enum {
|
|
|
|
|
($s1: ident => $v1: ident,
|
|
|
|
|
$($str: ident => $variant: ident
|
|
|
|
|
UIType:: $gui_type: ident
|
|
|
|
|
UICategory:: $ui_cat: ident
|
|
|
|
|
$(($in_idx: literal $para: ident
|
2021-06-04 03:06:41 +00:00
|
|
|
|
$n_fun: ident $d_fun: ident $r_fun: ident $f_fun: ident
|
2021-06-06 16:09:21 +00:00
|
|
|
|
$steps: ident $min: expr, $max: expr, $def: expr))*
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$({$in_at_idx: literal $at_idx: literal $atom: ident
|
2022-06-28 01:52:10 +00:00
|
|
|
|
$at_fun: ident ($at_init: expr) $at_ui: ident $fa_fun: ident
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$amin: literal $amax: literal})*
|
|
|
|
|
$([$out_idx: literal $out: ident])*
|
|
|
|
|
,)+
|
|
|
|
|
) => {
|
|
|
|
|
/// Represents the actually by the DSP thread ([crate::NodeExecutor])
|
|
|
|
|
/// executed [Node]. You don't construct this directly, but let the
|
|
|
|
|
/// [crate::NodeConfigurator] or more abstract types like
|
|
|
|
|
/// [crate::Matrix] do this for you. See also [NodeId] for a way to
|
|
|
|
|
/// refer to these.
|
|
|
|
|
///
|
|
|
|
|
/// The method [Node::process] is called by [crate::NodeExecutor]
|
|
|
|
|
/// and comes with the overhead of a big `match` statement.
|
|
|
|
|
///
|
|
|
|
|
/// This is the only point of primitive polymorphism inside
|
|
|
|
|
/// the DSP graph. Dynamic polymorphism via the trait object
|
|
|
|
|
/// is not done, as I hope the `match` dispatch is a slight bit faster
|
|
|
|
|
/// because it's more static.
|
|
|
|
|
///
|
|
|
|
|
/// The size of a [Node] is also limited and protected by a test
|
|
|
|
|
/// in the test suite. The size should not be needlessly increased
|
|
|
|
|
/// by implementations, in the hope to achieve better
|
|
|
|
|
/// cache locality. All allocated [Node]s are held in a big
|
|
|
|
|
/// continuous vector inside the [crate::NodeExecutor].
|
|
|
|
|
///
|
|
|
|
|
/// The function [node_factory] is responsible for actually creating
|
|
|
|
|
/// the [Node].
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum Node {
|
|
|
|
|
/// An empty node that does nothing. It's a placeholder
|
|
|
|
|
/// for non allocated nodes.
|
|
|
|
|
$v1,
|
|
|
|
|
$($variant { node: $variant },)+
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Node {
|
|
|
|
|
/// Returns the [NodeId] that can be used to refer to this node.
|
|
|
|
|
/// The node does not store it's instance index, so you have to
|
|
|
|
|
/// provide it. If the instance is of no meaning for the
|
|
|
|
|
/// use case pass 0 to `instance`.
|
|
|
|
|
pub fn to_id(&self, instance: usize) -> NodeId {
|
|
|
|
|
match self {
|
|
|
|
|
Node::$v1 => NodeId::$v1,
|
|
|
|
|
$(Node::$variant { .. } => NodeId::$variant(instance as u8)),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Resets any state of this [Node], such as
|
|
|
|
|
/// any internal state variables or counters or whatever.
|
|
|
|
|
/// The [Node] should just behave as if it was freshly returned
|
|
|
|
|
/// from [node_factory].
|
|
|
|
|
pub fn reset(&mut self) {
|
|
|
|
|
match self {
|
|
|
|
|
Node::$v1 => {},
|
|
|
|
|
$(Node::$variant { node } => {
|
|
|
|
|
node.reset();
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sets the current sample rate this [Node] should operate at.
|
|
|
|
|
pub fn set_sample_rate(&mut self, sample_rate: f32) {
|
|
|
|
|
match self {
|
|
|
|
|
Node::$v1 => {},
|
|
|
|
|
$(Node::$variant { node } => {
|
|
|
|
|
node.set_sample_rate(sample_rate);
|
|
|
|
|
}),+
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
node_list! {make_node_info_enum}
|
|
|
|
|
node_list! {make_node_enum}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
|
|
|
|
|
pub fn node_factory(node_id: NodeId) -> Option<(Node, NodeInfo)> {
|
|
|
|
|
macro_rules! make_node_factory_match {
|
|
|
|
|
($s1: expr => $v1: ident,
|
|
|
|
|
$($str: ident => $variant: ident
|
|
|
|
|
UIType:: $gui_type: ident
|
|
|
|
|
UICategory:: $ui_cat: ident
|
|
|
|
|
$(($in_idx: literal $para: ident
|
2021-06-04 03:06:41 +00:00
|
|
|
|
$n_fun: ident $d_fun: ident $r_fun: ident $f_fun: ident
|
2021-06-06 16:09:21 +00:00
|
|
|
|
$steps: ident $min: expr, $max: expr, $def: expr))*
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$({$in_at_idx: literal $at_idx: literal $atom: ident
|
2022-06-28 01:52:10 +00:00
|
|
|
|
$at_fun: ident ($at_init: expr) $at_ui: ident $fa_fun: ident
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$amin: literal $amax: literal})*
|
|
|
|
|
$([$out_idx: literal $out: ident])*
|
|
|
|
|
,)+
|
|
|
|
|
) => {
|
|
|
|
|
match node_id {
|
|
|
|
|
$(NodeId::$variant(_) => Some((
|
2021-06-03 03:10:29 +00:00
|
|
|
|
Node::$variant { node: $variant::new(&node_id) },
|
2021-05-18 03:11:19 +00:00
|
|
|
|
NodeInfo::from_node_id(node_id),
|
|
|
|
|
)),)+
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
node_list! {make_node_factory_match}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Node {
|
|
|
|
|
/// This function is the heart of any DSP.
|
|
|
|
|
/// It dispatches this call to the corresponding [Node] implementation.
|
|
|
|
|
///
|
|
|
|
|
/// You don't want to call this directly, but let [crate::NodeConfigurator] and
|
|
|
|
|
/// [crate::NodeExecutor] do their magic for you.
|
|
|
|
|
///
|
|
|
|
|
/// The slices get passed a [ProcBuf] which is a super _unsafe_
|
|
|
|
|
/// buffer, that requires special care and invariants to work safely.
|
|
|
|
|
///
|
|
|
|
|
/// Arguments:
|
|
|
|
|
/// * `ctx`: The [NodeAudioContext] usually provides global context information
|
|
|
|
|
/// such as access to the actual buffers of the audio driver or access to
|
|
|
|
|
/// MIDI events.
|
|
|
|
|
/// * `atoms`: The [SAtom] settings the user can set in the UI or via
|
|
|
|
|
/// other means. These are usually non interpolated/smoothed settings.
|
|
|
|
|
/// * `params`: The smoothed input parameters as set by the user (eg. in the UI).
|
|
|
|
|
/// There is usually no reason to use these, because any parameter can be
|
|
|
|
|
/// overridden by assigning an output port to the corresponding input.
|
|
|
|
|
/// This is provided for the rare case that you still want to use the
|
2021-08-31 03:00:27 +00:00
|
|
|
|
/// value the user set in the interface, and not the input Ctrl signal.
|
2021-05-18 03:11:19 +00:00
|
|
|
|
/// * `inputs`: For each `params` parameter there is a input port.
|
|
|
|
|
/// This slice will contain either a buffer from `params` or some output
|
|
|
|
|
/// buffer from some other (previously executed) [Node]s output.
|
2021-08-31 03:00:27 +00:00
|
|
|
|
/// * `outputs`: The output buffers this node will write it's signal/Ctrl
|
2021-05-18 03:11:19 +00:00
|
|
|
|
/// results to.
|
|
|
|
|
/// * `led`: Contains the feedback [LedPhaseVals], which are used
|
|
|
|
|
/// to communicate the current value (set once per `process()` call, usually at the end)
|
|
|
|
|
/// of the most important internal signal. Usually stuff like the output
|
|
|
|
|
/// value of an oscillator, envelope or the current sequencer output
|
|
|
|
|
/// value. It also provides a second value, a so called _phase_
|
|
|
|
|
/// which is usually used by graphical frontends to determine
|
|
|
|
|
/// the phase of the oscillator, envelope or the sequencer to
|
|
|
|
|
/// display some kind of position indicator.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn process<T: NodeAudioContext>(
|
2022-07-17 09:58:28 +00:00
|
|
|
|
&mut self,
|
|
|
|
|
ctx: &mut T,
|
|
|
|
|
ectx: &mut NodeExecContext,
|
2021-07-10 19:27:18 +00:00
|
|
|
|
nctx: &NodeContext,
|
2022-07-17 09:58:28 +00:00
|
|
|
|
atoms: &[SAtom],
|
|
|
|
|
inputs: &[ProcBuf],
|
|
|
|
|
outputs: &mut [ProcBuf],
|
|
|
|
|
led: LedPhaseVals,
|
|
|
|
|
) {
|
2021-05-18 03:11:19 +00:00
|
|
|
|
macro_rules! make_node_process {
|
|
|
|
|
($s1: ident => $v1: ident,
|
|
|
|
|
$($str: ident => $variant: ident
|
|
|
|
|
UIType:: $gui_type: ident
|
|
|
|
|
UICategory:: $ui_cat: ident
|
|
|
|
|
$(($in_idx: literal $para: ident
|
2021-06-04 03:06:41 +00:00
|
|
|
|
$n_fun: ident $d_fun: ident $r_fun: ident $f_fun: ident
|
2021-06-06 16:09:21 +00:00
|
|
|
|
$steps: ident $min: expr, $max: expr, $def: expr))*
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$({$in_at_idx: literal $at_idx: literal $atom: ident
|
2022-06-28 01:52:10 +00:00
|
|
|
|
$at_fun: ident ($at_init: expr) $at_ui: ident $fa_fun: ident
|
2021-05-18 03:11:19 +00:00
|
|
|
|
$amin: literal $amax: literal})*
|
|
|
|
|
$([$out_idx: literal $out: ident])*
|
|
|
|
|
,)+
|
|
|
|
|
) => {
|
|
|
|
|
match self {
|
|
|
|
|
Node::$v1 => {},
|
2021-05-22 20:56:19 +00:00
|
|
|
|
$(Node::$variant { node } =>
|
2021-07-10 19:27:18 +00:00
|
|
|
|
node.process(ctx, ectx, nctx, atoms,
|
2021-06-02 01:59:21 +00:00
|
|
|
|
inputs, outputs, led),)+
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-17 09:58:28 +00:00
|
|
|
|
node_list! {make_node_process}
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_node_size_staying_small() {
|
2022-07-17 09:58:28 +00:00
|
|
|
|
assert_eq!(std::mem::size_of::<Node>(), 56);
|
|
|
|
|
assert_eq!(std::mem::size_of::<NodeId>(), 2);
|
|
|
|
|
assert_eq!(std::mem::size_of::<ParamId>(), 24);
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_pitch() {
|
|
|
|
|
assert_eq!(d_pit!(-0.2).round() as i32, 110_i32);
|
|
|
|
|
assert_eq!((n_pit!(110.0) * 100.0).round() as i32, -20_i32);
|
|
|
|
|
assert_eq!(d_pit!(0.0).round() as i32, 440_i32);
|
|
|
|
|
assert_eq!((n_pit!(440.0) * 100.0).round() as i32, 0_i32);
|
|
|
|
|
assert_eq!(d_pit!(0.3).round() as i32, 3520_i32);
|
|
|
|
|
assert_eq!((n_pit!(3520.0) * 100.0).round() as i32, 30_i32);
|
|
|
|
|
|
|
|
|
|
for i in 1..999 {
|
|
|
|
|
let x = (((i as f32) / 1000.0) - 0.5) * 2.0;
|
|
|
|
|
let r = d_pit!(x);
|
2021-08-16 18:23:40 +00:00
|
|
|
|
//d// println!("x={:8.5} => {:8.5}", x, r);
|
2022-07-17 09:58:28 +00:00
|
|
|
|
assert_eq!((n_pit!(r) * 10000.0).round() as i32, (x * 10000.0).round() as i32);
|
2021-05-18 03:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|