added trigger input
This commit is contained in:
parent
de15c3cfd7
commit
9397b978dc
3 changed files with 62 additions and 12 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue