Skip to content

Commit 2d267db

Browse files
committed
Allow multiple channels for widgets
1 parent 74ab10f commit 2d267db

File tree

8 files changed

+254
-81
lines changed

8 files changed

+254
-81
lines changed

caw/examples/live_example.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ fn main() {
2121
let _ = scope.send_samples(buf);
2222
})
2323
});
24-
let key_events = computer_keyboard("keys").build().key_events().shared();
24+
let keys = computer_keyboard("keys").build().shared();
25+
let space_button = keys.clone().controllers().get_01(0).is_positive();
26+
let key_events = keys.clone().key_events().shared();
27+
let env2 = adsr(space_button).build().shared();
2528
out.set(|| {
2629
let voice = key_events.clone().mono_voice();
2730
super_saw(voice.note.freq_hz())
2831
.build()
29-
.filter(low_pass::default(100. + env.clone() * 4000.).q(0.5))
32+
.filter(
33+
low_pass::default(
34+
100. + env.clone() * 4000. + (env2.clone() * 5000.),
35+
)
36+
.q(0.5),
37+
)
3038
.filter(reverb())
3139
});
3240
thread::park();

core/src/sig.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,14 @@ where
788788
pub fn abs(self) -> Sig<SigAbs<S>> {
789789
Sig(SigAbs(self.0))
790790
}
791+
792+
pub fn is_positive(self) -> Sig<impl SigT<Item = bool>> {
793+
self.map(|x| x > 0.0)
794+
}
795+
796+
pub fn is_negative(self) -> Sig<impl SigT<Item = bool>> {
797+
self.map(|x| x < 0.0)
798+
}
791799
}
792800

793801
pub struct GateToTrigRisingEdge<S>

midi-udp-widgets-app-lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ caw_builder_proc_macros = { version = "0.1", path = "../builder-proc-macros" }
1717
caw_keyboard = { version = "0.4", path = "../keyboard" }
1818
lazy_static = "1.5"
1919
anyhow = "1.0"
20+
midly = "0.5"
2021

2122
[dev-dependencies]
2223
env_logger = "0.11"

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

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,73 @@
11
use caw_core::{Buf, Sig, SigShared, SigT, Zip, sig_shared};
22
use caw_keyboard::Note;
3-
use caw_midi::{MidiController01, MidiKeyPress, MidiMessages, MidiMessagesT};
4-
use caw_midi_udp::{MidiLiveUdp, MidiLiveUdpChannel};
3+
use caw_midi::{
4+
MidiChannel, MidiController01, MidiEventsT, MidiKeyPress, MidiMessages,
5+
MidiMessagesT,
6+
};
7+
use caw_midi_udp::MidiLiveUdp;
58
use lazy_static::lazy_static;
9+
use midly::num::{u4, u7};
610
use std::{
711
collections::HashMap,
812
net::SocketAddr,
913
process::{Child, Command},
1014
sync::Mutex,
1115
};
1216

13-
// For now this library only supports midi channel 0
14-
const CHANNEL: u8 = 0;
17+
struct ChannelAllocator {
18+
next_channel: Option<u4>,
19+
next_controller_index_by_channel: HashMap<u4, Option<u7>>,
20+
}
21+
22+
impl ChannelAllocator {
23+
fn new() -> Self {
24+
Self {
25+
next_channel: Some(0.into()),
26+
next_controller_index_by_channel: HashMap::new(),
27+
}
28+
}
29+
30+
fn alloc_channel(&mut self) -> u4 {
31+
let channel = self.next_channel.expect("Can't allocate MIDI channel because all MIDI channels are in use already.");
32+
self.next_channel = if channel == u4::max_value() {
33+
None
34+
} else {
35+
Some((u8::from(channel) + 1).into())
36+
};
37+
channel
38+
}
39+
40+
fn alloc_controller(&mut self, channel: u4) -> u7 {
41+
if let Some(next_channel) = self.next_channel {
42+
assert!(
43+
channel < next_channel,
44+
"Attempted to allocate controller on unallocated channel."
45+
);
46+
}
47+
let next_controller = self
48+
.next_controller_index_by_channel
49+
.entry(channel)
50+
.or_insert_with(|| Some(0.into()));
51+
if let Some(controller) = *next_controller {
52+
*next_controller = if controller == u7::max_value() {
53+
None
54+
} else {
55+
Some((u8::from(controller) + 1).into())
56+
};
57+
controller
58+
} else {
59+
panic!(
60+
"Can't allocate MIDI controller because all MIDI controllers on channel {} are in use already.",
61+
channel
62+
);
63+
}
64+
}
65+
}
1566

