alpha
Login
or
Join now
gwen.works
/
shapemaker
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
🚧 Work on vst beaconing
author
Gwenn Le Bihan
date
1 year ago
(Mar 28, 2025, 4:54 PM +0100)
commit
c85a89ec
c85a89ec8eb5948d314d54731bc948f708e49310
parent
73d2924c
73d2924c58b553bff80558b0a00025e69142211d
+476
-124
9 changed files
Expand all
Collapse all
Unified
Split
Cargo.lock
Cargo.toml
src
cli
mod.rs
main.rs
vst
beacon.rs
mod.rs
probe.rs
remote_probe.rs
vst.rs
+42
Cargo.lock
Reviewed
···
958
958
]
959
959
960
960
[[package]]
961
961
+
name = "data-encoding"
962
962
+
version = "2.8.0"
963
963
+
source = "registry+https://github.com/rust-lang/crates.io-index"
964
964
+
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
965
965
+
966
966
+
[[package]]
961
967
name = "data-url"
962
968
version = "0.3.1"
963
969
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2454
2460
]
2455
2461
2456
2462
[[package]]
2463
2463
+
name = "http"
2464
2464
+
version = "1.3.1"
2465
2465
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2466
2466
+
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
2467
2467
+
dependencies = [
2468
2468
+
"bytes 1.10.1",
2469
2469
+
"fnv",
2470
2470
+
"itoa",
2471
2471
+
]
2472
2472
+
2473
2473
+
[[package]]
2457
2474
name = "http-auth"
2458
2475
version = "0.1.10"
2459
2476
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4455
4472
"tiny-skia",
4456
4473
"tokio",
4457
4474
"toml 0.8.20",
4475
4475
+
"tungstenite",
4476
4476
+
"url",
4458
4477
"video-rs",
4459
4478
"wasm-bindgen",
4460
4479
"watchexec",
···
5080
5099
]
5081
5100
5082
5101
[[package]]
5102
5102
+
name = "tungstenite"
5103
5103
+
version = "0.26.2"
5104
5104
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5105
5105
+
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
5106
5106
+
dependencies = [
5107
5107
+
"bytes 1.10.1",
5108
5108
+
"data-encoding",
5109
5109
+
"http",
5110
5110
+
"httparse",
5111
5111
+
"log",
5112
5112
+
"rand 0.9.0",
5113
5113
+
"sha1",
5114
5114
+
"thiserror 2.0.12",
5115
5115
+
"utf-8",
5116
5116
+
]
5117
5117
+
5118
5118
+
[[package]]
5083
5119
name = "typeid"
5084
5120
version = "1.0.3"
5085
5121
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5209
5245
"unicode-vo",
5210
5246
"xmlwriter",
5211
5247
]
5248
5248
+
5249
5249
+
[[package]]
5250
5250
+
name = "utf-8"
5251
5251
+
version = "0.7.6"
5252
5252
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5253
5253
+
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
5212
5254
5213
5255
[[package]]
5214
5256
name = "utf16_iter"
+2
Cargo.toml
Reviewed
···
91
91
tokio = { version = "1.44.1", optional = true }
92
92
watchexec-events = { version = "5.0.0", optional = true }
93
93
serde = { version = "1.0.219", features = ["derive"] }
94
94
+
url = "2.5.4"
95
95
+
tungstenite = "0.26.2"
94
96
95
97
96
98
[dev-dependencies]
+2
src/cli/mod.rs
Reviewed
···
18
18
19
19
Usage: shapemaker test-video [options] [--color <mapping>...] <file>
20
20
shapemaker beacon start [options] [--color <mapping>...] <file>
21
21
+
shapemaker beacon ping
21
22
shapemaker examples (dna-analysis-machine|shapeshed|colors-shed|grid) [options] <file>
22
23
shapemaker new <name>
23
24
shapemaker watch [<directory>]
···
95
96
pub cmd_start: bool,
96
97
pub cmd_new: bool,
97
98
pub cmd_watch: bool,
99
99
+
pub cmd_ping: bool,
98
100
pub arg_directory: String,
99
101
pub arg_name: String,
100
102
pub arg_file: String,
+19
src/main.rs
Reviewed
···
51
51
run_video(args, canvas)
52
52
} else if args.cmd_beacon && args.cmd_start {
53
53
run_beacon_start(args, canvas)
54
54
+
} else if args.cmd_beacon && args.cmd_ping {
55
55
+
run_beacon_ping(args)
54
56
} else {
55
57
Ok(())
56
58
}
···
68
70
fn run_beacon_start(_args: cli::Args, _canvas: Canvas) -> Result<()> {
69
71
pub use vst::beacon::Beacon;
70
72
Beacon::start()
73
73
+
}
74
74
+
75
75
+
#[cfg(all(feature = "cli", not(feature = "vst")))]
76
76
+
fn run_beacon_ping(_args: cli::Args) -> Result<()> {
77
77
+
println!(
78
78
+
"VST support is disabled. Enable the vst feature to use VST beaconing."
79
79
+
);
80
80
+
Ok(())
81
81
+
}
82
82
+
83
83
+
#[cfg(all(feature = "cli", feature = "vst"))]
84
84
+
fn run_beacon_ping(_args: cli::Args) -> Result<()> {
85
85
+
use rand;
86
86
+
use vst::remote_probe::RemoteProbe;
87
87
+
let mut probe = RemoteProbe::new(rand::random());
88
88
+
probe.say("ping hehe");
89
89
+
Ok(())
71
90
}
72
91
73
92
#[cfg(all(feature = "cli", not(feature = "mp4")))]
+167
-78
src/vst/beacon.rs
Reviewed
···
1
1
extern crate env_logger;
2
2
extern crate ws;
3
3
4
4
+
use crate::vst::probe::Datapoint;
5
5
+
4
6
use super::Probe;
5
7
use anyhow::Result;
6
8
use once_cell::sync::Lazy;
7
9
use std::sync::{Mutex, MutexGuard};
10
10
+
use tungstenite;
8
11
9
12
const BEACON_PORT: u16 = 8080;
10
13
···
13
16
return format!("ws://localhost:{BEACON_PORT}");
14
17
}
15
18
16
16
-
pub fn connect_to_beacon<T: FnMut(&ws::Sender) -> ()>(
17
17
-
mut action: T,
18
18
-
) -> Result<()> {
19
19
-
ws::connect(beacon_url(), |out| {
20
20
-
action(&out);
21
21
-
move |_msg| out.close(ws::CloseCode::Normal)
22
22
-
})?;
23
23
-
Ok(())
24
24
-
}
25
25
-
26
26
-
pub fn register_probe(probe: Probe) -> Result<()> {
27
27
-
connect_to_beacon(|beacon| {
28
28
-
beacon
29
29
-
.send(format!(
30
30
-
"+ probe {}",
31
31
-
serde_json::to_string(&probe).expect("Failed to serialize probe")
32
32
-
))
33
33
-
.expect("Failed to send register probe message");
34
34
-
})?;
35
35
-
Ok(())
36
36
-
}
37
37
-
38
38
-
pub fn unregister_probe(id: u32) -> Result<()> {
39
39
-
connect_to_beacon(|beacon| {
40
40
-
beacon
41
41
-
.send(format!("- probe {}", id))
42
42
-
.expect("Failed to send unregister probe message");
43
43
-
})?;
44
44
-
Ok(())
19
19
+
pub fn connect_to_beacon() -> Result<
20
20
+
tungstenite::WebSocket<
21
21
+
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
22
22
+
>,
23
23
+
> {
24
24
+
println!("Connecting to beacon at {}", beacon_url());
25
25
+
let (socket, _response) = tungstenite::connect(beacon_url())?;
26
26
+
Ok(socket)
45
27
}
46
28
47
29
#[derive(Default)]
···
63
45
pub fn start() -> Result<()> {
64
46
ws::listen(format!("127.0.0.1:{BEACON_PORT}"), |out| {
65
47
println!("Opening beacon connection with a probe...");
66
66
-
move |msg| match msg {
67
67
-
ws::Message::Text(text) => match split3(&text) {
68
68
-
("+", "probe", probe_json) => {
69
69
-
match serde_json::from_str::<Probe>(probe_json) {
70
70
-
Ok(probe) => {
71
71
-
let mut beacon = get_beacon();
72
72
-
beacon.probes.push(probe);
73
73
-
out.send("^ probe added")
48
48
+
move |msg| {
49
49
+
println!("Received message: {:?}", msg);
50
50
+
match msg {
51
51
+
ws::Message::Text(text) => match text.split3() {
52
52
+
("?", "hi", probe_json) => {
53
53
+
match serde_json::from_str::<Probe>(probe_json) {
54
54
+
Ok(probe) => {
55
55
+
let mut beacon = get_beacon();
56
56
+
beacon.probes.push(probe);
57
57
+
out.send("{} probe added!")
58
58
+
}
59
59
+
Err(_) => out.send("? invalid JSON :/"),
60
60
+
}
61
61
+
}
62
62
+
(id_str, "hi", probe_json) => {
63
63
+
match serde_json::from_str::<Probe>(probe_json) {
64
64
+
Ok(probe) => {
65
65
+
let mut beacon = get_beacon();
66
66
+
let probe_index =
67
67
+
beacon.probes.iter().position(|p| {
68
68
+
p.id == id_str.parse::<u32>().unwrap()
69
69
+
});
70
70
+
if let None = probe_index {
71
71
+
return out.send(format!(
72
72
+
"{} not found :/",
73
73
+
probe.id
74
74
+
));
75
75
+
}
76
76
+
beacon.probes[probe_index.unwrap()] = probe;
77
77
+
out.send("{} probe added!")
78
78
+
}
79
79
+
Err(_) => out.send("? invalid JSON :/"),
80
80
+
}
81
81
+
}
82
82
+
(id_str, "byebye", "") => {
83
83
+
let id = id_str.parse::<u32>().unwrap();
84
84
+
let mut beacon = get_beacon();
85
85
+
let probe_index = beacon
86
86
+
.probes
87
87
+
.iter()
88
88
+
.position(|probe| probe.id == id);
89
89
+
match probe_index {
90
90
+
Some(probe_index) => {
91
91
+
let removed_probe =
92
92
+
beacon.probes.remove(probe_index);
93
93
+
out.send(format!(
94
94
+
"{} probe removed!",
95
95
+
removed_probe.id
96
96
+
))
97
97
+
}
98
98
+
None => out.send(format!("{id} not found :/")),
99
99
+
}
100
100
+
}
101
101
+
("*", "wtf", "") => {
102
102
+
let beacon = get_beacon();
103
103
+
let body =
104
104
+
serde_json::to_string(&beacon.probes).unwrap();
105
105
+
out.send(body)
106
106
+
}
107
107
+
(id_str, "wtf", "") => {
108
108
+
let id = id_str.parse::<u32>().unwrap();
109
109
+
let beacon = get_beacon();
110
110
+
let probe =
111
111
+
beacon.probes.iter().find(|probe| probe.id == id);
112
112
+
match probe {
113
113
+
Some(probe) => {
114
114
+
out.send(format!(
115
115
+
"probe {} with {} datapoints stored",
116
116
+
probe.id,
117
117
+
probe.datapoints.len()
118
118
+
))?;
119
119
+
out.send(
120
120
+
serde_json::to_string(probe).unwrap(),
121
121
+
)
122
122
+
}
123
123
+
None => out.send(format!("{id} not found :/")),
74
124
}
75
75
-
Err(_) => out.send("! probe invalid JSON"),
76
125
}
77
77
-
}
78
78
-
("-", "probe", id_str) => {
79
79
-
let id = id_str.parse::<u32>().unwrap();
80
80
-
let mut beacon = get_beacon();
81
81
-
let probe_index =
82
82
-
beacon.probes.iter().position(|probe| probe.id == id);
83
83
-
match probe_index {
84
84
-
Some(probe_index) => {
85
85
-
let removed_probe =
86
86
-
beacon.probes.remove(probe_index);
87
87
-
out.send(format!(
88
88
-
"^ probe {} removed",
89
89
-
removed_probe.id
90
90
-
))
126
126
+
(id_str, "say", msg) => {
127
127
+
let id = id_str.parse::<u32>().unwrap();
128
128
+
let beacon = get_beacon();
129
129
+
let probe =
130
130
+
beacon.probes.iter().find(|probe| probe.id == id);
131
131
+
match probe {
132
132
+
Some(probe) => {
133
133
+
println!("probe {}: {}", probe.id, msg);
134
134
+
out.send("ok")
135
135
+
}
136
136
+
None => out.send(format!("{id} not found :/")),
91
137
}
92
92
-
None => out.send(format!("! probe {id} not found")),
93
138
}
94
94
-
}
95
95
-
("=", "probe", "*") => {
96
96
-
let beacon = get_beacon();
97
97
-
let body = serde_json::to_string(&beacon.probes).unwrap();
98
98
-
out.send(body)
99
99
-
}
100
100
-
("=", "probe", id_str) => {
101
101
-
let id = id_str.parse::<u32>().unwrap();
102
102
-
let beacon = get_beacon();
103
103
-
let probe =
104
104
-
beacon.probes.iter().find(|probe| probe.id == id);
105
105
-
match probe {
106
106
-
Some(probe) => {
107
107
-
out.send(serde_json::to_string(probe).unwrap())
139
139
+
(probe_id, timestamp, msg) => {
140
140
+
let id = probe_id.parse::<u32>().unwrap();
141
141
+
let mut beacon = get_beacon();
142
142
+
let probe = beacon
143
143
+
.probes
144
144
+
.iter_mut()
145
145
+
.find(|probe| probe.id == id);
146
146
+
147
147
+
if let None = probe {
148
148
+
return out.send(format!("{id} not found :/"));
108
149
}
109
109
-
None => out.send(format!("! probe {id} not found")),
150
150
+
151
151
+
let probe = probe.unwrap();
152
152
+
let timestamp: usize =
153
153
+
timestamp.parse().expect(&format!(
154
154
+
"{timestamp} to be a number (timestamp)"
155
155
+
));
156
156
+
157
157
+
match msg.split2() {
158
158
+
("%", data) => match data.split2() {
159
159
+
(paramname, paramvalue) => {
160
160
+
probe.store(Datapoint::Automation(
161
161
+
timestamp,
162
162
+
paramname.parse().expect(&format!(
163
163
+
"{paramname} to be a number"
164
164
+
)),
165
165
+
paramvalue.parse().expect(&format!(
166
166
+
"{paramvalue} to be a number"
167
167
+
)),
168
168
+
))
169
169
+
}
170
170
+
},
171
171
+
("#", data) => probe.store(Datapoint::Midi(
172
172
+
timestamp,
173
173
+
data.as_bytes().to_vec(),
174
174
+
)),
175
175
+
("~", data) => probe.store(Datapoint::Audio(
176
176
+
timestamp,
177
177
+
data.split(' ')
178
178
+
.map(|f| f.parse().unwrap())
179
179
+
.collect(),
180
180
+
)),
181
181
+
_ => {
182
182
+
return out.send("invalid command :/");
183
183
+
}
184
184
+
}
185
185
+
186
186
+
out.send("gotchu!")
110
187
}
111
111
-
}
112
112
-
_ => out.send("! invalid command"),
113
113
-
},
114
114
-
ws::Message::Binary(_) => todo!(),
188
188
+
},
189
189
+
ws::Message::Binary(_) => todo!(),
190
190
+
}
115
191
}
116
192
})?;
117
193
Ok(())
118
194
}
119
195
}
120
196
121
121
-
fn split3(subject: &str) -> (&str, &str, &str) {
122
122
-
let mut parts = subject.splitn(3, ' ');
123
123
-
let first = parts.next().unwrap_or_default();
124
124
-
let second = parts.next().unwrap_or_default();
125
125
-
let third = parts.next().unwrap_or_default();
126
126
-
return (first, second, third);
197
197
+
trait FixedSplits {
198
198
+
fn split3(&self) -> (&str, &str, &str);
199
199
+
fn split2(&self) -> (&str, &str);
200
200
+
}
201
201
+
impl FixedSplits for str {
202
202
+
fn split3(&self) -> (&str, &str, &str) {
203
203
+
let mut parts = self.splitn(3, ' ');
204
204
+
let first = parts.next().unwrap_or_default();
205
205
+
let second = parts.next().unwrap_or_default();
206
206
+
let third = parts.next().unwrap_or_default();
207
207
+
return (first, second, third);
208
208
+
}
209
209
+
210
210
+
fn split2(&self) -> (&str, &str) {
211
211
+
let mut parts = self.splitn(2, ' ');
212
212
+
let first = parts.next().unwrap_or_default();
213
213
+
let second = parts.next().unwrap_or_default();
214
214
+
return (first, second);
215
215
+
}
127
216
}
+1
src/vst/mod.rs
Reviewed
···
1
1
pub mod beacon;
2
2
+
pub mod remote_probe;
2
3
pub mod probe;
3
4
pub mod vst;
4
5
+34
-4
src/vst/probe.rs
Reviewed
···
1
1
use serde::{Deserialize, Serialize};
2
2
-
use std::fmt::Display;
2
2
+
use std::{collections::HashMap, fmt::Display};
3
3
4
4
#[derive(Serialize, Deserialize, Clone)]
5
5
pub struct Probe {
6
6
pub id: u32,
7
7
pub added_at: String,
8
8
-
pub automation_name: String,
8
8
+
/// Maps automation parameter indices to their names
9
9
+
pub automation_names: HashMap<usize, String>,
10
10
+
/// MIDI In signal name
9
11
pub midi_name: String,
12
12
+
/// Audio In signal name
10
13
pub audio_name: String,
14
14
+
15
15
+
#[serde(skip)]
16
16
+
pub datapoints: Vec<Datapoint>,
17
17
+
}
18
18
+
19
19
+
#[derive(Clone)]
20
20
+
pub enum Datapoint {
21
21
+
Automation(usize, usize, f32),
22
22
+
Midi(usize, Vec<u8>),
23
23
+
Audio(usize, Vec<f32>),
11
24
}
12
25
13
26
impl Probe {
···
18
31
..self.clone()
19
32
};
20
33
}
34
34
+
35
35
+
pub fn store(&mut self, datapoint: Datapoint) {
36
36
+
self.datapoints.push(datapoint);
37
37
+
}
21
38
}
22
39
23
40
impl Display for Probe {
24
41
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25
42
write!(f, "probe {} [", self.id)?;
26
26
-
if !self.automation_name.is_empty() {
27
27
-
write!(f, "automation \"{}\"", self.automation_name)?;
43
43
+
if !self.automation_names.is_empty() {
44
44
+
write!(f, "automations {:?}", self.automation_names)?;
28
45
if !self.midi_name.is_empty() || !self.audio_name.is_empty() {
29
46
write!(f, " ")?;
30
47
}
···
42
59
return Ok(());
43
60
}
44
61
}
62
62
+
63
63
+
impl Default for Probe {
64
64
+
fn default() -> Self {
65
65
+
Self {
66
66
+
id: 0,
67
67
+
audio_name: String::new(),
68
68
+
midi_name: String::new(),
69
69
+
added_at: chrono::Utc::now().to_rfc3339(),
70
70
+
datapoints: Vec::new(),
71
71
+
automation_names: HashMap::new(),
72
72
+
}
73
73
+
}
74
74
+
}
+147
src/vst/remote_probe.rs
Reviewed
···
1
1
+
use super::{beacon::connect_to_beacon, probe::Datapoint, Probe};
2
2
+
use anyhow::Result;
3
3
+
use nih_plug::params::FloatParam;
4
4
+
use std::{fmt::Display, net::TcpStream};
5
5
+
use tungstenite::{stream::MaybeTlsStream, WebSocket};
6
6
+
7
7
+
pub struct RemoteProbe {
8
8
+
pub id: u32,
9
9
+
pub out: WebSocket<MaybeTlsStream<TcpStream>>,
10
10
+
pub pointsbuffer: Vec<Datapoint>,
11
11
+
}
12
12
+
13
13
+
impl RemoteProbe {
14
14
+
pub fn new(id: u32) -> Self {
15
15
+
Self {
16
16
+
id,
17
17
+
out: connect_to_beacon().unwrap(),
18
18
+
pointsbuffer: Vec::new(),
19
19
+
}
20
20
+
}
21
21
+
22
22
+
pub fn register(&mut self) -> Result<()> {
23
23
+
let probe = Probe {
24
24
+
id: self.id,
25
25
+
..Default::default()
26
26
+
};
27
27
+
28
28
+
self.out
29
29
+
.send(
30
30
+
format!(
31
31
+
"? hi {}",
32
32
+
serde_json::to_string(&probe)
33
33
+
.expect("Failed to serialize probe")
34
34
+
)
35
35
+
.into(),
36
36
+
)
37
37
+
.expect("Failed to send register probe message");
38
38
+
39
39
+
Ok(())
40
40
+
}
41
41
+
42
42
+
pub fn update(&mut self, probe: Probe) -> Result<()> {
43
43
+
self.out
44
44
+
.send(
45
45
+
format!(
46
46
+
"{} hi {}",
47
47
+
self.id,
48
48
+
serde_json::to_string(&probe)
49
49
+
.expect("Failed to serialize probe")
50
50
+
)
51
51
+
.into(),
52
52
+
)
53
53
+
.expect("Failed to send update probe message");
54
54
+
Ok(())
55
55
+
}
56
56
+
57
57
+
pub fn timestamp() -> usize {
58
58
+
std::time::SystemTime::now()
59
59
+
.duration_since(std::time::UNIX_EPOCH)
60
60
+
.unwrap()
61
61
+
.as_millis() as usize
62
62
+
}
63
63
+
64
64
+
/// Store a automation data point. Don't forget to call .out.flush(), this one does not flush on its own!
65
65
+
pub fn store_automation(
66
66
+
&mut self,
67
67
+
timestamp: usize,
68
68
+
param_id: usize,
69
69
+
param: &FloatParam,
70
70
+
) -> Result<()> {
71
71
+
self.store(Datapoint::Automation(timestamp, param_id, param.value()))
72
72
+
}
73
73
+
74
74
+
/// Store a audio data point. Don't forget to call .out.flush(), this one does not flush on its own!
75
75
+
pub fn store_audio(
76
76
+
&mut self,
77
77
+
timestamp: usize,
78
78
+
samples: Vec<f32>,
79
79
+
) -> Result<()> {
80
80
+
self.store(Datapoint::Audio(timestamp, samples))
81
81
+
}
82
82
+
83
83
+
/// Store a midi data point. Don't forget to call .out.flush(), this one does not flush on its own!
84
84
+
pub fn store_midi(&mut self, timestamp: usize, data: &[u8]) -> Result<()> {
85
85
+
self.store(Datapoint::Midi(timestamp, data.to_vec()))
86
86
+
}
87
87
+
88
88
+
/// Store a data point. Don't forget to call .out.flush(), this one does not flush on its own!
89
89
+
pub fn say(&mut self, msg: impl Display) -> Result<()> {
90
90
+
self.out
91
91
+
.write(format!("{} say {}", self.id, msg).into())
92
92
+
.expect("Failed to send say message");
93
93
+
Ok(())
94
94
+
}
95
95
+
96
96
+
pub fn store(&mut self, datapoint: Datapoint) -> Result<()> {
97
97
+
self.pointsbuffer.push(datapoint);
98
98
+
if self.pointsbuffer.len() >= 100 {
99
99
+
self.say("flushing buffer of datapoints")?;
100
100
+
for datapoint in self.pointsbuffer.drain(..) {
101
101
+
self.out
102
102
+
.write(
103
103
+
format!(
104
104
+
"{} {}",
105
105
+
self.id,
106
106
+
match &datapoint {
107
107
+
Datapoint::Automation(ts, param_id, value) => {
108
108
+
format!("{ts} % {} {}", param_id, value)
109
109
+
}
110
110
+
Datapoint::Midi(ts, data) => {
111
111
+
format!(
112
112
+
"{ts} # 0 {}",
113
113
+
data.iter()
114
114
+
.map(|b| b.to_string())
115
115
+
.collect::<Vec<String>>()
116
116
+
.join(" ")
117
117
+
)
118
118
+
}
119
119
+
Datapoint::Audio(ts, data) => {
120
120
+
format!(
121
121
+
"{ts} ~ {}",
122
122
+
data.iter()
123
123
+
.map(|f| f.to_string())
124
124
+
.collect::<Vec<String>>()
125
125
+
.join(" ")
126
126
+
)
127
127
+
}
128
128
+
}
129
129
+
)
130
130
+
.into(),
131
131
+
)
132
132
+
.expect("Failed to send store datapoint message");
133
133
+
}
134
134
+
self.out.flush().expect("Failed to flush probe connection");
135
135
+
}
136
136
+
137
137
+
Ok(())
138
138
+
}
139
139
+
}
140
140
+
141
141
+
impl Drop for RemoteProbe {
142
142
+
fn drop(&mut self) {
143
143
+
self.out
144
144
+
.close(None)
145
145
+
.expect("Failed to close probe connection");
146
146
+
}
147
147
+
}
+62
-42
src/vst/vst.rs
Reviewed
···
1
1
-
use super::beacon;
2
2
-
use super::probe::Probe;
1
1
+
use super::{probe::Datapoint, remote_probe::RemoteProbe};
3
2
use nih_plug::prelude::*;
4
3
use rand::Rng;
5
4
use std::sync::Arc;
6
5
7
6
pub struct ShapemakerVST {
8
7
params: Arc<ShapemakerVSTParams>,
9
9
-
probe: Probe,
8
8
+
probe: RemoteProbe,
10
9
}
11
10
12
11
#[derive(Params)]
13
12
struct ShapemakerVSTParams {
14
13
/// Used to send automation data to Shapemaker
15
15
-
#[id = "automation"]
16
16
-
pub automation: FloatParam,
14
14
+
#[id = "param1"]
15
15
+
pub param1: FloatParam,
16
16
+
#[id = "param2"]
17
17
+
pub param2: FloatParam,
18
18
+
#[id = "param3"]
19
19
+
pub param3: FloatParam,
20
20
+
#[id = "param4"]
21
21
+
pub param4: FloatParam,
22
22
+
#[id = "param5"]
23
23
+
pub param5: FloatParam,
24
24
+
#[id = "param6"]
25
25
+
pub param6: FloatParam,
26
26
+
#[id = "param7"]
27
27
+
pub param7: FloatParam,
28
28
+
#[id = "param8"]
29
29
+
pub param8: FloatParam,
30
30
+
#[id = "param9"]
31
31
+
pub param9: FloatParam,
17
32
}
18
33
19
34
impl Default for ShapemakerVST {
···
21
36
let probe_id = rand::thread_rng().gen_range(1..=u32::MAX);
22
37
Self {
23
38
params: Arc::new(ShapemakerVSTParams::default()),
24
24
-
probe: Probe {
25
25
-
id: probe_id,
26
26
-
added_at: chrono::Utc::now().to_rfc3339(),
27
27
-
automation_name: format!("{probe_id}/automation"),
28
28
-
midi_name: format!("{probe_id}/midi"),
29
29
-
audio_name: format!("{probe_id}/audio"),
30
30
-
},
39
39
+
probe: RemoteProbe::new(probe_id),
31
40
}
32
41
}
33
42
}
34
43
35
44
impl Default for ShapemakerVSTParams {
36
45
fn default() -> Self {
37
37
-
Self {
38
38
-
automation: FloatParam::new(
39
39
-
"Send automation data",
40
40
-
util::db_to_gain(0.0),
41
41
-
FloatRange::Skewed {
42
42
-
min: util::db_to_gain(-30.0),
43
43
-
max: util::db_to_gain(30.0),
44
44
-
// This makes the range appear as if it was linear when displaying the values as
45
45
-
// decibels
46
46
-
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
47
47
-
},
46
46
+
let paramdef = |id: usize| {
47
47
+
FloatParam::new(
48
48
+
format!("Param #{}", id),
49
49
+
0.0,
50
50
+
FloatRange::Linear { min: 0.0, max: 1.1 },
48
51
)
49
49
-
// Because the gain parameter is stored as linear gain instead of storing the value as
50
50
-
// decibels, we need logarithmic smoothing
51
51
-
.with_smoother(SmoothingStyle::Logarithmic(50.0))
52
52
-
.with_unit(" dB")
53
53
-
// There are many predefined formatters we can use here. If the gain was stored as
54
54
-
// decibels instead of as a linear gain value, we could have also used the
55
55
-
// `.with_step_size(0.1)` function to get internal rounding.
56
56
-
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
57
57
-
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
52
52
+
};
53
53
+
Self {
54
54
+
param1: paramdef(1),
55
55
+
param2: paramdef(2),
56
56
+
param3: paramdef(3),
57
57
+
param4: paramdef(4),
58
58
+
param5: paramdef(5),
59
59
+
param6: paramdef(6),
60
60
+
param7: paramdef(7),
61
61
+
param8: paramdef(8),
62
62
+
param9: paramdef(9),
58
63
}
59
64
}
60
65
}
···
106
111
_buffer_config: &BufferConfig,
107
112
_context: &mut impl InitContext<Self>,
108
113
) -> bool {
109
109
-
let _ = beacon::register_probe(self.probe.with_added_at_now());
114
114
+
let _ = self.probe.register();
110
115
true
111
116
}
112
117
···
116
121
}
117
122
118
123
fn deactivate(&mut self) {
119
119
-
let _ = beacon::unregister_probe(self.probe.id);
124
124
+
// probe should be removed from beacon thanks to the Drop impl
120
125
}
121
126
122
127
fn process(
123
128
&mut self,
124
124
-
buffer: &mut Buffer,
129
129
+
_buffer: &mut Buffer,
125
130
_aux: &mut AuxiliaryBuffers,
126
131
_context: &mut impl ProcessContext<Self>,
127
132
) -> ProcessStatus {
128
128
-
for channel_samples in buffer.iter_samples() {
129
129
-
// Smoothing is optionally built into the parameters themselves
130
130
-
let gain = self.params.automation.smoothed.next();
133
133
+
let ts = RemoteProbe::timestamp();
134
134
+
// self.probe.say(format!("{} sending data", ts));
135
135
+
self.probe.store_automation(ts, 1, &self.params.param1);
136
136
+
self.probe.store_automation(ts, 2, &self.params.param2);
137
137
+
self.probe.store_automation(ts, 3, &self.params.param3);
138
138
+
self.probe.store_automation(ts, 4, &self.params.param4);
139
139
+
self.probe.store_automation(ts, 4, &self.params.param4);
140
140
+
self.probe.store_automation(ts, 5, &self.params.param5);
141
141
+
self.probe.store_automation(ts, 6, &self.params.param6);
142
142
+
self.probe.store_automation(ts, 7, &self.params.param7);
143
143
+
self.probe.store_automation(ts, 8, &self.params.param8);
144
144
+
self.probe.store_automation(ts, 9, &self.params.param9);
145
145
+
// self.probe.say(format!("{} sent automation", ts));
131
146
132
132
-
for sample in channel_samples {
133
133
-
*sample *= gain;
134
134
-
}
135
135
-
}
147
147
+
// self.probe.store_audio(
148
148
+
// ts,
149
149
+
// buffer
150
150
+
// .iter_samples()
151
151
+
// .flatten()
152
152
+
// .map(|f| *f)
153
153
+
// .collect::<Vec<f32>>(),
154
154
+
// );
155
155
+
// self.probe.say(format!("{} sent audio", ts));
136
156
137
157
ProcessStatus::Normal
138
158
}