Skip to content

Commit 45c1d4a

Browse files
committed
Button widget detects space
1 parent ef3c22c commit 45c1d4a

File tree

6 files changed

+124
-27
lines changed

6 files changed

+124
-27
lines changed

caw/examples/live_example.rs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn main() {
2525
let space_button = keyboard.space_button().shared();
2626
let key_events = keyboard.key_events().shared();
2727
let (drum_bits, drum_space) = num_keys_bits_7_with_space("drums").build();
28-
let length = 16;
28+
let length = 32;
2929
let drum_bits_loop = value_looper(drum_bits, clock.clone(), drum_space)
3030
.persist_with_name("drums")
3131
.length(length)
@@ -54,7 +54,7 @@ fn main() {
5454
.shared();
5555
out.set_channel(|channel| {
5656
let voice = key_events.clone().mono_voice();
57-
let (note, gate) = key_looper(voice.triggered_note(), clock.clone())
57+
let (note, gate) = key_looper(voice.gated_note(), clock.clone())
5858
.clearing(space_button.clone())
5959
.length(length)
6060
.persist_with_name("keys")
@@ -64,23 +64,40 @@ fn main() {
6464
let max = semitone_ratio_sig(knob("vib scale").build() * 4.).shared();
6565
let min = (1.0 / max.clone()).shared();
6666
let vib = sine(vib_hz).build().signed_to_range(min, max);
67-
let lfo =
68-
sine(10.0).build().signed_to_01() * knob("lfo").build() * 1000.;
6967
let cutoff_hz =
7068
value_looper(cutoff_hz.clone(), clock.clone(), lpf_space.clone())
7169
.persist_with_name("low_pass")
7270
.length(length)
7371
.build();
74-
let env = adsr(gate)
75-
.key_press_trig(clock.clone())
76-
.a(tempo_s.clone() * knob("attack").build() * 4.)
77-
// .r(tempo_s.clone() * knob("release").build() * 4.)
78-
.s(knob("sustain").build())
79-
.d(tempo_s.clone() * knob("decay").build() * 4.)
80-
.build()
81-
.exp_01(1.)
72+
let env = adsr(
73+
(gate & clock.clone())
74+
.trig_to_gate(tempo_s.clone() * knob("duration").build()),
75+
)
76+
.key_press_trig(clock.clone())
77+
.a(tempo_s.clone() * knob("attack").build() * 4.)
78+
.r(tempo_s.clone() * knob("release").build() * 4.)
79+
.s(knob("sustain").build())
80+
.d(tempo_s.clone() * knob("decay").build() * 4.)
81+
.build()
82+
.exp_01(1.)
83+
.shared();
84+
let (glide_enable, glide_enable_record) =
85+
button_with_space("glide enable").build();
86+
let note_freq = (note.freq_hz() * vib)
87+
.filter_enable(
88+
value_looper(glide_enable, clock.clone(), glide_enable_record)
89+
.persist_with_name("glide")
90+
.length(length)
91+
.build(),
92+
low_pass::butterworth(10. * knob("glide").build()),
93+
)
8294
.shared();
83-
let voice = (saw(note.freq_hz() * vib)
95+
let lfo = sine(knob("lfo rate").build() * note_freq.clone())
96+
.build()
97+
.signed_to_01()
98+
* knob("lfo amp").build()
99+
* 2_000.;
100+
let voice = (saw(note_freq)
84101
.reset_offset_01(channel.circle_phase_offset_01())
85102
.build()
86103
* env.clone())

core/src/sig.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ where
728728
self,
729729
enable: E,
730730
filter: F,
731-
) -> impl SigT<Item = S::Item>
731+
) -> Sig<impl SigT<Item = S::Item>>
732732
where
733733
E: SigT<Item = bool>,
734734
F: Filter<ItemIn = S::Item>,
Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,49 @@
11
use crate::{
22
midi,
33
server::MidiChannelUdp,
4-
widget::{ByTitle, Widget},
4+
widget::{ByTitle, MidiControllerBoolUdp, Widget},
55
};
66
use caw_core::{Sig, SigShared, sig_shared};
77
use caw_keyboard::Note;
88
use caw_midi::{MidiKeyPress, MidiMessagesT};
99
use lazy_static::lazy_static;
1010

11-
pub type Button = MidiKeyPress<MidiChannelUdp>;
11+
pub type ButtonWithSpace = (
12+
Sig<SigShared<MidiKeyPress<MidiChannelUdp>>>,
13+
Sig<SigShared<MidiControllerBoolUdp>>,
14+
);
1215

1316
lazy_static! {
14-
static ref BY_TITLE: ByTitle<Sig<SigShared<Button>>> = Default::default();
17+
static ref BY_TITLE: ByTitle<ButtonWithSpace> = Default::default();
1518
}
1619

17-
fn new_button(title: String) -> Sig<SigShared<Button>> {
20+
fn new_button_with_space(title: String) -> ButtonWithSpace {
1821
let (channel, key) = midi::alloc_key();
22+
let (space_channel, space_controller) = midi::alloc_controller();
1923
let note = Note::from_midi_index(key);
2024
BY_TITLE.get_or_insert(title.as_str(), || {
2125
let widget = Widget::new(
2226
title.clone(),
2327
channel,
2428
"button",
25-
vec![format!("--note={}", note)],
29+
vec![
30+
format!("--note={}", note),
31+
format!("--space-channel={}", space_channel),
32+
format!("--space-controller={}", space_controller),
33+
],
2634
)
2735
.unwrap();
28-
// The choice of note here is arbitrary.
29-
sig_shared(widget.channel().key_press(note))
36+
let controllers = widget.channel().controllers();
37+
let value = sig_shared(widget.channel().key_press(note));
38+
let space = controllers.get_bool(space_controller.into()).shared();
39+
(value, space)
3040
})
3141
}
3242

43+
fn new_button(title: String) -> Sig<SigShared<MidiKeyPress<MidiChannelUdp>>> {
44+
new_button_with_space(title).0
45+
}
46+
3347
mod builder {
3448
use super::*;
3549
use caw_builder_proc_macros::builder;
@@ -40,7 +54,7 @@ mod builder {
4054
#[constructor_doc = "A visual button in a new window"]
4155
#[generic_setter_type_name = "X"]
4256
#[build_fn = "new_button"]
43-
#[build_ty = "Sig<SigShared<Button>>"]
57+
#[build_ty = "Sig<SigShared<MidiKeyPress<MidiChannelUdp>>>"]
4458
pub struct Props {
4559
title: String,
4660
}
@@ -49,6 +63,21 @@ mod builder {
4963
pub fn button(title: impl Into<String>) -> Props {
5064
button_(title.into())
5165
}
66+
67+
builder! {
68+
#[constructor = "button_with_space_"]
69+
#[constructor_doc = "A visual button in a new window"]
70+
#[generic_setter_type_name = "X"]
71+
#[build_fn = "new_button_with_space"]
72+
#[build_ty = "ButtonWithSpace"]
73+
pub struct PropsWithSpace {
74+
title: String,
75+
}
76+
}
77+
78+
pub fn button_with_space(title: impl Into<String>) -> PropsWithSpace {
79+
button_with_space_(title.into())
80+
}
5281
}
5382

54-
pub use builder::button;
83+
pub use builder::{button, button_with_space};

midi-udp-widgets-app-lib/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod button;
2-
pub use button::button;
2+
pub use button::{button, button_with_space};
33

44
pub mod switch;
55
pub use switch::switch;

midi-udp-widgets-app/src/main.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@ use midly::num::u7;
1616
#[derive(Subcommand)]
1717
enum Command {
1818
Button {
19+
/// Buttons are implemented with midi key presses. The note determines which key will
20+
/// represent this button.
1921
#[arg(long)]
2022
note: Note,
23+
#[arg(long)]
24+
space_channel: Option<u8>,
25+
#[arg(long)]
26+
space_controller: Option<u8>,
2127
},
2228
Switch {
29+
/// Switches are implemented with midi key presses. The note determines which key will
30+
/// represent this switch.
2331
#[arg(long)]
2432
note: Note,
2533
},
@@ -92,9 +100,14 @@ fn main() {
92100
let spam_end = Instant::now() + SPAM_DURATION;
93101
let is_spam = || Instant::now() < spam_end;
94102
match cli.command {
95-
Command::Button { note } => {
103+
Command::Button {
104+
note,
105+
space_channel,
106+
space_controller,
107+
} => {
96108
let mut button = Button::new(cli.title.as_deref()).unwrap();
97109
let mut prev_pressed = false;
110+
let mut prev_space = false;
98111
loop {
99112
button.tick().unwrap();
100113
let pressed = button.pressed();
@@ -108,6 +121,27 @@ fn main() {
108121
client.send(MidiEvent { channel, message }).unwrap();
109122
prev_pressed = pressed;
110123
}
124+
let space = button.is_space_pressed();
125+
if let Some(space_controller) = space_controller {
126+
if is_spam() || space != prev_space {
127+
client
128+
.send(MidiEvent {
129+
channel: space_channel
130+
.map(|x| x.into())
131+
.unwrap_or(channel),
132+
message: MidiMessage::Controller {
133+
controller: space_controller.into(),
134+
value: if space {
135+
u7::max_value()
136+
} else {
137+
0.into()
138+
},
139+
},
140+
})
141+
.unwrap();
142+
prev_space = space;
143+
}
144+
}
111145
}
112146
}
113147
Command::Switch { note } => {
@@ -177,7 +211,6 @@ fn main() {
177211
},
178212
})
179213
.unwrap();
180-
181214
prev_space = space;
182215
}
183216
}

widgets/src/button.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::window::{TitlePosition, Window};
22
use anyhow::anyhow;
3-
use sdl2::{pixels::Color, rect::Rect};
3+
use sdl2::{keyboard::Scancode, pixels::Color, rect::Rect};
44
use std::time::Instant;
55

66
const WIDTH_PX: u32 = 128;
@@ -9,6 +9,7 @@ const HEIGHT_PX: u32 = 128;
99
pub struct Button {
1010
window: Window,
1111
pressed: bool,
12+
space_pressed: bool,
1213
}
1314

1415
impl Button {
@@ -17,6 +18,7 @@ impl Button {
1718
Ok(Self {
1819
window,
1920
pressed: false,
21+
space_pressed: false,
2022
})
2123
}
2224

@@ -34,6 +36,18 @@ impl Button {
3436
Event::MouseButtonUp { .. } => {
3537
self.pressed = false;
3638
}
39+
Event::KeyDown {
40+
scancode: Some(Scancode::Space),
41+
..
42+
} => {
43+
self.space_pressed = true;
44+
}
45+
Event::KeyUp {
46+
scancode: Some(Scancode::Space),
47+
..
48+
} => {
49+
self.space_pressed = false;
50+
}
3751
_ => (),
3852
}
3953
}
@@ -88,4 +102,8 @@ impl Button {
88102
pub fn pressed(&self) -> bool {
89103
self.pressed
90104
}
105+
106+
pub fn is_space_pressed(&self) -> bool {
107+
self.space_pressed
108+
}
91109
}

0 commit comments

Comments
 (0)