Skip to content

Commit 0e1aff6

Browse files
committed
Working looper module
1 parent f26ef12 commit 0e1aff6

File tree

13 files changed

+388
-69
lines changed

13 files changed

+388
-69
lines changed

caw/examples/live_example.rs

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,42 @@ fn main() {
77
..Default::default()
88
})
99
.with_volume(volume.clone());
10-
volume.set(knob("volume").initial_value_01(0.2).build());
11-
let tempo_s = sv(knob("tempo s").build() * 2.);
12-
let clock = sv(periodic_trig_s(tempo_s.clone()).build());
13-
let env_ = sv({
14-
let mut scope = blink::Server::new("env", Default::default()).unwrap();
15-
adsr(clock.clone().trig_to_gate(tempo_s.clone() / 2.))
16-
.a(tempo_s.clone() / 8.)
17-
.r(tempo_s.clone() / 2.)
18-
.build()
19-
.exp_01(1.)
20-
.with_buf(move |buf| {
21-
let _ = scope.send_samples(buf);
22-
})
23-
});
10+
volume.set(knob("volume").initial_value_01(0.5).build());
11+
let tempo_s = sv(knob("tempo s").build() * 0.5);
12+
let lpf = sv(knob("lpf").build() * 0.5);
13+
let clock = sv(periodic_trig_s(tempo_s.clone())
14+
.build()
15+
.viz_blink("clock".to_string(), Default::default()));
2416
let keys = computer_keyboard("keys")
2517
.start_note(note::B_1)
2618
.build()
2719
.shared();
2820
let space_button =
2921
keys.clone().controllers().get_01(0).is_positive().shared();
3022
let key_events = keys.clone().key_events().shared();
31-
out.set(|| {
23+
out.set_channel(|channel| {
3224
let voice = key_events.clone().mono_voice();
33-
let (note, gate) = sequence_looper(
25+
let (note, gate) = key_looper(
3426
voice.gated_note(),
3527
clock.clone(),
3628
space_button.clone(),
3729
8,
3830
)
3931
.ungated();
4032
let env = adsr(gate)
33+
.key_press_trig(clock.clone())
4134
.a(tempo_s.clone() / 24.)
4235
.r(tempo_s.clone() / 2.)
43-
.s(0.75)
44-
.d(tempo_s.clone() / 24.)
36+
.s(0.25)
37+
.d(tempo_s.clone() / 2.)
4538
.build()
46-
.exp_01(1.);
47-
super_saw(note.freq_hz())
48-
.build()
49-
.filter(
50-
low_pass::default(10. + env_.clone() * 0. + (env * 15000.))
51-
.q(0.2),
52-
)
39+
.exp_01(1.)
40+
.shared();
41+
let voice = (super_saw(note.freq_hz()).build() * env.clone()).filter(
42+
low_pass::default(10. + (env.clone() * lpf.clone() * 15000.))
43+
.q(0.2),
44+
);
45+
voice
5346
.filter(
5447
chorus()
5548
.lfo_rate_hz(0.1)
@@ -59,9 +52,6 @@ fn main() {
5952
.feedback_ratio(0.5),
6053
)
6154
.filter(reverb::default().room_size(0.9).damping(0.1))
62-
63-
// .filter(reverb())
64-
// .filter(chorus())
6555
});
6656
thread::park();
6757
}

core/src/sig.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,16 @@ where
666666
x
667667
})
668668
}
669+
670+
/// Force the evaluation of some other signal when this signal is evaluated but ignore the
671+
/// result. Use this when computing a signal has side effects (e.g. rendering a visualization)
672+
/// but the effectful signal's value is unnecessary.
673+
pub fn force<O>(self, other: O) -> Sig<impl SigT<Item = S::Item>>
674+
where
675+
O: SigT,
676+
{
677+
self.zip(other).map(|(x, _)| x)
678+
}
669679
}
670680

671681
impl<S, A, B> Sig<S>

core/src/stereo.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt::Display;
2+
13
use crate::{Buf, Sig, SigCtx, SigSampleIntoBufT, SigT, sig_ops::sig_add};
24

35
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -6,6 +8,15 @@ pub enum Channel {
68
Right,
79
}
810

