Skip to content

Commit f4e7927

Browse files
committed
Able to render oscilloscope in separate process
1 parent e435646 commit f4e7927

File tree

10 files changed

+247
-61
lines changed

10 files changed

+247
-61
lines changed

caw/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ web = ["caw_player/web", "caw_keyboard/web", "caw_modules/web", "caw_utils/web"]
1414
player = ["caw_player"]
1515
live = ["caw_live"]
1616
widgets = ["caw_midi_udp_widgets_app_lib"]
17+
viz = ["caw_viz_udp_app_lib"]
1718
midi_live = ["caw_midi", "caw_midi_live"]
1819
midi_file = ["caw_midi", "caw_midi_file"]
1920
midi_serial = ["caw_midi", "caw_midi_serial"]
@@ -37,6 +38,7 @@ caw_audio_file = { version = "0.4", path = "../audio-file", optional = true }
3738
caw_interactive = { version = "0.8", path = "../interactive", optional = true }
3839
caw_live = { version = "0.1", path = "../live", optional = true }
3940
caw_midi_udp_widgets_app_lib = { version = "0.1", path = "../midi-udp-widgets-app-lib", optional = true }
41+
caw_viz_udp_app_lib = { version = "0.1", path = "../viz-udp-app-lib", optional = true }
4042

4143
[dev-dependencies]
4244
anyhow = "1.0"
@@ -67,3 +69,7 @@ required-features = [ "interactive" ]
6769
[[example]]
6870
name = "audio_file_playback"
6971
required-features = [ "interactive", "audio_file" ]
72+
73+
[[example]]
74+
name = "udp_viz_demo"
75+
required-features = [ "viz", "player" ]

caw/examples/udp_viz_demo.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::{thread, time::Duration};
2+
3+
use caw::prelude::*;
4+
5+
fn main() -> anyhow::Result<()> {
6+
let sig = Stereo::new_fn_channel(|channel| {
7+
let freq_hz = match channel {
8+
Channel::Left => 60.,
9+
Channel::Right => 90.5,
10+
};
11+
oscillator(Sine, freq_hz)
12+
.reset_offset_01(channel.circle_phase_offset_01())
13+
.build();
14+
super_saw(40.)
15+
.build()
16+
.filter(low_pass::default(6000.).q(0.5))
17+
.filter(chorus())
18+
.filter(reverb())
19+
});
20+
let player_handle = play_stereo(
21+
sig,
22+
ConfigOwned {
23+
visualization_data_policy: Some(VisualizationDataPolicy::All),
24+
..Default::default()
25+
},
26+
)?;
27+
let viz_data = player_handle.visualization_data();
28+
let mut viz = VizUdpServer::new()?;
29+
loop {
30+
viz_data.with_and_clear(|buf| viz.send_samples(buf))?;
31+
thread::sleep(Duration::from_millis(16));
32+
}
33+
}

caw/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub use caw_live as live;
3333
#[cfg(feature = "caw_midi_udp_widgets_app_lib")]
3434
pub use caw_midi_udp_widgets_app_lib as midi_udp_widgets_app_lib;
3535

