Skip to content

Commit 37b6233

Browse files
committed
Introduce the concept of graphical widgets
1 parent c7a2c80 commit 37b6233

File tree

17 files changed

+599
-4
lines changed

17 files changed

+599
-4
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ members = [
1111
"core",
1212
"interactive",
1313
"keyboard",
14+
"midi",
1415
"midi-file",
1516
"midi-live",
1617
"midi-serial",
17-
"midi",
18+
"midi-udp",
19+
"midi-udp-client",
20+
"midi-udp-widgets-app",
1821
"modules",
1922
"patches",
2023
"player",
2124
"utils",
25+
"widgets",
2226
]

interactive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ caw_modules = { version = "0.4", path = "../modules" }
2828
caw_patches = { version = "0.4", path = "../patches" }
2929
caw_utils = { version = "0.4", path = "../utils" }
3030
caw_audio_file = { version = "0.4", path = "../audio-file" }
31+
caw_widgets = { version = "0.1", path = "../widgets" }
3132
env_logger = "0.11"
3233
rand = "0.8"
3334
wide = "0.7"

interactive/src/window.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl WindowBuilder {
129129
title: self.title.unwrap_or_else(|| "CAW Synthesizer".to_string()),
130130
width_px: self.width_px.unwrap_or(960),
131131
height_px: self.height_px.unwrap_or(720),
132-
stable: self.stable.unwrap_or(false),
132+
stable: self.stable.unwrap_or(true),
133133
stride: self.stride.unwrap_or(1),
134134
scale: self.scale.unwrap_or(1.0),
135135
spread: self.spread.unwrap_or(1),

midi-udp-client/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "caw_midi_udp_client"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
anyhow = "1.0"
8+
midly = "0.5"
9+
10+
[dev-dependencies]
11+
env_logger = "0.11"
12+
clap = { version = "4", features = ["derive"] }
13+
14+
[[example]]
15+
name = "midi_udp_client_controller"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use caw_midi_udp_client::{MidiEvent, MidiMessage, MidiUdpClient};
2+
use clap::Parser;
3+
4+
#[derive(Parser, Debug)]
5+
struct Args {
6+
#[arg(short, long)]
7+
server: String,
8+
#[arg(long, default_value_t = 0)]
9+
channel: u8,
10+
#[arg(short, long, default_value_t = 0)]
11+
controller: u8,
12+
#[arg(short, long)]
13+
value: u8,
14+
}
15+
16+
fn main() {
17+
let args = Args::parse();
18+
let client = MidiUdpClient::new(args.server).unwrap();
19+
let midi_event = MidiEvent {
20+
channel: args.channel.into(),
21+
message: MidiMessage::Controller {
22+
controller: args.controller.into(),
23+
value: args.value.into(),
24+
},
25+
};
26+
client.send(midi_event).unwrap();
27+
}

midi-udp-client/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use midly::live::LiveEvent;
2+
pub use midly::{MidiMessage, num::u4};
3+
use std::net::{Ipv4Addr, ToSocketAddrs, UdpSocket};
4+
5+
pub struct MidiUdpClient {
6+
socket: UdpSocket,
7+
}
8+
9+
pub struct MidiEvent {
10+
pub channel: u4,
11+
pub message: MidiMessage,
12+
}
13+
14+
impl MidiUdpClient {
15+
pub fn new<A: ToSocketAddrs>(addrs: A) -> anyhow::Result<Self> {
16+
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
17+
socket.connect(addrs)?;
18+
Ok(Self { socket })
19+
}
20+
21+
pub fn send(
22+
&self,
23+
MidiEvent { channel, message }: MidiEvent,
24+
) -> anyhow::Result<()> {
25+
let event = LiveEvent::Midi { channel, message };
26+
let mut buf = Vec::new();
27+
match event.write_std(&mut buf) {
28+
Ok(()) => (),
29+
Err(e) => anyhow::bail!("{e}"),
30+
}
31+
self.socket.send(&buf)?;
32+
Ok(())
33+
}
34+
}

midi-udp-widgets-app/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "caw_midi_udp_widgets_app"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
caw_widgets = { version = "0.1", path = "../widgets" }
8+
caw_midi_udp_client = { version = "0.1", path = "../midi-udp-client" }
9+
clap = { version = "4", features = ["derive"] }

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use caw_midi_udp_client::*;
2+
use caw_widgets::Knob;
3+
use clap::{Parser, Subcommand};
4+
5+
#[derive(Subcommand)]
6+
enum Command {
7+
Knob {
8+
#[arg(short, long)]
9+
server: String,
10+
#[arg(long, default_value_t = 0)]
11+
channel: u8,
12+
#[arg(short, long, default_value_t = 0)]
13+
controller: u8,
14+
#[arg(short, long)]
15+
title: Option<String>,
16+
#[arg(short, long, default_value_t = 0.5)]
17+
initial_value: f32,
18+
#[arg(long, default_value_t = 0.2)]
19+
sensitivity: f32,
20+
},
21+
}
22+
23+
#[derive(Parser)]
24+
#[command(name = "caw_midi_udp_widgets_app")]
25+
#[command(
26+
about = "App for launching widgets that communicate with a caw synthesizer by sending midi commands over UDP"
27+
)]
28+
struct Cli {
29+
#[command(subcommand)]
30+
command: Command,
31+
}
32+
33+
fn main() {
34+
match Cli::parse().command {
35+
Command::Knob {
36+
server,
37+
channel,
38+
controller,
39+
title,
40+
initial_value,
41+
sensitivity,
42+
} => {
43+
let client = MidiUdpClient::new(server).unwrap();
44+
let title = title.unwrap_or_else(|| "".to_string());
45+
let mut knob =
46+
Knob::new(title.as_str(), initial_value, sensitivity).unwrap();
47+
loop {
48+
knob.tick().unwrap();
49+
client
50+
.send(MidiEvent {
51+
channel: channel.into(),
52+
message: MidiMessage::Controller {
53+
controller: controller.into(),
54+
value: knob.value_midi(),
55+
},
56+
})
57+
.unwrap();
58+
}
59+
}
60+
}
61+
}