11+
impl Display for Channel {
12+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13+
match self {
14+
Self::Left => write!(f, "left"),
15+
Self::Right => write!(f, "right"),
16+
}
17+
}
18+
}
19+
920
impl Channel {
1021
// Apply a 90 degree offset to the right channel so a stereo sine wave would draw a circle with
1122
// stereo oscillographics.

keyboard/src/a440_12tet.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,39 +120,39 @@ impl NoteName {
120120
pub const B: Self = Self::from_index(11);
121121

122122
/// Returns a str representation of the note name where all accidentals are sharp, formatted
123-
/// like "C" or "C_sharp"
123+
/// like "C" or "C#"
124124
pub const fn to_str_sharp(self) -> &'static str {
125125
match self.relative_midi_index {
126126
0 => "C",
127-
1 => "C_sharp",
127+
1 => "C#",
128128
2 => "D",
129-
3 => "D_sharp",
129+
3 => "D#",
130130
4 => "E",
131131
5 => "F",
132-
6 => "F_sharp",
132+
6 => "F#",
133133
7 => "G",
134-
8 => "G_sharp",
134+
8 => "G#",
135135
9 => "A",
136-
10 => "A_sharp",
136+
10 => "A#",
137137
11 => "B",
138138
_ => unreachable!(),
139139
}
140140
}
141141

142-
/// Parses a str like "C" or "C_sharp"
142+
/// Parses a str like "C" or "C#"
143143
pub fn from_str_sharp(s: &str) -> Option<Self> {
144144
let relative_midi_index = match s {
145145
"C" => 0,
146-
"C_sharp" => 1,
146+
"C#" => 1,
147147
"D" => 2,
148-
"D_sharp" => 3,
148+
"D#" => 3,
149149
"E" => 4,
150150
"F" => 5,
151-
"F_sharp" => 6,
151+
"F#" => 6,
152152
"G" => 7,
153-
"G_sharp" => 8,
153+
"G#" => 8,
154154
"A" => 9,
155-
"A_sharp" => 10,
155+
"A#" => 10,
156156
"B" => 11,
157157
_ => return None,
158158
};
@@ -287,7 +287,7 @@ impl Note {
287287
}
288288
}
289289

290-
/// Example formats: "C_sharp:4", "C:4". Notes in octave "-1" are written like "C_sharp:-1" or
290+
/// Example formats: "C#:4", "C:4". Notes in octave "-1" are written like "C#:-1" or
291291
/// "C:-1".
292292
impl Display for Note {
293293
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -300,7 +300,7 @@ impl Display for Note {
300300
}
301301
}
302302

303-
/// Expected format: "C_sharp-4", "C-4"
303+
/// Expected format: "C#-4", "C-4"
304304
impl FromStr for Note {
305305
type Err = String;
306306

utils/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pub mod noise;
44
pub mod sequencer;
55
pub use sequencer::value_sequencer;
66
pub mod looper;
7-
pub use looper::sequence_looper;
7+
pub use looper::{key_looper, value_looper};

utils/src/looper.rs

Lines changed: 139 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,188 @@
11
use caw_core::{Buf, Sig, SigCtx, SigT};
22
use itertools::izip;
33

4-
struct SequenceLooper<X, S, T, R, N>
4+
#[derive(Default)]
5+
struct Sequence<T> {
6+
sequence: Vec<T>,
7+
index: usize,
8+
}
9+
10+
impl<T> Sequence<T> {
11+
fn tick(&mut self) {
12+
assert!(self.sequence.len() > 0);
13+
self.index = (self.index + 1) % self.sequence.len();
14+
}
15+
16+
fn current(&self) -> &T {
17+
&self.sequence[self.index]
18+
}
19+
20+
fn current_mut(&mut self) -> &mut T {
21+
&mut self.sequence[self.index]
22+
}
23+
24+
fn resize_with<F: FnMut() -> T>(&mut self, length: usize, f: F) {
25+
if length != self.sequence.len() {
26+
let length = length.max(1);
27+
self.sequence.resize_with(length, f);
28+
if self.index >= length {
29+
self.index = 0;
30+
}
31+
}
32+
}
33+
34+
fn new_with<F: FnMut() -> T>(length: usize, f: F) -> Self {
35+
let length = length.max(1);
36+
let mut sequence = Vec::new();
37+
sequence.resize_with(length, f);
38+
Self { sequence, index: 0 }
39+
}
40+
}
41+
42+
struct KeyLooper<X, S, T, C, N>
543
where
644
X: Clone,
745
S: SigT<Item = Option<X>>,
846
T: SigT<Item = bool>,
9-
R: SigT<Item = bool>,
47+
C: SigT<Item = bool>,
1048
N: SigT<Item = u32>,
1149
{
1250
sig: S,
51+
last_value: S::Item,
1352
tick: T,
14-
recording: R,
53+
clearing: C,
1554
length: N,
16-
sequence: Vec<S::Item>,
55+
sequence: Sequence<S::Item>,
1756
buf: Vec<S::Item>,
1857
}
1958

20-
impl<X, S, T, R, N> SigT for SequenceLooper<X, S, T, R, N>
59+
impl<X, S, T, C, N> SigT for KeyLooper<X, S, T, C, N>
2160
where
2261
X: Clone,
2362
S: SigT<Item = Option<X>>,
2463
T: SigT<Item = bool>,
25-
R: SigT<Item = bool>,
64+
C: SigT<Item = bool>,
2665
N: SigT<Item = u32>,
2766
{
2867
type Item = S::Item;
2968

3069
fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
70+
self.buf.clear();
3171
self.buf.resize_with(ctx.num_samples, || None);
3272
let sig = self.sig.sample(ctx);
3373
let tick = self.tick.sample(ctx);
34-
let recording = self.recording.sample(ctx);
74+
let clearing = self.clearing.sample(ctx);
3575
let length = self.length.sample(ctx);
36-
for (out, sample, tick, recording, length) in izip! {
76+
for (out, sample, tick, clearing, length) in izip! {
3777
self.buf.iter_mut(),
3878
sig.iter(),
3979
tick.iter(),
40-
recording.iter(),
80+
clearing.iter(),
4181
length.iter(),
4282
} {
43-
*out = sample;
83+
self.sequence.resize_with(length as usize, || None);
84+
if tick {
85+
self.sequence.tick();
86+
self.last_value = None;
87+
} else if sample.is_some() {
88+
self.last_value = sample.clone();
89+
}
90+
if clearing {
91+
*self.sequence.current_mut() = None;
92+
} else if self.last_value.is_some() {
93+
*self.sequence.current_mut() = self.last_value.clone();
94+
}
95+
*out = self.sequence.current().clone();
4496
}
4597
&self.buf
4698
}
4799
}
48100

49-
pub fn sequence_looper<X: Clone>(
101+
pub fn key_looper<X: Clone>(
50102
sig: impl SigT<Item = Option<X>>,
51103
tick: impl SigT<Item = bool>,
52-
recording: impl SigT<Item = bool>,
104+
clearing: impl SigT<Item = bool>,
53105
length: impl SigT<Item = u32>,
54106
) -> Sig<impl SigT<Item = Option<X>>> {
55-
Sig(SequenceLooper {
107+
Sig(KeyLooper {
108+
sig,
109+
last_value: None,
110+
tick,
111+
clearing,
112+
length,
113+
sequence: Sequence::new_with(1, || None),
114+
buf: Vec::new(),
115+
})
116+
}
117+
118+
struct ValueLooper<S, T, R, N>
119+
where
120+
S: SigT,
121+
S::Item: Clone,
122+
T: SigT<Item = bool>,
123+
R: SigT<Item = bool>,
124+
N: SigT<Item = u32>,
125+
{
126+
sig: S,
127+
tick: T,
128+
recording: R,
129+
length: N,
130+
sequence: Sequence<Option<S::Item>>,
131+
buf: Vec<S::Item>,
132+
}
133+
134+
impl<S, T, R, N> SigT for ValueLooper<S, T, R, N>
135+
where
136+
S: SigT,
137+
S::Item: Clone,
138+
T: SigT<Item = bool>,
139+
R: SigT<Item = bool>,
140+
N: SigT<Item = u32>,
141+
{
142+
type Item = S::Item;
143+
144+
fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
145+
self.buf.clear();
146+
let sig = self.sig.sample(ctx);
147+
let tick = self.tick.sample(ctx);
148+
let recording = self.recording.sample(ctx);
149+
let length = self.length.sample(ctx);
150+
for (sample, tick, recording, length) in izip! {
151+
sig.iter(),
152+
tick.iter(),
153+
recording.iter(),
154+
length.iter(),
155+
} {
156+
self.sequence.resize_with(length as usize, || None);
157+
if tick {
158+
self.sequence.tick();
159+
}
160+
let stored = self.sequence.current_mut();
161+
let out = match (recording, stored.clone()) {
162+
(true, _) | (_, None) => {
163+
*stored = Some(sample.clone());
164+
sample
165+
}
166+
(_, Some(stored)) => stored,
167+
};
168+
self.buf.push(out);
169+
}
170+
&self.buf
171+
}
172+
}
173+
174+
pub fn value_looper<T: Clone>(
175+
sig: impl SigT<Item = T>,
176+
tick: impl SigT<Item = bool>,
177+
recording: impl SigT<Item = bool>,
178+
length: impl SigT<Item = u32>,
179+
) -> Sig<impl SigT<Item = T>> {
180+
Sig(ValueLooper {
56181
sig,
57182
tick,
58183
recording,
59184
length,
60-
sequence: Vec::new(),
185+
sequence: Sequence::new_with(1, || None),
61186
buf: Vec::new(),
62187
})
63188
}

0 commit comments

Comments
 (0)