36+
#[cfg(feature = "caw_viz_udp_app_lib")]
37+
pub use caw_viz_udp_app_lib as viz_udp_app_lib;
38+
3639
pub mod prelude {
3740
pub use super::builder_proc_macros::*;
3841
pub use super::computer_keyboard::*;
@@ -68,4 +71,7 @@ pub mod prelude {
6871

6972
#[cfg(feature = "caw_midi_udp_widgets_app_lib")]
7073
pub use super::midi_udp_widgets_app_lib::*;
74+
75+
#[cfg(feature = "caw_viz_udp_app_lib")]
76+
pub use super::viz_udp_app_lib::*;
7177
}

player/src/lib.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -540,20 +540,21 @@ impl Clone for PlayerVisualizationData {
540540
}
541541

542542
impl PlayerVisualizationData {
543-
pub fn with<F>(&self, mut f: F)
543+
pub fn with<F, T>(&self, mut f: F) -> T
544544
where
545-
F: FnMut(&[f32]),
545+
F: FnMut(&[f32]) -> T,
546546
{
547-
f(&self.0.read().unwrap());
547+
f(&self.0.read().unwrap())
548548
}
549549

550-
pub fn with_and_clear<F>(&self, mut f: F)
550+
pub fn with_and_clear<F, T>(&self, mut f: F) -> T
551551
where
552-
F: FnMut(&[f32]),
552+
F: FnMut(&[f32]) -> T,
553553
{
554554
let mut visualization_data = self.0.write().unwrap();
555-
f(&visualization_data);
555+
let out = f(&visualization_data);
556556
visualization_data.clear();
557+
out
557558
}
558559
}
559560

@@ -567,18 +568,18 @@ impl PlayerHandle {
567568
pub fn visualization_data(&self) -> PlayerVisualizationData {
568569
self.visualization_data.clone()
569570
}
570-
pub fn with_visualization_data<F>(&self, f: F)
571+
pub fn with_visualization_data<F, T>(&self, f: F) -> T
571572
where
572-
F: FnMut(&[f32]),
573+
F: FnMut(&[f32]) -> T,
573574
{
574-
self.visualization_data.with(f);
575+
self.visualization_data.with(f)
575576
}
576577

577-
pub fn with_visualization_data_and_clear<F>(&self, f: F)
578+
pub fn with_visualization_data_and_clear<F, T>(&self, f: F) -> T
578579
where
579-
F: FnMut(&[f32]),
580+
F: FnMut(&[f32]) -> T,
580581
{
581-
self.visualization_data.with_and_clear(f);
582+
self.visualization_data.with_and_clear(f)
582583
}
583584
}
584585

viz-udp-app-lib/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "viz_udp_app_lib"
2+
name = "caw_viz_udp_app_lib"
33
version = "0.1.0"
44
edition = "2024"
55
description = "Library for launching and interacting with instances of caw_viz_udp_app"
@@ -11,6 +11,7 @@ documentation = "https://docs.rs/viz_udp_app_lib"
1111

1212
[dependencies]
1313
anyhow = "1.0"
14+
log = "0.4"
1415

1516
[[example]]
1617
name = "viz_udp_app_lib_handshake"
Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
1-
use std::{
2-
net::{Ipv4Addr, UdpSocket},
3-
process::Command,
4-
time::Duration,
5-
};
6-
7-
// TODO
8-
const PROGRAM_NAME: &str = "/home/s/src/caw/target/debug/caw_viz_udp_app";
1+
use caw_viz_udp_app_lib::VizUdpServer;
2+
use std::time::Duration;
93

104
fn main() -> anyhow::Result<()> {
11-
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
12-
println!("Our address: {:?}", socket.local_addr()?);
13-
let mut command = Command::new(PROGRAM_NAME);
14-
command.args(["--server".to_string(), socket.local_addr()?.to_string()]);
15-
let _child = command.spawn()?;
16-
let mut buf = [0];
17-
let (size, client_addr) = socket.recv_from(&mut buf)?;
18-
// TODO: expect a magic number here
19-
assert_eq!(size, 1);
20-
assert_eq!(buf, [42]);
21-
println!("Client address: {:?}", client_addr);
22-
socket.connect(client_addr)?;
23-
let mut i = 0;
5+
let mut server = VizUdpServer::new()?;
246
loop {
25-
// TODO: gracefully shut down if the client dissapears
26-
assert_eq!(socket.send(&[i])?, 1);
27-
i = i.wrapping_add(1);
7+
server.send_samples(&[1.0, 1.5, 2.0, 3.0])?;
288
std::thread::sleep(Duration::from_millis(100));
299
}
3010
}

viz-udp-app-lib/src/lib.rs

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,126 @@
1-
pub fn add(left: u64, right: u64) -> u64 {
2-
left + right
1+
use std::{
2+
net::{Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket},
3+
process::{Child, Command},
4+
};
5+
6+
pub const HANDSHAKE: [u8; 4] = [0x43, 0x41, 0x57, 0x0];
7+
8+
// Based on the max number of bytes that can somewhat reliably be sent over UDP (508) divided by 4
9+
// as we'll be sending f32's. That gives us 127, but since we're actually sending pairs of f32's,
10+
// reduce this to an even number.
11+
const MAX_SAMPLES_TO_SEND: usize = 126;
12+
13+
// TODO
14+
const PROGRAM_NAME: &str = "/Users/s/src/caw/target/release/caw_viz_udp_app";
15+
16+
fn wait_for_handshake(socket: &UdpSocket) -> anyhow::Result<SocketAddr> {
17+
let mut buf = [0; 8];
18+
loop {
19+
let (size, client_addr) = socket.recv_from(&mut buf)?;
20+
if size == HANDSHAKE.len() && buf[0..HANDSHAKE.len()] == HANDSHAKE {
21+
return Ok(client_addr);
22+
}
23+
}
24+
}
25+
26+
fn samples_to_ne_bytes(samples: &[f32], out: &mut Vec<u8>) {
27+
out.clear();
28+
for sample in samples {
29+
out.extend_from_slice(&sample.to_ne_bytes());
30+
}
31+
}
32+
33+
fn samples_from_ne_bytes(bytes: &[u8], out: &mut Vec<f32>) {
34+
if bytes.len() % 4 != 0 {
35+
log::error!(
36+
"Received a buffer of bytes that is not a multiple of 4 bytes in length (length is {}).",
37+
bytes.len()
38+
);
39+
}
40+
out.clear();
41+
let mut buf = [0; 4];
42+
for w in bytes.chunks_exact(4) {
43+
buf.copy_from_slice(w);
44+
out.push(f32::from_ne_bytes(buf));
45+
}
46+
}
47+
48+
fn start_client_app(socket: &UdpSocket) -> anyhow::Result<Child> {
49+
let mut command = Command::new(PROGRAM_NAME);
50+
command.args(["--server".to_string(), socket.local_addr()?.to_string()]);
51+
Ok(command.spawn()?)
52+
}
53+
54+
pub struct VizUdpServer {
55+
socket: UdpSocket,
56+
buf: Vec<u8>,
357
}
458

5-
#[cfg(test)]
6-
mod tests {
7-
use super::*;
59+
impl VizUdpServer {
60+
pub fn new() -> anyhow::Result<Self> {
61+
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
62+
log::info!("Viz udp server address: {:?}", socket.local_addr());
63+
let _client_process = start_client_app(&socket)?;
64+
let client_address = wait_for_handshake(&socket)?;
65+
log::info!("Viz udp client address: {:?}", client_address);
66+
socket.connect(client_address)?;
67+
socket.set_nonblocking(true)?;
68+
Ok(Self {
69+
socket,
70+
buf: Vec::new(),
71+
})
72+
}
73+
74+
pub fn send_samples(&mut self, samples: &[f32]) -> anyhow::Result<()> {
75+
for samples_chunk in samples.chunks(MAX_SAMPLES_TO_SEND) {
76+
samples_to_ne_bytes(samples_chunk, &mut self.buf);
77+
// TODO: gracefully handle case when the client dissapears
78+
let bytes_sent = self.socket.send(&self.buf)?;
79+
if bytes_sent != samples.len() {
80+
log::error!(
81+
"Failed to send all samples to viz udp client. Tried to send {}. Actually sent {}.",
82+
samples.len(),
83+
bytes_sent
84+
);
85+
}
86+
}
87+
Ok(())
88+
}
89+
}
90+
91+
pub struct VizUdpClient {
92+
socket: UdpSocket,
93+
buf_raw: Vec<u8>,
94+
buf: Vec<f32>,
95+
}
96+
97+
impl VizUdpClient {
98+
pub fn new<A: ToSocketAddrs>(server_address: A) -> anyhow::Result<Self> {
99+
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
100+
socket.connect(server_address)?;
101+
assert_eq!(socket.send(&HANDSHAKE)?, HANDSHAKE.len());
102+
socket.set_nonblocking(true)?;
103+
Ok(Self {
104+
socket,
105+
buf_raw: vec![0; 32768],
106+
buf: Vec::new(),
107+
})
108+
}
109+
110+
pub fn recv_sample(&mut self) -> anyhow::Result<bool> {
111+
match self.socket.recv(&mut self.buf_raw) {
112+
Ok(size) => {
113+
samples_from_ne_bytes(&self.buf_raw[0..size], &mut self.buf);
114+
Ok(true)
115+
}
116+
Err(e) => match e.kind() {
117+
std::io::ErrorKind::WouldBlock => Ok(false),
118+
_ => anyhow::bail!(e),
119+
},
120+
}
121+
}
8122

9-
#[test]
10-
fn it_works() {
11-
let result = add(2, 2);
12-
assert_eq!(result, 4);
123+
pub fn pairs(&self) -> impl Iterator<Item = (f32, f32)> {
124+
self.buf.chunks_exact(2).map(|c| (c[0], c[1]))
13125
}
14126
}

viz-udp-app/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ anyhow = "1.0"
1414
sdl2 = { version = "0.37" }
1515
line_2d = "0.5"
1616
clap = { version = "4", features = ["derive"] }
17+
caw_viz_udp_app_lib = { version = "0.1", path = "../viz-udp-app-lib" }

0 commit comments

Comments
 (0)