midi-udp/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "caw_midi_udp"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
anyhow = "1.0"
8+
caw_core = { version = "0.5", path = "../core" }
9+
caw_midi = { version = "0.4", path = "../midi" }
10+
midly = "0.5"
11+
log = "0.4"
12+
13+
[dev-dependencies]
14+
env_logger = "0.11"
15+
caw_interactive = { path = "../interactive" }
16+
caw_modules = { path = "../modules" }
17+
18+
[[example]]
19+
name = "midi_udp_controller"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use caw_core::*;
2+
use caw_interactive::{Visualization, window::Window};
3+
use caw_midi::MidiMessagesT;
4+
use caw_midi_udp::MidiLiveUdp;
5+
use caw_modules::*;
6+
7+
fn sig(
8+
pitch_01: Sig<impl SigT<Item = f32>>,
9+
filter_01: Sig<impl SigT<Item = f32>>,
10+
resonance_01: Sig<impl SigT<Item = f32>>,
11+
) -> Sig<impl SigT<Item = f32>> {
12+
super_saw(40.0 + (pitch_01 * 200.))
13+
.build()
14+
.filter(low_pass::default(10_000. * filter_01).q(resonance_01))
15+
.filter(chorus())
16+
.filter(reverb())
17+
}
18+
19+
fn main() -> anyhow::Result<()> {
20+
env_logger::init();
21+
let window = Window::builder()
22+
.visualization(Visualization::StereoOscillographics)
23+
.build();
24+
let midi_live = MidiLiveUdp::new("0.0.0.0:2000")?;
25+
let controllers = midi_live.channel(0).controllers();
26+
window.play_stereo(
27+
Stereo::new_fn(|| {
28+
sig(
29+
controllers.get_01(0),
30+
controllers.get_01(1),
31+
controllers.get_01(2),
32+
)
33+
}),
34+
Default::default(),
35+
)
36+
}

0 commit comments

Comments
 (0)