1667
lazy_static! {
1768
// A stream of MIDI events received by the UDP/IP server. It owns the UDP/IP server.
18-
static ref SIG: Sig<SigShared<MidiLiveUdpChannel>> =
19-
sig_shared(MidiLiveUdp::new_unspecified().unwrap().channel(CHANNEL).0);
69+
static ref SIG: Sig<SigShared<MidiLiveUdp>> =
70+
sig_shared(MidiLiveUdp::new_unspecified().unwrap());
2071

2172
// Used to prevent multiple windows from opening at the same time with the same name. Note that
2273
// when using with evcxr this seems to get cleared when a cell is recomputed, but this is still
@@ -32,36 +83,29 @@ lazy_static! {
3283
static ref COMPUTER_KEYBOARDS_BY_TITLE: Mutex<HashMap<String, Sig<SigShared<ComputerKeyboard>>>> =
3384
Mutex::new(HashMap::new());
3485

35-
/// MIDI controller indices are allocated dynamically and this global tracks the value of the
36-
/// next controller index to allocate.
37-
static ref NEXT_CONTROLLER_INDEX: Mutex<u8> = Mutex::new(0);
86+
static ref CHANNEL_ALLOCATOR: Mutex<ChannelAllocator> = Mutex::new(ChannelAllocator::new());
3887
}
3988

4089
/// Returns the IP address of the server that will receive MIDI events.
4190
fn sig_server_local_socket_address() -> SocketAddr {
42-
SIG.0.with_inner(|midi_live_udp_channel| {
43-
midi_live_udp_channel.server.local_socket_address().unwrap()
91+
SIG.0.with_inner(|midi_live_udp| {
92+
midi_live_udp.local_socket_address().unwrap()
4493
})
4594
}
4695

47-
/// Allocates and returns unique MIDI controller value.
48-
fn alloc_controller() -> u8 {
49-
let mut next_controller_index = NEXT_CONTROLLER_INDEX.lock().unwrap();
50-
let controller_index = *next_controller_index;
51-
*next_controller_index += 1;
52-
controller_index
53-
}
54-
5596
const PROGRAM_NAME: &str = "caw_midi_udp_widgets_app";
5697

98+
type MidiChannelUdp = MidiChannel<SigShared<MidiLiveUdp>>;
99+
57100
struct Widget {
58101
title: String,
59102
process: Option<Child>,
60103
}
61104

62105
pub struct Button {
63106
widget: Widget,
64-
sig: Sig<MidiKeyPress<SigShared<MidiLiveUdpChannel>>>,
107+
sig: Sig<MidiKeyPress<MidiChannelUdp>>,
108+
channel_index: u4,
65109
}
66110

67111
impl Button {
@@ -70,14 +114,18 @@ impl Button {
70114
if let Some(button) = buttons_by_title.get(&title) {
71115
button.clone()
72116
} else {
117+
let channel_index =
118+
CHANNEL_ALLOCATOR.lock().unwrap().alloc_channel();
119+
let channel = SIG.clone().channel(channel_index.into());
73120
// The choice of note here is arbitrary.
74-
let sig = Sig(SIG.clone().key_press(Note::default()));
121+
let sig = Sig(channel.key_press(Note::default()));
75122
let mut s = Self {
76123
widget: Widget {
77124
title: title.clone(),
78125
process: None,
79126
},
80127
sig,
128+
channel_index,
81129
};
82130
let child = match s.command().spawn() {
83131
Ok(child) => child,
@@ -100,7 +148,7 @@ impl Button {
100148
"--server={}",
101149
sig_server_local_socket_address().to_string()
102150
),
103-
format!("--channel={}", CHANNEL),
151+
format!("--channel={}", self.channel_index),
104152
format!("--title={}", self.widget.title.clone()),
105153
"button".to_string(),
106154
];
@@ -145,7 +193,8 @@ pub struct Knob {
145193
controller: u8,
146194
initial_value_01: f32,
147195
sensitivity: f32,
148-
sig: Sig<MidiController01<SigShared<MidiLiveUdpChannel>>>,
196+
sig: Sig<MidiController01<MidiChannelUdp>>,
197+
channel_index: u4,
149198
}
150199

151200
impl Knob {
@@ -158,9 +207,11 @@ impl Knob {
158207
if let Some(knob) = knobs_by_title.get(&title) {
159208
knob.clone()
160209
} else {
161-
let controller = alloc_controller();
162-
let sig = SIG
163-
.clone()
210+
let mut allocator = CHANNEL_ALLOCATOR.lock().unwrap();
211+
let channel_index = allocator.alloc_channel();
212+
let controller = allocator.alloc_controller(channel_index).into();
213+
let channel = SIG.clone().channel(channel_index.into());
214+
let sig = channel
164215
.controllers()
165216
.get_with_initial_value_01(controller, initial_value_01);
166217
let mut s = Self {
@@ -172,6 +223,7 @@ impl Knob {
172223
initial_value_01,
173224
sensitivity,
174225
sig,
226+
channel_index,
175227
};
176228
let child = match s.command().spawn() {
177229
Ok(child) => child,
@@ -194,7 +246,7 @@ impl Knob {
194246
"--server={}",
195247
sig_server_local_socket_address().to_string()
196248
),
197-
format!("--channel={}", CHANNEL),
249+
format!("--channel={}", self.channel_index),
198250
format!("--title={}", self.widget.title.clone()),
199251
"knob".to_string(),
200252
format!("--controller={}", self.controller),
@@ -247,10 +299,11 @@ pub struct Xy {
247299
controller_y: u8,
248300
sig: Sig<
249301
Zip<
250-
MidiController01<SigShared<MidiLiveUdpChannel>>,
251-
MidiController01<SigShared<MidiLiveUdpChannel>>,
302+
MidiController01<SigShared<MidiChannelUdp>>,
303+
MidiController01<SigShared<MidiChannelUdp>>,
252304
>,
253305
>,
306+
channel_index: u4,
254307
}
255308

256309
impl Xy {
@@ -259,13 +312,16 @@ impl Xy {
259312
if let Some(xy) = xys_by_title.get(&title) {
260313
xy.clone()
261314
} else {
262-
let controller_x = alloc_controller();
263-
let controller_y = alloc_controller();
264-
let sig_x = SIG
315+
let mut allocator = CHANNEL_ALLOCATOR.lock().unwrap();
316+
let channel_index = allocator.alloc_channel();
317+
let controller_x = allocator.alloc_controller(channel_index).into();
318+
let controller_y = allocator.alloc_controller(channel_index).into();
319+
let channel = SIG.clone().channel(channel_index.into()).shared();
320+
let sig_x = channel
265321
.clone()
266322
.controllers()
267323
.get_with_initial_value_01(controller_x, 0.5);
268-
let sig_y = SIG
324+
let sig_y = channel
269325
.clone()
270326
.controllers()
271327
.get_with_initial_value_01(controller_y, 0.5);
@@ -278,6 +334,7 @@ impl Xy {
278334
controller_x,
279335
controller_y,
280336
sig,
337+
channel_index,
281338
};
282339
let child = match s.command().spawn() {
283340
Ok(child) => child,
@@ -300,7 +357,7 @@ impl Xy {
300357
"--server={}",
301358
sig_server_local_socket_address().to_string()
302359
),
303-
format!("--channel={}", CHANNEL),
360+
format!("--channel={}", self.channel_index),
304361
format!("--title={}", self.widget.title.clone()),
305362
"xy".to_string(),
306363
format!("--controller-x={}", self.controller_x),
@@ -345,7 +402,8 @@ pub use xy_builder::xy;
345402
pub struct ComputerKeyboard {
346403
widget: Widget,
347404
start_note: Note,
348-
sig: Sig<SigShared<MidiLiveUdpChannel>>,
405+
sig: Sig<MidiChannelUdp>,
406+
channel_index: u4,
349407
}
350408

351409
impl ComputerKeyboard {
@@ -356,14 +414,17 @@ impl ComputerKeyboard {
356414
{
357415
computer_keyboard.clone()
358416
} else {
359-
let sig = SIG.clone();
417+
let mut allocator = CHANNEL_ALLOCATOR.lock().unwrap();
418+
let channel_index = allocator.alloc_channel();
419+
let sig = SIG.clone().channel(channel_index.into());
360420
let mut s = Self {
361421
widget: Widget {
362422
title: title.clone(),
363423
process: None,
364424
},
365425
start_note,
366426
sig,
427+
channel_index,
367428
};
368429
let child = match s.command().spawn() {
369430
Ok(child) => child,
@@ -386,7 +447,7 @@ impl ComputerKeyboard {
386447
"--server={}",
387448
sig_server_local_socket_address().to_string()
388449
),
389-
format!("--channel={}", CHANNEL),
450+
format!("--channel={}", self.channel_index),
390451
format!("--title={}", self.widget.title.clone()),
391452
"computer-keyboard".to_string(),
392453
format!("--start-note={}", self.start_note),

midi-udp/examples/midi_udp_controller.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use caw_core::*;
22
use caw_interactive::{Visualization, window::Window};
3-
use caw_midi::MidiMessagesT;
3+
use caw_midi::{MidiEventsT, MidiMessagesT};
44
use caw_midi_udp::MidiLiveUdp;
55
use caw_modules::*;
66

@@ -22,7 +22,7 @@ fn main() -> anyhow::Result<()> {
2222
.visualization(Visualization::StereoOscillographics)
2323
.build();
2424
let midi_live = MidiLiveUdp::new("0.0.0.0:2000")?;
25-
let controllers = midi_live.channel(0).controllers();
25+
let controllers = Sig(midi_live).channel(0.into()).controllers();
2626
window.play_stereo(
2727
Stereo::new_fn(|| {
2828
sig(

0 commit comments

Comments
 (0)