added trigger input

This commit is contained in:
Weird Constructor 2021-06-27 23:38:14 +02:00
parent de15c3cfd7
commit 9397b978dc
3 changed files with 62 additions and 12 deletions

View file

@ -453,6 +453,11 @@ impl TriggerPhaseClock {
self.clock_samples = 0; self.clock_samples = 0;
} }
#[inline]
pub fn sync(&mut self) {
self.clock_phase = 0.0;
}
#[inline] #[inline]
pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 { pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
if self.prev_trigger { if self.prev_trigger {

View file

@ -405,7 +405,8 @@ macro_rules! node_list {
[0 sig], [0 sig],
tseq => TSeq UIType::Generic UICategory::CV tseq => TSeq UIType::Generic UICategory::CV
(0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0) (0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
{1 0 cmode setting(1) fa_tseq_cmode 0 2} (1 trig n_id n_id r_id f_def stp_d -1.0, 1.0, 0.0)
{2 0 cmode setting(1) fa_tseq_cmode 0 2}
[0 trk1] [0 trk1]
[1 trk2] [1 trk2]
[2 trk3] [2 trk3]

View file

@ -3,7 +3,7 @@
// See README.md and COPYING for details. // See README.md and COPYING for details.
use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::nodes::{NodeAudioContext, NodeExecContext};
use crate::dsp::helpers::TriggerPhaseClock; use crate::dsp::helpers::{TriggerPhaseClock, Trigger};
use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals}; use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals};
use crate::dsp::tracker::TrackerBackend; use crate::dsp::tracker::TrackerBackend;
@ -22,12 +22,18 @@ macro_rules! fa_tseq_cmode { ($formatter: expr, $v: expr, $denorm_v: expr) => {
} } } } } }
#[derive(Debug)]
pub struct TSeqTime {
clock: TriggerPhaseClock,
trigger: Trigger,
}
/// A tracker based sequencer /// A tracker based sequencer
#[derive(Debug)] #[derive(Debug)]
pub struct TSeq { pub struct TSeq {
backend: Option<Box<TrackerBackend>>, backend: Option<Box<TrackerBackend>>,
clock: TriggerPhaseClock,
srate: f64, srate: f64,
time: Box<TSeqTime>,
} }
impl Clone for TSeq { impl Clone for TSeq {
@ -39,7 +45,10 @@ impl TSeq {
Self { Self {
backend: None, backend: None,
srate: 48000.0, srate: 48000.0,
time: Box::new(TSeqTime {
clock: TriggerPhaseClock::new(), clock: TriggerPhaseClock::new(),
trigger: Trigger::new(),
}),
} }
} }
@ -49,6 +58,8 @@ impl TSeq {
pub const clock : &'static str = pub const clock : &'static str =
"TSeq clock\nClock input\nRange: (0..1)\n"; "TSeq clock\nClock input\nRange: (0..1)\n";
pub const trig : &'static str =
"TSeq trig\nSynchronization trigger which restarts the sequence.\nRange: (-1..1)\n";
pub const cmode : &'static str = pub const cmode : &'static str =
"TSeq cmode\n'clock' input signal mode:\n\ "TSeq cmode\n'clock' input signal mode:\n\
- RowT: Trigger = advance row\n\ - RowT: Trigger = advance row\n\
@ -89,12 +100,25 @@ impl TSeq {
pub const HELP : &'static str = pub const HELP : &'static str =
r#"Tracker (based) Sequencer r#"Tracker (based) Sequencer
This sequencer gets it's speed from the clock source. The 'clock'
signal can be interpreted in different modes. But if you want to
run multiple sequencers in parallel, you want to synchronize them.
For this you can use the 'trig' input, it resets the played row to
the beginning of the sequence every time a trigger is received.
Alternatively you can run the sequencer clock using the phase mode.
With that the phase (0..1) signal on the 'clock' input determines the
exact play head position in the pattern. With this you just need to
synchronize the phase generators for different sequencers.
For an idea how to chain multiple tracker sequencers, see the next page.
This tracker provides 6 columns that each can have one of the following This tracker provides 6 columns that each can have one of the following
types: types:
- Note column: for specifying pitches. - Note column: for specifying pitches.
- Step column: for specifying non interpolated CV signals. - Step column: for specifying non interpolated CV signals.
- Value column: for specifying linearily interpolated CV signals. - Value column: for specifying linearly interpolated CV signals.
- Gate column: for specifying gates, with probability and ratcheting. - Gate column: for specifying gates, with probability and ratcheting.
Step, value and gate cells can be set to 4096 (0xFFF) different values Step, value and gate cells can be set to 4096 (0xFFF) different values
@ -102,6 +126,10 @@ or contain nothing at all. For step and value columns these values
are mapped to the 0.0-1.0 CV signal range, with 0xFFF being 1.0 are mapped to the 0.0-1.0 CV signal range, with 0xFFF being 1.0
and 0x000 being 0.0. and 0x000 being 0.0.
On the next page you can read about the gate cells and the gate outputs.
---page---
Gate Input and Output
The gate cells are differently coded: The gate cells are differently coded:
- 0x00F: The least significant nibble controls the gate length. - 0x00F: The least significant nibble controls the gate length.
@ -109,7 +137,7 @@ The gate cells are differently coded:
- 0x0F0: The second nibble controls ratcheting, with 0x0F0 being one - 0x0F0: The second nibble controls ratcheting, with 0x0F0 being one
gate per row, and 0x000 being 16 gates per row. gate per row, and 0x000 being 16 gates per row.
- 0xF00: The most significant nibble controls probability of the - 0xF00: The most significant nibble controls probability of the
whole gate cell. With 0xF00 meaing the gate will always be whole gate cell. With 0xF00 meaning the gate will always be
triggered, and 0x000 means that the gate is only triggered with triggered, and 0x000 means that the gate is only triggered with
6% probability. 50% is 0x070. 6% probability. 50% is 0x070.
@ -118,7 +146,7 @@ column type:
- Step gat1-gat6: Like note columns, this will output a 1.0 for the whole - Step gat1-gat6: Like note columns, this will output a 1.0 for the whole
row if a step value is set. With two step values directly row if a step value is set. With two step values directly
following each other no 0.0 will be emitted inbetween following each other no 0.0 will be emitted in between
the rows. This means if you want to drive an envelope the rows. This means if you want to drive an envelope
with release phase with this signal, you need to make with release phase with this signal, you need to make
space for the release phase. space for the release phase.
@ -127,6 +155,12 @@ column type:
- Value gat1-gat6: Outputs a 1.0 value for the duration of the last row. - Value gat1-gat6: Outputs a 1.0 value for the duration of the last row.
You can use this to trigger other things once the You can use this to trigger other things once the
sequence has been played. sequence has been played.
Tip:
If you want to use the end of a tracker sequence as trigger for
something else, eg. switching to a different 'tseq' and restart
it using it's 'trig' input, you will need to use the gate output
of a value column and invert it.
"#; "#;
} }
@ -139,7 +173,8 @@ impl DspNode for TSeq {
fn reset(&mut self) { fn reset(&mut self) {
self.backend = None; self.backend = None;
self.clock.reset(); self.time.clock.reset();
self.time.trigger.reset();
} }
#[inline] #[inline]
@ -148,8 +183,9 @@ impl DspNode for TSeq {
atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf], atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf],
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
{ {
use crate::dsp::{out, inp, at}; use crate::dsp::{out, inp, at, denorm};
let clock = inp::TSeq::clock(inputs); let clock = inp::TSeq::clock(inputs);
let trig = inp::TSeq::trig(inputs);
let cmode = at::TSeq::cmode(atoms); let cmode = at::TSeq::cmode(atoms);
let backend = let backend =
@ -163,13 +199,21 @@ impl DspNode for TSeq {
[0.0; MAX_BLOCK_SIZE]; [0.0; MAX_BLOCK_SIZE];
let cmode = cmode.i(); let cmode = cmode.i();
let plen = backend.pattern_len() as f64; let plen = backend.pattern_len().max(1) as f64;
let time = &mut self.time;
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
if time.trigger.check_trigger(
denorm::TSeq::trig(trig, frame))
{
time.clock.sync();
}
let phase = let phase =
match cmode { match cmode {
0 => self.clock.next_phase(plen, clock.read(frame)) / plen, 0 => time.clock.next_phase(plen, clock.read(frame)) / plen,
1 => self.clock.next_phase(1.0, clock.read(frame)), 1 => time.clock.next_phase(1.0, clock.read(frame)),
2 | _ => (clock.read(frame).abs() as f64).fract(), 2 | _ => (clock.read(frame).abs() as f64).fract(),
}; };