Skip to content

Commit 3ac844d

Browse files
committed
Work in progress blink visualization
1 parent 6351c5b commit 3ac844d

File tree

4 files changed

+182
-109
lines changed

4 files changed

+182
-109
lines changed

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,33 @@ pub const HANDSHAKE: [u8; 4] = [0x43, 0x41, 0x57, 0x0];
33

44
pub const PROGRAM_NAME: &str = "caw_viz_udp_app";
55

6+
// Based on the max number of bytes that can somewhat reliably be sent over UDP (508) divided by 4
7+
// as we'll be sending f32's. That gives us 127, but since we're actually sending pairs of f32's,
8+
// reduce this to an even number.
9+
pub const MAX_F32_SAMPLES_TO_SEND: usize = 126;
10+
11+
pub fn samples_to_ne_bytes(samples: &[f32], out: &mut Vec<u8>) {
12+
out.clear();
13+
for sample in samples {
14+
out.extend_from_slice(&sample.to_ne_bytes());
15+
}
16+
}
17+
18+
pub fn samples_from_ne_bytes(bytes: &[u8], out: &mut Vec<f32>) {
19+
if bytes.len() % 4 != 0 {
20+
log::error!(
21+
"Received a buffer of bytes that is not a multiple of 4 bytes in length (length is {}).",
22+
bytes.len()
23+
);
24+
}
25+
out.clear();
26+
let mut buf = [0; 4];
27+
for w in bytes.chunks_exact(4) {
28+
buf.copy_from_slice(w);
29+
out.push(f32::from_ne_bytes(buf));
30+
}
31+
}
32+
633
fn wait_for_handshake(socket: &UdpSocket) -> anyhow::Result<SocketAddr> {
734
let mut buf = [0; 8];
835
loop {

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
/// A wrapper for the `caw_viz_udp_app` command. That command starts a udp client and opens a
2-
/// window. The client listens for messages from a server representing vizualizations and renders
3-
/// them in the window. This library provides the server implementation. Synthesizer code is
4-
/// expected to use this library to create a udp server and spawn a client which connects to it,
5-
/// and then manipulate the server to send visualization commands to the client. This library also
6-
/// contains helper code for writing client applications.
1+
//! A wrapper for the `caw_viz_udp_app` command. That command starts a udp client and opens a
2+
//! window. The client listens for messages from a server representing vizualizations and renders
3+
//! them in the window. This library provides the server implementation. Synthesizer code is
4+
//! expected to use this library to create a udp server and spawn a client which connects to it,
5+
//! and then manipulate the server to send visualization commands to the client. This library also
6+
//! contains helper code for writing client applications.
7+
8+
pub mod blink;
79
mod common;
810
pub mod oscilloscope;

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

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
use crate::common::{PROGRAM_NAME, VizUdpClient, VizUdpServer};
2-
1+
use crate::common::{
2+
MAX_F32_SAMPLES_TO_SEND, PROGRAM_NAME, VizUdpClient, VizUdpServer,
3+
samples_from_ne_bytes, samples_to_ne_bytes,
4+
};
35
use std::{
46
net::{SocketAddr, ToSocketAddrs},
57
process::{Child, Command},
68
};
79

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-
1310
pub struct Config {
1411
pub width: u32,
1512
pub height: u32,
@@ -38,28 +35,6 @@ impl Default for Config {
3835
}
3936
}
4037

41-
fn samples_to_ne_bytes(samples: &[f32], out: &mut Vec<u8>) {
42-
out.clear();
43-
for sample in samples {
44-
out.extend_from_slice(&sample.to_ne_bytes());
45-
}
46-
}
47-
48-
fn samples_from_ne_bytes(bytes: &[u8], out: &mut Vec<f32>) {
49-
if bytes.len() % 4 != 0 {
50-
log::error!(
51-
"Received a buffer of bytes that is not a multiple of 4 bytes in length (length is {}).",
52-
bytes.len()
53-
);
54-
}
55-
out.clear();
56-
let mut buf = [0; 4];
57-
for w in bytes.chunks_exact(4) {
58-
buf.copy_from_slice(w);
59-
out.push(f32::from_ne_bytes(buf));
60-
}
61-
}
62-
6338
fn start_client_app(
6439
server_addr: SocketAddr,
6540
program_name: &str,
@@ -93,7 +68,7 @@ fn send_samples(
9368
server: &mut VizUdpServer,
9469
samples: &[f32],
9570
) -> anyhow::Result<()> {
96-
for samples_chunk in samples.chunks(MAX_SAMPLES_TO_SEND) {
71+
for samples_chunk in samples.chunks(MAX_F32_SAMPLES_TO_SEND) {
9772
samples_to_ne_bytes(samples_chunk, &mut server.buf);
9873
if !server.send_buf()? {
9974
break;
@@ -127,7 +102,7 @@ impl Server {
127102
samples: &[f32],
128103
) -> anyhow::Result<()> {
129104
self.buf.extend_from_slice(samples);
130-
if self.buf.len() >= MAX_SAMPLES_TO_SEND {
105+
if self.buf.len() >= MAX_F32_SAMPLES_TO_SEND {
131106
send_samples(&mut self.raw, &self.buf)?;
132107
self.buf.clear();
133108
}

viz-udp-app/src/main.rs

Lines changed: 141 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use anyhow::anyhow;
44
use caw_viz_udp_app_lib::oscilloscope;
55
use clap::{Parser, Subcommand};
66
use line_2d::Coord;
7-
use sdl2::{event::Event, pixels::Color, rect::Rect};
7+
use sdl2::{
8+
EventPump, event::Event, pixels::Color, rect::Rect, render::Canvas,
9+
video::Window,
10+
};
811
use std::collections::VecDeque;
912

1013
#[derive(Parser)]
@@ -29,9 +32,43 @@ struct OscilloscopeCommand {
2932
blue: u8,
3033
}
3134

35+
#[derive(Parser)]
36+
struct BlinkCommand {
37+
#[arg(long, default_value_t = 100)]
38+
width: u32,
39+
#[arg(long, default_value_t = 100)]
40+
height: u32,
41+
#[arg(long, short, default_value_t = 0)]
42+
red: u8,
43+
#[arg(long, short, default_value_t = 255)]
44+
green: u8,
45+
#[arg(long, short, default_value_t = 0)]
46+
blue: u8,
47+
}
48+
3249
#[derive(Subcommand)]
3350
enum Command {
3451
Oscilloscope(OscilloscopeCommand),
52+
Blink(BlinkCommand),
53+
}
54+
55+
impl Command {
56+
fn width(&self) -> u32 {
57+
match self {
58+
Self::Oscilloscope(oscilloscope_command) => {
59+
oscilloscope_command.width
60+
}
61+
Self::Blink(blink_command) => blink_command.width,
62+
}
63+
}
64+
fn height(&self) -> u32 {
65+
match self {
66+
Self::Oscilloscope(oscilloscope_command) => {
67+
oscilloscope_command.height
68+
}
69+
Self::Blink(blink_command) => blink_command.height,
70+
}
71+
}
3572
}
3673

3774
#[derive(Parser)]
@@ -53,17 +90,108 @@ struct ScopeState {
5390
samples: VecDeque<(f32, f32)>,
5491
}
5592

93+
struct App {
94+
server: String,
95+
canvas: Canvas<Window>,
96+
event_pump: EventPump,
97+
}
98+
99+
impl App {
100+
fn run_oscilloscope(
101+
mut self,
102+
mut args: OscilloscopeCommand,
103+
) -> anyhow::Result<()> {
104+
let mut viz_udp_client = oscilloscope::Client::new(self.server)?;
105+
let mut scope_state = ScopeState::default();
106+
loop {
107+
for event in self.event_pump.poll_iter() {
108+
match event {
109+
Event::Quit { .. } => std::process::exit(0),
110+
Event::MouseWheel { y, .. } => {
111+
let ratio = 1.1;
112+
if y > 0 {
113+
args.scale *= ratio;
114+
} else {
115+
args.scale /= ratio;
116+
}
117+
}
118+
Event::MouseMotion {
119+
mousestate, xrel, ..
120+
} => {
121+
if mousestate.left() {
122+
args.line_width = ((args.line_width as i32) + xrel)
123+
.clamp(1, 20)
124+
as u32;
125+
}
126+
if mousestate.right() {
127+
args.alpha_scale = (args.alpha_scale as i32 + xrel)
128+
.clamp(1, 255)
129+
as u8;
130+
}
131+
}
132+
_ => (),
133+
}
134+
}
135+
while let Ok(true) = viz_udp_client.recv_sample() {
136+
for sample_pair in viz_udp_client.pairs() {
137+
scope_state.samples.push_back(sample_pair);
138+
}
139+
while scope_state.samples.len() > args.max_num_samples {
140+
scope_state.samples.pop_front();
141+
}
142+
}
143+
self.canvas.set_draw_color(Color::RGB(0, 0, 0));
144+
self.canvas.clear();
145+
let screen_size = Coord {
146+
x: args.width as i32,
147+
y: args.height as i32,
148+
};
149+
let mut coord_iter =
150+
scope_state.samples.iter().map(|(left, right)| {
151+
Coord {
152+
x: (left * args.scale) as i32,
153+
y: (right * args.scale) as i32,
154+
} + screen_size / 2
155+
});
156+
let mut prev = if let Some(first) = coord_iter.next() {
157+
first
158+
} else {
159+
Coord::new(0, 0)
160+
};
161+
for (i, coord) in coord_iter.enumerate() {
162+
let alpha = ((args.alpha_scale as usize * i)
163+
/ args.max_num_samples)
164+
.min(255) as u8;
165+
self.canvas.set_draw_color(Color::RGBA(
166+
args.red, args.green, args.blue, alpha,
167+
));
168+
for Coord { x, y } in line_2d::coords_between(prev, coord) {
169+
let rect =
170+
Rect::new(x, y, args.line_width, args.line_width);
171+
let _ = self.canvas.fill_rect(rect);
172+
}
173+
prev = coord;
174+
}
175+
self.canvas.present();
176+
}
177+
}
178+
179+
fn run_blink(mut self, mut args: BlinkCommand) -> anyhow::Result<()> {
180+
println!("xxxxxx");
181+
loop {}
182+
}
183+
}
184+
56185
fn main() -> anyhow::Result<()> {
57186
let Cli {
58187
command,
59188
server,
60189
title,
61190
} = Cli::parse();
62-
let Command::Oscilloscope(mut args) = command;
63191
let sdl_context = sdl2::init().map_err(|e| anyhow!(e))?;
64192
let video_subsystem = sdl_context.video().map_err(|e| anyhow!(e))?;
65193
let window = video_subsystem
66-
.window(title.as_str(), args.width, args.height)
194+
.window(title.as_str(), command.width(), command.height())
67195
.always_on_top()
68196
.build()?;
69197
let mut canvas = window
@@ -72,75 +200,16 @@ fn main() -> anyhow::Result<()> {
72200
.present_vsync()
73201
.build()?;
74202
canvas.set_blend_mode(sdl2::render::BlendMode::Blend);
75-
let mut event_pump = sdl_context.event_pump().map_err(|e| anyhow!(e))?;
76-
let mut viz_udp_client = oscilloscope::Client::new(server)?;
77-
let mut scope_state = ScopeState::default();
78-
loop {
79-
for event in event_pump.poll_iter() {
80-
match event {
81-
Event::Quit { .. } => std::process::exit(0),
82-
Event::MouseWheel { y, .. } => {
83-
let ratio = 1.1;
84-
if y > 0 {
85-
args.scale *= ratio;
86-
} else {
87-
args.scale /= ratio;
88-
}
89-
}
90-
Event::MouseMotion {
91-
mousestate, xrel, ..
92-
} => {
93-
if mousestate.left() {
94-
args.line_width = ((args.line_width as i32) + xrel)
95-
.clamp(1, 20)
96-
as u32;
97-
}
98-
if mousestate.right() {
99-
args.alpha_scale = (args.alpha_scale as i32 + xrel)
100-
.clamp(1, 255)
101-
as u8;
102-
}
103-
}
104-
_ => (),
105-
}
106-
}
107-
while let Ok(true) = viz_udp_client.recv_sample() {
108-
for sample_pair in viz_udp_client.pairs() {
109-
scope_state.samples.push_back(sample_pair);
110-
}
111-
while scope_state.samples.len() > args.max_num_samples {
112-
scope_state.samples.pop_front();
113-
}
114-
}
115-
canvas.set_draw_color(Color::RGB(0, 0, 0));
116-
canvas.clear();
117-
let screen_size = Coord {
118-
x: args.width as i32,
119-
y: args.height as i32,
120-
};
121-
let mut coord_iter = scope_state.samples.iter().map(|(left, right)| {
122-
Coord {
123-
x: (left * args.scale) as i32,
124-
y: (right * args.scale) as i32,
125-
} + screen_size / 2
126-
});
127-
let mut prev = if let Some(first) = coord_iter.next() {
128-
first
129-
} else {
130-
Coord::new(0, 0)
131-
};
132-
for (i, coord) in coord_iter.enumerate() {
133-
let alpha = ((args.alpha_scale as usize * i) / args.max_num_samples)
134-
.min(255) as u8;
135-
canvas.set_draw_color(Color::RGBA(
136-
args.red, args.green, args.blue, alpha,
137-
));
138-
for Coord { x, y } in line_2d::coords_between(prev, coord) {
139-
let rect = Rect::new(x, y, args.line_width, args.line_width);
140-
let _ = canvas.fill_rect(rect);
141-
}
142-
prev = coord;
203+
let event_pump = sdl_context.event_pump().map_err(|e| anyhow!(e))?;
204+
let app = App {
205+
server,
206+
event_pump,
207+
canvas,
208+
};
209+
match command {
210+
Command::Oscilloscope(oscilloscope_command) => {
211+
app.run_oscilloscope(oscilloscope_command)
143212
}
144-
canvas.present();
213+
Command::Blink(blink_command) => app.run_blink(blink_command),
145214
}
146215
}

0 commit comments

Comments
 (0)