examples
src
geometry
graphics
random
rendering
synchronization
video
vst
wasm
···
1
1
-
use shapemaker::*;
2
1
use rand;
2
2
+
use shapemaker::*;
3
3
4
4
pub fn main() {
5
5
let mut canvas = Canvas::with_colors(ColorMapping {
···
68
68
.add_to(hatches);
69
69
}
70
70
71
71
-
canvas.render_to_png("dna-analysis-machine.png", 480).unwrap();
71
71
+
canvas
72
72
+
.render_to_png("dna-analysis-machine.png", 480)
73
73
+
.unwrap();
72
74
}
···
188
188
self.start.0.min(other.start.0),
189
189
self.start.1.min(other.start.1),
190
190
),
191
191
-
end: Point(
192
192
-
self.end.0.max(other.end.0),
193
193
-
self.end.1.max(other.end.1),
194
194
-
),
191
191
+
end: Point(self.end.0.max(other.end.0), self.end.1.max(other.end.1)),
195
192
}
196
193
}
197
194
···
206
203
)
207
204
}
208
205
209
209
-
pub fn from_bottomleft(
210
210
-
origin: Point,
211
211
-
size: (usize, usize),
212
212
-
) -> Result<Self> {
206
206
+
pub fn from_bottomleft(origin: Point, size: (usize, usize)) -> Result<Self> {
213
207
Self::from_topleft(origin.translated(0, -(size.1 as i32 - 1)), size)
214
208
}
215
209
216
216
-
pub fn from_bottomright(
217
217
-
origin: Point,
218
218
-
size: (usize, usize),
219
219
-
) -> Result<Self> {
210
210
+
pub fn from_bottomright(origin: Point, size: (usize, usize)) -> Result<Self> {
220
211
Self::from_points(
221
212
origin.translated_by(Point::from(size).translated(-1, -1)),
222
213
origin,
···
134
134
}
135
135
136
136
pub fn from_json(content: &str) -> ColorMapping {
137
137
-
let json: HashMap<String, String> = serde_json::from_str(content).unwrap();
137
137
+
let json: HashMap<String, String> =
138
138
+
serde_json::from_str(content).unwrap();
138
139
ColorMapping::from_hashmap(json)
139
140
}
140
141
···
230
231
pub fn from_json_file(path: PathBuf) -> ColorMapping {
231
232
let file = File::open(path).unwrap();
232
233
let reader = BufReader::new(file);
233
233
-
let json: HashMap<String, String> = serde_json::from_reader(reader).unwrap();
234
234
+
let json: HashMap<String, String> =
235
235
+
serde_json::from_reader(reader).unwrap();
234
236
ColorMapping::from_hashmap(json)
235
237
}
236
238
···
51
51
);
52
52
}
53
53
if let Fill::Dotted(color, diameter, spacing) = self {
54
54
-
return format!("pattern-dotted-{}-{}-{}", color.name(), diameter, spacing);
54
54
+
return format!(
55
55
+
"pattern-dotted-{}-{}-{}",
56
56
+
color.name(),
57
57
+
diameter,
58
58
+
spacing
59
59
+
);
55
60
}
56
61
String::from("")
57
62
}
···
79
84
svg::node::element::Polygon::new()
80
85
.set(
81
86
"points",
82
82
-
format!("0,0 {},0 0,{}", thickness / 2.0, thickness / 2.0),
87
87
+
format!(
88
88
+
"0,0 {},0 0,{}",
89
89
+
thickness / 2.0,
90
90
+
thickness / 2.0
91
91
+
),
83
92
)
84
93
.set("fill", color.render(colormapping)),
85
94
)
···
48
48
impl PartialEq for Filter {
49
49
fn eq(&self, other: &Self) -> bool {
50
50
// TODO use way less restrictive epsilon
51
51
-
self.kind == other.kind && (self.parameter - other.parameter).abs() < f32::EPSILON
51
51
+
self.kind == other.kind
52
52
+
&& (self.parameter - other.parameter).abs() < f32::EPSILON
52
53
}
53
54
}
54
55
···
79
79
}
80
80
81
81
pub fn move_all_objects(&mut self, dx: i32, dy: i32) {
82
82
-
self.objects.iter_mut().for_each(
83
83
-
|(_, ColoredObject { object, .. })| object.translate(dx, dy),
84
84
-
);
82
82
+
self.objects
83
83
+
.iter_mut()
84
84
+
.for_each(|(_, ColoredObject { object, .. })| {
85
85
+
object.translate(dx, dy)
86
86
+
});
85
87
self.flush();
86
88
}
87
89
···
16
16
4 => Self::Dot(start),
17
17
5 => Self::CurveInward(start, region.random_end(start), line_width),
18
18
6 => Self::CurveOutward(start, region.random_end(start), line_width),
19
19
-
7 => Self::Line(Point::random(region), Point::random(region), line_width),
19
19
+
7 => Self::Line(
20
20
+
Point::random(region),
21
21
+
Point::random(region),
22
22
+
line_width,
23
23
+
),
20
24
_ => unreachable!(),
21
25
}
22
26
}
23
27
24
24
-
pub fn random_polygon(region: &Region, vertices_range: impl SampleRange<usize>) -> Object {
28
28
+
pub fn random_polygon(
29
29
+
region: &Region,
30
30
+
vertices_range: impl SampleRange<usize>,
31
31
+
) -> Object {
25
32
let number_of_anchors = rand::thread_rng().gen_range(vertices_range);
26
33
let start = Point::random(region);
27
34
let mut lines: Vec<LineSegment> = vec![];
···
47
54
polygon_vertices_range: impl SampleRange<usize>,
48
55
) -> Object {
49
56
let start = Point::random(region);
50
50
-
Object::random_starting_at(start, region, line_width, polygon_vertices_range)
57
57
+
Object::random_starting_at(
58
58
+
start,
59
59
+
region,
60
60
+
line_width,
61
61
+
polygon_vertices_range,
62
62
+
)
51
63
}
52
64
53
65
pub fn random_curve_within(region: &Region, line_width: f32) -> Object {
54
66
let start = region.random_point();
55
67
match rand::thread_rng().gen_range(1..=3) {
56
68
1 => Object::CurveInward(start, region.random_end(start), line_width),
57
57
-
2 => Object::CurveOutward(start, region.random_end(start), line_width),
58
58
-
3 => Object::Line(Point::random(region), Point::random(region), line_width),
69
69
+
2 => {
70
70
+
Object::CurveOutward(start, region.random_end(start), line_width)
71
71
+
}
72
72
+
3 => Object::Line(
73
73
+
Point::random(region),
74
74
+
Point::random(region),
75
75
+
line_width,
76
76
+
),
59
77
_ => unreachable!(),
60
78
}
61
79
}
···
29
29
}
30
30
31
31
// Pick a random end anchor from the possible end anchors
32
32
-
possible_end_anchors[rand::thread_rng().gen_range(0..possible_end_anchors.len())]
32
32
+
possible_end_anchors
33
33
+
[rand::thread_rng().gen_range(0..possible_end_anchors.len())]
33
34
}
34
35
35
36
pub fn random(within: &Region) -> Self {
···
1
1
-
use std::path::PathBuf;
2
2
-
3
3
-
use resvg::usvg;
4
4
-
5
5
-
#[derive(Default, Debug, Clone)]
6
6
-
pub struct FontOptions {
7
7
-
skip_system_fonts: bool,
8
8
-
font_files: Vec<PathBuf>,
9
9
-
font_dirs: Vec<PathBuf>,
10
10
-
serif_family: Option<String>,
11
11
-
sans_serif_family: Option<String>,
12
12
-
cursive_family: Option<String>,
13
13
-
fantasy_family: Option<String>,
14
14
-
monospace_family: Option<String>,
15
15
-
}
16
16
-
17
17
-
pub fn load_fonts(args: &FontOptions) -> anyhow::Result<usvg::Options> {
18
18
-
let mut usvg = usvg::Options {
19
19
-
font_family: args.sans_serif_family.clone().unwrap_or("Arial".into()),
20
20
-
..Default::default()
21
21
-
};
22
22
-
let fontdb = usvg.fontdb_mut();
23
23
-
24
24
-
if !args.skip_system_fonts {
25
25
-
fontdb.load_system_fonts();
26
26
-
}
27
27
-
28
28
-
for path in &args.font_files {
29
29
-
if let Err(e) = fontdb.load_font_file(path) {
30
30
-
log::warn!("Failed to load '{}' cause {}.", path.display(), e);
31
31
-
}
32
32
-
}
33
33
-
34
34
-
for path in &args.font_dirs {
35
35
-
fontdb.load_fonts_dir(path);
36
36
-
}
37
37
-
38
38
-
fontdb.set_serif_family(args.serif_family.as_deref().unwrap_or("Times New Roman"));
39
39
-
fontdb.set_sans_serif_family(args.sans_serif_family.as_deref().unwrap_or("Arial"));
40
40
-
fontdb.set_cursive_family(args.cursive_family.as_deref().unwrap_or("Comic Sans MS"));
41
41
-
fontdb.set_fantasy_family(args.fantasy_family.as_deref().unwrap_or("Impact"));
42
42
-
fontdb.set_monospace_family(args.monospace_family.as_deref().unwrap_or("Courier New"));
43
43
-
44
44
-
Ok(usvg)
45
45
-
}
1
1
+
use std::path::PathBuf;
2
2
+
3
3
+
use resvg::usvg;
4
4
+
5
5
+
#[derive(Default, Debug, Clone)]
6
6
+
pub struct FontOptions {
7
7
+
skip_system_fonts: bool,
8
8
+
font_files: Vec<PathBuf>,
9
9
+
font_dirs: Vec<PathBuf>,
10
10
+
serif_family: Option<String>,
11
11
+
sans_serif_family: Option<String>,
12
12
+
cursive_family: Option<String>,
13
13
+
fantasy_family: Option<String>,
14
14
+
monospace_family: Option<String>,
15
15
+
}
16
16
+
17
17
+
pub fn load_fonts(args: &FontOptions) -> anyhow::Result<usvg::Options> {
18
18
+
let mut usvg = usvg::Options {
19
19
+
font_family: args.sans_serif_family.clone().unwrap_or("Arial".into()),
20
20
+
..Default::default()
21
21
+
};
22
22
+
let fontdb = usvg.fontdb_mut();
23
23
+
24
24
+
if !args.skip_system_fonts {
25
25
+
fontdb.load_system_fonts();
26
26
+
}
27
27
+
28
28
+
for path in &args.font_files {
29
29
+
if let Err(e) = fontdb.load_font_file(path) {
30
30
+
log::warn!("Failed to load '{}' cause {}.", path.display(), e);
31
31
+
}
32
32
+
}
33
33
+
34
34
+
for path in &args.font_dirs {
35
35
+
fontdb.load_fonts_dir(path);
36
36
+
}
37
37
+
38
38
+
fontdb.set_serif_family(
39
39
+
args.serif_family.as_deref().unwrap_or("Times New Roman"),
40
40
+
);
41
41
+
fontdb.set_sans_serif_family(
42
42
+
args.sans_serif_family.as_deref().unwrap_or("Arial"),
43
43
+
);
44
44
+
fontdb.set_cursive_family(
45
45
+
args.cursive_family.as_deref().unwrap_or("Comic Sans MS"),
46
46
+
);
47
47
+
fontdb.set_fantasy_family(args.fantasy_family.as_deref().unwrap_or("Impact"));
48
48
+
fontdb.set_monospace_family(
49
49
+
args.monospace_family.as_deref().unwrap_or("Courier New"),
50
50
+
);
51
51
+
52
52
+
Ok(usvg)
53
53
+
}
···
1
1
-
use crate::Layer;
2
1
use super::renderable::SVGRenderable;
2
2
+
use crate::Layer;
3
3
4
4
impl SVGRenderable for Layer {
5
5
fn render_to_svg(
···
5
5
ColoredObject, Object,
6
6
};
7
7
8
8
-
use super::{
9
9
-
renderable::SVGRenderable, CSSRenderable, SVGAttributesRenderable,
10
10
-
};
8
8
+
use super::{renderable::SVGRenderable, CSSRenderable, SVGAttributesRenderable};
11
9
12
10
impl SVGRenderable for ColoredObject {
13
11
fn render_to_svg(
···
308
306
"d",
309
307
svg::node::element::path::Data::new()
310
308
.move_to(start.coords(cell_size))
311
311
-
.quadratic_curve_to((
312
312
-
control,
313
313
-
end.coords(cell_size),
314
314
-
)),
309
309
+
.quadratic_curve_to((control, end.coords(cell_size))),
315
310
)
316
311
.set("stroke-width", format!("{stroke_width}")),
317
312
);
···
32
32
pub trait CSSRenderable {
33
33
fn render_to_css_filled(&self, colormap: &ColorMapping) -> String;
34
34
fn render_to_css_stroked(&self, colormap: &ColorMapping) -> String;
35
35
-
fn render_to_css(&self, colormap: &ColorMapping, fill_as_stroke_color: bool) -> String {
35
35
+
fn render_to_css(
36
36
+
&self,
37
37
+
colormap: &ColorMapping,
38
38
+
fill_as_stroke_color: bool,
39
39
+
) -> String {
36
40
if fill_as_stroke_color {
37
41
self.render_to_css_stroked(colormap)
38
42
} else {
···
76
80
) -> Result<HashMap<String, String>> {
77
81
let mut attrs = HashMap::<String, String>::new();
78
82
for attrmap in self.clone().into_iter().map(|v| {
79
79
-
v.render_to_svg_attributes(colormap.clone(), cell_size, object_sizes, id)
80
80
-
.unwrap()
83
83
+
v.render_to_svg_attributes(
84
84
+
colormap.clone(),
85
85
+
cell_size,
86
86
+
object_sizes,
87
87
+
id,
88
88
+
)
89
89
+
.unwrap()
81
90
}) {
82
91
for (key, value) in attrmap {
83
92
if attrs.contains_key(&key) {
84
93
attrs.insert(
85
94
key.clone(),
86
86
-
format!("{}{}{}", attrs[&key], T::MULTIPLE_VALUES_JOIN_BY, value),
95
95
+
format!(
96
96
+
"{}{}{}",
97
97
+
attrs[&key],
98
98
+
T::MULTIPLE_VALUES_JOIN_BY,
99
99
+
value
100
100
+
),
87
101
);
88
102
} else {
89
103
attrs.insert(key, value);
···
17
17
match self {
18
18
Transformation::Scale(x, y) => format!("scale({} {})", x, y),
19
19
Transformation::Rotate(angle) => format!("rotate({})", angle),
20
20
-
Transformation::Skew(x, y) => format!("skewX({}) skewY({})", x, y),
20
20
+
Transformation::Skew(x, y) => {
21
21
+
format!("skewX({}) skewY({})", x, y)
22
22
+
}
21
23
Transformation::Matrix(a, b, c, d, e, f) => {
22
24
format!("matrix({}, {}, {}, {}, {}, {})", a, b, c, d, e, f)
23
25
}
···
29
29
}
30
30
31
31
fn load(&self, progressbar: Option<&ProgressBar>) -> SyncData {
32
32
-
let (now, notes_per_instrument) = load_notes(&self.midi_path, progressbar);
32
32
+
let (now, notes_per_instrument) =
33
33
+
load_notes(&self.midi_path, progressbar);
33
34
34
35
SyncData {
35
36
bpm: tempo_to_bpm(now.tempo),
36
36
-
stems: HashMap::from_iter(notes_per_instrument.iter().map(|(name, notes)| {
37
37
-
let mut notes_per_ms = HashMap::<usize, Vec<audio::Note>>::new();
37
37
+
stems: HashMap::from_iter(notes_per_instrument.iter().map(
38
38
+
|(name, notes)| {
39
39
+
let mut notes_per_ms =
40
40
+
HashMap::<usize, Vec<audio::Note>>::new();
38
41
39
39
-
if let Some(pb) = progressbar {
40
40
-
pb.set_length(notes.len() as u64);
41
41
-
pb.set_position(0);
42
42
-
}
43
43
-
progressbar.set_message(format!("Adding loaded notes for {name}"));
42
42
+
if let Some(pb) = progressbar {
43
43
+
pb.set_length(notes.len() as u64);
44
44
+
pb.set_position(0);
45
45
+
}
46
46
+
progressbar
47
47
+
.set_message(format!("Adding loaded notes for {name}"));
44
48
45
45
-
for note in notes.iter() {
46
46
-
notes_per_ms
47
47
-
.entry(note.ms as usize)
48
48
-
.or_default()
49
49
-
.push(audio::Note {
50
50
-
pitch: note.key,
51
51
-
tick: note.tick,
52
52
-
velocity: note.vel,
53
53
-
});
54
54
-
progressbar.inc(1);
55
55
-
}
49
49
+
for note in notes.iter() {
50
50
+
notes_per_ms.entry(note.ms as usize).or_default().push(
51
51
+
audio::Note {
52
52
+
pitch: note.key,
53
53
+
tick: note.tick,
54
54
+
velocity: note.vel,
55
55
+
},
56
56
+
);
57
57
+
progressbar.inc(1);
58
58
+
}
56
59
57
57
-
let duration_ms = *notes_per_ms.keys().max().unwrap_or(&0);
60
60
+
let duration_ms = *notes_per_ms.keys().max().unwrap_or(&0);
58
61
59
59
-
if let Some(pb) = progressbar {
60
60
-
pb.set_length(duration_ms as u64 - 1);
61
61
-
pb.set_position(0);
62
62
-
}
63
63
-
progressbar.set_message(format!("Infering amplitudes for {name}"));
62
62
+
if let Some(pb) = progressbar {
63
63
+
pb.set_length(duration_ms as u64 - 1);
64
64
+
pb.set_position(0);
65
65
+
}
66
66
+
progressbar
67
67
+
.set_message(format!("Infering amplitudes for {name}"));
64
68
65
65
-
let mut amplitudes = Vec::<f32>::new();
66
66
-
let mut last_amplitude = 0.0;
67
67
-
for i in 0..duration_ms {
68
68
-
if let Some(notes) = notes_per_ms.get(&i) {
69
69
-
last_amplitude = notes
70
70
-
.iter()
71
71
-
.map(|n| n.velocity as f32)
72
72
-
.collect::<Vec<f32>>()
73
73
-
.average();
69
69
+
let mut amplitudes = Vec::<f32>::new();
70
70
+
let mut last_amplitude = 0.0;
71
71
+
for i in 0..duration_ms {
72
72
+
if let Some(notes) = notes_per_ms.get(&i) {
73
73
+
last_amplitude = notes
74
74
+
.iter()
75
75
+
.map(|n| n.velocity as f32)
76
76
+
.collect::<Vec<f32>>()
77
77
+
.average();
78
78
+
}
79
79
+
amplitudes.push(last_amplitude);
80
80
+
progressbar.inc(1);
74
81
}
75
75
-
amplitudes.push(last_amplitude);
76
76
-
progressbar.inc(1);
77
77
-
}
78
82
79
79
-
(
80
80
-
name.clone(),
81
81
-
Stem {
82
82
-
amplitude_max: notes.iter().map(|n| n.vel).max().unwrap_or(0) as f32,
83
83
-
amplitude_db: amplitudes,
84
84
-
duration_ms,
85
85
-
notes: notes_per_ms,
86
86
-
name: name.clone(),
87
87
-
},
88
88
-
)
89
89
-
})),
83
83
+
(
84
84
+
name.clone(),
85
85
+
Stem {
86
86
+
amplitude_max: notes
87
87
+
.iter()
88
88
+
.map(|n| n.vel)
89
89
+
.max()
90
90
+
.unwrap_or(0)
91
91
+
as f32,
92
92
+
amplitude_db: amplitudes,
93
93
+
duration_ms,
94
94
+
notes: notes_per_ms,
95
95
+
name: name.clone(),
96
96
+
},
97
97
+
)
98
98
+
},
99
99
+
)),
90
100
markers: HashMap::new(),
91
101
}
92
102
}
···
154
164
pb.set_position(0);
155
165
}
156
166
157
157
-
let raw = std::fs::read(source)
158
158
-
.unwrap_or_else(|_| panic!("Failed to read MIDI file {}", source.to_str().unwrap()));
167
167
+
let raw = std::fs::read(source).unwrap_or_else(|_| {
168
168
+
panic!("Failed to read MIDI file {}", source.to_str().unwrap())
169
169
+
});
159
170
let midifile = midly::Smf::parse(&raw).unwrap();
160
171
161
172
let mut timeline = Timeline::new();
162
162
-
progressbar.set_message(format!("MIDI file has {} tracks", midifile.tracks.len()));
173
173
+
progressbar
174
174
+
.set_message(format!("MIDI file has {} tracks", midifile.tracks.len()));
163
175
164
176
let mut now = Now {
165
177
ms: 0,
166
178
tempo: 0,
167
179
ticks_per_beat: match midifile.header.timing {
168
180
midly::Timing::Metrical(ticks_per_beat) => ticks_per_beat.as_int(),
169
169
-
midly::Timing::Timecode(fps, subframe) => (1.0 / fps.as_f32() / subframe as f32) as u16,
181
181
+
midly::Timing::Timecode(fps, subframe) => {
182
182
+
(1.0 / fps.as_f32() / subframe as f32) as u16
183
183
+
}
170
184
},
171
185
};
172
186
···
179
193
for event in track {
180
194
match event.kind {
181
195
TrackEventKind::Meta(MetaMessage::TrackName(name_bytes)) => {
182
182
-
track_name = String::from_utf8(name_bytes.to_vec()).unwrap_or_default();
196
196
+
track_name = String::from_utf8(name_bytes.to_vec())
197
197
+
.unwrap_or_default();
183
198
}
184
199
TrackEventKind::Meta(MetaMessage::Tempo(tempo)) => {
185
200
if now.tempo == 0 {
···
239
254
}
240
255
241
256
if let Some(pb) = progressbar {
242
242
-
pb.set_length(midifile.tracks.iter().map(|t| t.len() as u64).sum::<u64>());
257
257
+
pb.set_length(
258
258
+
midifile.tracks.iter().map(|t| t.len() as u64).sum::<u64>(),
259
259
+
);
243
260
pb.set_prefix("Loading");
244
261
pb.set_message("parsing MIDI events");
245
262
pb.set_position(0);
···
255
272
} = event.kind
256
273
{
257
274
match message {
258
258
-
MidiMessage::NoteOn { key, vel } | MidiMessage::NoteOff { key, vel } => {
275
275
+
MidiMessage::NoteOn { key, vel }
276
276
+
| MidiMessage::NoteOff { key, vel } => {
259
277
stem_notes
260
278
.entry(absolute_tick_to_ms[tick] as u32)
261
279
.or_default()
···
265
283
tick: *tick,
266
284
ms: absolute_tick_to_ms[tick] as u32,
267
285
key: key.as_int(),
268
268
-
vel: if matches!(message, MidiMessage::NoteOff { .. }) {
286
286
+
vel: if matches!(
287
287
+
message,
288
288
+
MidiMessage::NoteOff { .. }
289
289
+
) {
269
290
0
270
291
} else {
271
292
vel.as_int()
···
3
3
use crate::{Canvas, Layer};
4
4
5
5
/// Arguments: animation progress (from 0.0 to 1.0), canvas, current ms
6
6
-
pub type AnimationUpdateFunction = dyn Fn(f32, &mut Canvas, usize) -> anyhow::Result<()>;
6
6
+
pub type AnimationUpdateFunction =
7
7
+
dyn Fn(f32, &mut Canvas, usize) -> anyhow::Result<()>;
7
8
8
9
/// An animation that only manipulates a single layer. The layer's render cache is automatically flushed at the end. See `AnimationUpdateFunction` for more information.
9
9
-
pub type LayerAnimationUpdateFunction = dyn Fn(f32, &mut Layer, usize) -> anyhow::Result<()>;
10
10
+
pub type LayerAnimationUpdateFunction =
11
11
+
dyn Fn(f32, &mut Layer, usize) -> anyhow::Result<()>;
10
12
11
13
pub struct Animation {
12
14
pub name: String,
···
42
42
.notes
43
43
.get(&self.ms)
44
44
.iter()
45
45
-
.map(|notes| notes.iter().map(|note| note.velocity).max().unwrap_or(0))
45
45
+
.map(|notes| {
46
46
+
notes.iter().map(|note| note.velocity).max().unwrap_or(0)
47
47
+
})
46
48
.max()
47
49
.unwrap_or(0),
48
50
duration: stems[name].duration_ms,
···
75
77
}
76
78
}
77
79
78
78
-
pub fn later_frames(&mut self, delay: usize, render_function: &'static LaterRenderFunction) {
80
80
+
pub fn later_frames(
81
81
+
&mut self,
82
82
+
delay: usize,
83
83
+
render_function: &'static LaterRenderFunction,
84
84
+
) {
79
85
let current_frame = self.frame;
80
86
81
87
self.later_hooks.insert(
···
90
96
);
91
97
}
92
98
93
93
-
pub fn later_ms(&mut self, delay: usize, render_function: &'static LaterRenderFunction) {
99
99
+
pub fn later_ms(
100
100
+
&mut self,
101
101
+
delay: usize,
102
102
+
render_function: &'static LaterRenderFunction,
103
103
+
) {
94
104
let current_ms = self.ms;
95
105
96
106
self.later_hooks.insert(
97
107
0,
98
108
LaterHook {
99
109
once: true,
100
100
-
when: Box::new(move |_, context, _previous_beat| context.ms >= current_ms + delay),
110
110
+
when: Box::new(move |_, context, _previous_beat| {
111
111
+
context.ms >= current_ms + delay
112
112
+
}),
101
113
render_function: Box::new(render_function),
102
114
},
103
115
);
104
116
}
105
117
106
106
-
pub fn later_beats(&mut self, delay: f32, render_function: &'static LaterRenderFunction) {
118
118
+
pub fn later_beats(
119
119
+
&mut self,
120
120
+
delay: f32,
121
121
+
render_function: &'static LaterRenderFunction,
122
122
+
) {
107
123
let current_beat = self.beat;
108
124
109
125
self.later_hooks.insert(
···
134
150
}
135
151
136
152
/// duration is in milliseconds
137
137
-
pub fn animate(&mut self, duration: usize, f: &'static AnimationUpdateFunction) {
153
153
+
pub fn animate(
154
154
+
&mut self,
155
155
+
duration: usize,
156
156
+
f: &'static AnimationUpdateFunction,
157
157
+
) {
138
158
self.start_animation(
139
159
duration,
140
160
Animation::new(format!("unnamed animation {}", nanoid!()), f),
···
17
17
pub type FrameNumber = usize;
18
18
pub type Millisecond = usize;
19
19
20
20
-
pub type RenderFunction<C> = dyn Fn(&mut Canvas, &mut Context<C>) -> anyhow::Result<()>;
21
21
-
pub type CommandAction<C> = dyn Fn(String, &mut Canvas, &mut Context<C>) -> anyhow::Result<()>;
20
20
+
pub type RenderFunction<C> =
21
21
+
dyn Fn(&mut Canvas, &mut Context<C>) -> anyhow::Result<()>;
22
22
+
pub type CommandAction<C> =
23
23
+
dyn Fn(String, &mut Canvas, &mut Context<C>) -> anyhow::Result<()>;
22
24
23
25
/// Arguments: canvas, context, previous rendered beat, previous rendered frame
24
24
-
pub type HookCondition<C> = dyn Fn(&Canvas, &Context<C>, BeatNumber, FrameNumber) -> bool;
26
26
+
pub type HookCondition<C> =
27
27
+
dyn Fn(&Canvas, &Context<C>, BeatNumber, FrameNumber) -> bool;
25
28
26
29
/// Arguments: canvas, context, current milliseconds timestamp
27
27
-
pub type LaterRenderFunction = dyn Fn(&mut Canvas, Millisecond) -> anyhow::Result<()>;
30
30
+
pub type LaterRenderFunction =
31
31
+
dyn Fn(&mut Canvas, Millisecond) -> anyhow::Result<()>;
28
32
29
33
/// Arguments: canvas, context, previous rendered beat
30
34
pub type LaterHookCondition<C> = dyn Fn(&Canvas, &Context<C>, BeatNumber) -> bool;
···
137
141
Self { hooks, ..self }
138
142
}
139
143
140
140
-
pub fn init(self, render_function: &'static RenderFunction<AdditionalContext>) -> Self {
144
144
+
pub fn init(
145
145
+
self,
146
146
+
render_function: &'static RenderFunction<AdditionalContext>,
147
147
+
) -> Self {
141
148
self.with_hook(Hook {
142
149
when: Box::new(move |_, context, _, _| context.frame == 0),
143
150
render_function: Box::new(render_function),
···
150
157
render_function: &'static RenderFunction<AdditionalContext>,
151
158
) -> Self {
152
159
self.with_hook(Hook {
153
153
-
when: Box::new(move |_, context, _, _| context.marker() == marker_text),
160
160
+
when: Box::new(move |_, context, _, _| {
161
161
+
context.marker() == marker_text
162
162
+
}),
154
163
render_function: Box::new(render_function),
155
164
})
156
165
}
157
166
158
158
-
pub fn each_beat(self, render_function: &'static RenderFunction<AdditionalContext>) -> Self {
167
167
+
pub fn each_beat(
168
168
+
self,
169
169
+
render_function: &'static RenderFunction<AdditionalContext>,
170
170
+
) -> Self {
159
171
self.with_hook(Hook {
160
172
when: Box::new(
161
161
-
move |_, context, previous_rendered_beat, previous_rendered_frame| {
173
173
+
move |_,
174
174
+
context,
175
175
+
previous_rendered_beat,
176
176
+
previous_rendered_frame| {
162
177
previous_rendered_frame != context.frame
163
163
-
&& (context.ms == 0 || previous_rendered_beat != context.beat)
178
178
+
&& (context.ms == 0
179
179
+
|| previous_rendered_beat != context.beat)
164
180
},
165
181
),
166
182
render_function: Box::new(render_function),
···
183
199
};
184
200
185
201
self.with_hook(Hook {
186
186
-
when: Box::new(move |_, context, _, _| context.beat_fractional % beats < 0.01),
202
202
+
when: Box::new(move |_, context, _, _| {
203
203
+
context.beat_fractional % beats < 0.01
204
204
+
}),
187
205
render_function: Box::new(render_function),
188
206
})
189
207
}
190
208
191
191
-
pub fn each_frame(self, render_function: &'static RenderFunction<AdditionalContext>) -> Self {
209
209
+
pub fn each_frame(
210
210
+
self,
211
211
+
render_function: &'static RenderFunction<AdditionalContext>,
212
212
+
) -> Self {
192
213
let hook = Hook {
193
214
when: Box::new(move |_, context, _, previous_rendered_frame| {
194
215
context.frame != previous_rendered_frame
···
281
302
) -> Self {
282
303
self.with_hook(Hook {
283
304
when: Box::new(move |_, ctx, _, _| {
284
284
-
stems
285
285
-
.split(',')
286
286
-
.any(|stem_name| ctx.stem(stem_name).notes.iter().any(|note| note.is_on()))
305
305
+
stems.split(',').any(|stem_name| {
306
306
+
ctx.stem(stem_name).notes.iter().any(|note| note.is_on())
307
307
+
})
287
308
}),
288
309
render_function: Box::new(move |canvas, ctx| {
289
310
let object = create_object(canvas, ctx)?;
···
295
316
when: Box::new(move |_, ctx, _, _| {
296
317
stems.split(',').any(|stem_name| {
297
318
ctx.stem(stem_name).amplitude_relative() < cutoff_amplitude
298
298
-
|| ctx.stem(stem_name).notes.iter().any(|note| note.is_off())
319
319
+
|| ctx
320
320
+
.stem(stem_name)
321
321
+
.notes
322
322
+
.iter()
323
323
+
.any(|note| note.is_off())
299
324
})
300
325
}),
301
326
render_function: Box::new(move |canvas, _| {
···
370
395
match precision {
371
396
"milliseconds" => {
372
397
let current_time: NaiveDateTime =
373
373
-
NaiveDateTime::parse_from_str(timestamp, "%H:%M:%S%.3f").unwrap();
398
398
+
NaiveDateTime::parse_from_str(
399
399
+
timestamp,
400
400
+
"%H:%M:%S%.3f",
401
401
+
)
402
402
+
.unwrap();
374
403
current_time == criteria_time
375
404
}
376
405
"seconds" => {
377
406
let current_time: NaiveDateTime =
378
378
-
NaiveDateTime::parse_from_str(timestamp, "%H:%M:%S").unwrap();
407
407
+
NaiveDateTime::parse_from_str(timestamp, "%H:%M:%S")
408
408
+
.unwrap();
379
409
current_time == criteria_time
380
410
}
381
411
_ => panic!("Unknown precision"),
···
1
1
-
extern crate env_logger;
2
2
-
extern crate ws;
3
3
-
4
4
-
use super::Probe;
5
5
-
use anyhow::Result;
6
6
-
use once_cell::sync::Lazy;
7
7
-
use std::sync::{Mutex, MutexGuard};
8
8
-
9
9
-
const BEACON_PORT: u16 = 8080;
10
10
-
11
11
-
#[inline]
12
12
-
pub fn beacon_url() -> String {
13
13
-
return format!("ws://localhost:{BEACON_PORT}");
14
14
-
}
15
15
-
16
16
-
pub fn connect_to_beacon<T: FnMut(&ws::Sender) -> ()>(mut action: T) -> Result<()> {
17
17
-
ws::connect(beacon_url(), |out| {
18
18
-
action(&out);
19
19
-
move |_msg| out.close(ws::CloseCode::Normal)
20
20
-
})?;
21
21
-
Ok(())
22
22
-
}
23
23
-
24
24
-
pub fn register_probe(probe: Probe) -> Result<()> {
25
25
-
connect_to_beacon(|beacon| {
26
26
-
beacon
27
27
-
.send(format!(
28
28
-
"+ probe {}",
29
29
-
serde_json::to_string(&probe).expect("Failed to serialize probe")
30
30
-
))
31
31
-
.expect("Failed to send register probe message");
32
32
-
})?;
33
33
-
Ok(())
34
34
-
}
35
35
-
36
36
-
pub fn unregister_probe(id: u32) -> Result<()> {
37
37
-
connect_to_beacon(|beacon| {
38
38
-
beacon
39
39
-
.send(format!("- probe {}", id))
40
40
-
.expect("Failed to send unregister probe message");
41
41
-
})?;
42
42
-
Ok(())
43
43
-
}
44
44
-
45
45
-
#[derive(Default)]
46
46
-
pub struct Beacon {
47
47
-
pub probes: Vec<Probe>,
48
48
-
}
49
49
-
50
50
-
static BEACON: Lazy<Mutex<Beacon>> = Lazy::new(|| Mutex::new(Beacon::new()));
51
51
-
52
52
-
pub fn get_beacon() -> MutexGuard<'static, Beacon> {
53
53
-
return BEACON.lock().unwrap();
54
54
-
}
55
55
-
56
56
-
impl Beacon {
57
57
-
pub fn new() -> Self {
58
58
-
return Self::default();
59
59
-
}
60
60
-
61
61
-
pub fn start() -> Result<()> {
62
62
-
ws::listen(format!("127.0.0.1:{BEACON_PORT}"), |out| {
63
63
-
println!("Opening beacon connection with a probe...");
64
64
-
move |msg| match msg {
65
65
-
ws::Message::Text(text) => match split3(&text) {
66
66
-
("+", "probe", probe_json) => match serde_json::from_str::<Probe>(probe_json) {
67
67
-
Ok(probe) => {
68
68
-
let mut beacon = get_beacon();
69
69
-
beacon.probes.push(probe);
70
70
-
out.send("^ probe added")
71
71
-
}
72
72
-
Err(_) => out.send("! probe invalid JSON"),
73
73
-
},
74
74
-
("-", "probe", id_str) => {
75
75
-
let id = id_str.parse::<u32>().unwrap();
76
76
-
let mut beacon = get_beacon();
77
77
-
let probe_index = beacon.probes.iter().position(|probe| probe.id == id);
78
78
-
match probe_index {
79
79
-
Some(probe_index) => {
80
80
-
let removed_probe = beacon.probes.remove(probe_index);
81
81
-
out.send(format!("^ probe {} removed", removed_probe.id))
82
82
-
}
83
83
-
None => out.send(format!("! probe {id} not found")),
84
84
-
}
85
85
-
}
86
86
-
("=", "probe", "*") => {
87
87
-
let beacon = get_beacon();
88
88
-
let body = serde_json::to_string(&beacon.probes).unwrap();
89
89
-
out.send(body)
90
90
-
}
91
91
-
("=", "probe", id_str) => {
92
92
-
let id = id_str.parse::<u32>().unwrap();
93
93
-
let beacon = get_beacon();
94
94
-
let probe = beacon.probes.iter().find(|probe| probe.id == id);
95
95
-
match probe {
96
96
-
Some(probe) => out.send(serde_json::to_string(probe).unwrap()),
97
97
-
None => out.send(format!("! probe {id} not found")),
98
98
-
}
99
99
-
}
100
100
-
_ => out.send("! invalid command"),
101
101
-
},
102
102
-
ws::Message::Binary(_) => todo!(),
103
103
-
}
104
104
-
})?;
105
105
-
Ok(())
106
106
-
}
107
107
-
}
108
108
-
109
109
-
fn split3(subject: &str) -> (&str, &str, &str) {
110
110
-
let mut parts = subject.splitn(3, ' ');
111
111
-
let first = parts.next().unwrap_or_default();
112
112
-
let second = parts.next().unwrap_or_default();
113
113
-
let third = parts.next().unwrap_or_default();
114
114
-
return (first, second, third);
115
115
-
}
1
1
+
extern crate env_logger;
2
2
+
extern crate ws;
3
3
+
4
4
+
use super::Probe;
5
5
+
use anyhow::Result;
6
6
+
use once_cell::sync::Lazy;
7
7
+
use std::sync::{Mutex, MutexGuard};
8
8
+
9
9
+
const BEACON_PORT: u16 = 8080;
10
10
+
11
11
+
#[inline]
12
12
+
pub fn beacon_url() -> String {
13
13
+
return format!("ws://localhost:{BEACON_PORT}");
14
14
+
}
15
15
+
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(())
45
45
+
}
46
46
+
47
47
+
#[derive(Default)]
48
48
+
pub struct Beacon {
49
49
+
pub probes: Vec<Probe>,
50
50
+
}
51
51
+
52
52
+
static BEACON: Lazy<Mutex<Beacon>> = Lazy::new(|| Mutex::new(Beacon::new()));
53
53
+
54
54
+
pub fn get_beacon() -> MutexGuard<'static, Beacon> {
55
55
+
return BEACON.lock().unwrap();
56
56
+
}
57
57
+
58
58
+
impl Beacon {
59
59
+
pub fn new() -> Self {
60
60
+
return Self::default();
61
61
+
}
62
62
+
63
63
+
pub fn start() -> Result<()> {
64
64
+
ws::listen(format!("127.0.0.1:{BEACON_PORT}"), |out| {
65
65
+
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")
74
74
+
}
75
75
+
Err(_) => out.send("! probe invalid JSON"),
76
76
+
}
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
+
))
91
91
+
}
92
92
+
None => out.send(format!("! probe {id} not found")),
93
93
+
}
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())
108
108
+
}
109
109
+
None => out.send(format!("! probe {id} not found")),
110
110
+
}
111
111
+
}
112
112
+
_ => out.send("! invalid command"),
113
113
+
},
114
114
+
ws::Message::Binary(_) => todo!(),
115
115
+
}
116
116
+
})?;
117
117
+
Ok(())
118
118
+
}
119
119
+
}
120
120
+
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);
127
127
+
}
···
1
1
-
pub mod beacon;
2
2
-
pub mod vst;
3
3
-
pub mod probe;
4
4
-
5
5
-
use nih_plug::{nih_export_clap, nih_export_vst3};
6
6
-
pub use probe::Probe;
7
7
-
pub use vst::*;
8
8
-
9
9
-
nih_export_clap!(ShapemakerVST);
10
10
-
nih_export_vst3!(ShapemakerVST);
1
1
+
pub mod beacon;
2
2
+
pub mod probe;
3
3
+
pub mod vst;
4
4
+
5
5
+
use nih_plug::{nih_export_clap, nih_export_vst3};
6
6
+
pub use probe::Probe;
7
7
+
pub use vst::*;
8
8
+
9
9
+
nih_export_clap!(ShapemakerVST);
10
10
+
nih_export_vst3!(ShapemakerVST);
···
1
1
-
use std::fmt::Display;
2
2
-
use serde::{Deserialize, Serialize};
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,
9
9
-
pub midi_name: String,
10
10
-
pub audio_name: String,
11
11
-
}
12
12
-
13
13
-
impl Probe {
14
14
-
/// Returns a new probe with the `added_at` field set to the current time.
15
15
-
pub fn with_added_at_now(&self) -> Self {
16
16
-
return Self {
17
17
-
added_at: chrono::Utc::now().to_rfc3339(),
18
18
-
..self.clone()
19
19
-
};
20
20
-
}
21
21
-
}
22
22
-
23
23
-
impl Display for Probe {
24
24
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25
25
-
write!(f, "probe {} [", self.id)?;
26
26
-
if !self.automation_name.is_empty() {
27
27
-
write!(f, "automation \"{}\"", self.automation_name)?;
28
28
-
if !self.midi_name.is_empty() || !self.audio_name.is_empty() {
29
29
-
write!(f, " ")?;
30
30
-
}
31
31
-
}
32
32
-
if !self.midi_name.is_empty() {
33
33
-
write!(f, "midi \"{}\"", self.midi_name)?;
34
34
-
if !self.audio_name.is_empty() {
35
35
-
write!(f, " ")?;
36
36
-
}
37
37
-
}
38
38
-
if !self.audio_name.is_empty() {
39
39
-
write!(f, "audio \"{}\"", self.audio_name)?;
40
40
-
}
41
41
-
write!(f, "]")?;
42
42
-
return Ok(());
43
43
-
}
44
44
-
}
1
1
+
use serde::{Deserialize, Serialize};
2
2
+
use std::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,
9
9
+
pub midi_name: String,
10
10
+
pub audio_name: String,
11
11
+
}
12
12
+
13
13
+
impl Probe {
14
14
+
/// Returns a new probe with the `added_at` field set to the current time.
15
15
+
pub fn with_added_at_now(&self) -> Self {
16
16
+
return Self {
17
17
+
added_at: chrono::Utc::now().to_rfc3339(),
18
18
+
..self.clone()
19
19
+
};
20
20
+
}
21
21
+
}
22
22
+
23
23
+
impl Display for Probe {
24
24
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25
25
+
write!(f, "probe {} [", self.id)?;
26
26
+
if !self.automation_name.is_empty() {
27
27
+
write!(f, "automation \"{}\"", self.automation_name)?;
28
28
+
if !self.midi_name.is_empty() || !self.audio_name.is_empty() {
29
29
+
write!(f, " ")?;
30
30
+
}
31
31
+
}
32
32
+
if !self.midi_name.is_empty() {
33
33
+
write!(f, "midi \"{}\"", self.midi_name)?;
34
34
+
if !self.audio_name.is_empty() {
35
35
+
write!(f, " ")?;
36
36
+
}
37
37
+
}
38
38
+
if !self.audio_name.is_empty() {
39
39
+
write!(f, "audio \"{}\"", self.audio_name)?;
40
40
+
}
41
41
+
write!(f, "]")?;
42
42
+
return Ok(());
43
43
+
}
44
44
+
}
···
1
1
+
use super::beacon;
2
2
+
use super::probe::Probe;
1
3
use nih_plug::prelude::*;
2
4
use rand::Rng;
3
5
use std::sync::Arc;
4
4
-
use super::beacon;
5
5
-
use super::probe::Probe;
6
6
7
7
pub struct ShapemakerVST {
8
8
params: Arc<ShapemakerVSTParams>,
···
146
146
const CLAP_SUPPORT_URL: Option<&'static str> = None;
147
147
148
148
// Don't forget to change these features
149
149
-
const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::AudioEffect, ClapFeature::Stereo];
149
149
+
const CLAP_FEATURES: &'static [ClapFeature] =
150
150
+
&[ClapFeature::AudioEffect, ClapFeature::Stereo];
150
151
}
151
152
152
153
impl Vst3Plugin for ShapemakerVST {
···
11
11
impl From<TransformationWASM> for Transformation {
12
12
fn from(transformation: TransformationWASM) -> Self {
13
13
match transformation.kind {
14
14
-
TransformationType::Scale => {
15
15
-
Transformation::Scale(transformation.parameters[0], transformation.parameters[1])
16
16
-
}
17
17
-
TransformationType::Rotate => Transformation::Rotate(transformation.parameters[0]),
18
18
-
TransformationType::Skew => {
19
19
-
Transformation::Skew(transformation.parameters[0], transformation.parameters[1])
14
14
+
TransformationType::Scale => Transformation::Scale(
15
15
+
transformation.parameters[0],
16
16
+
transformation.parameters[1],
17
17
+
),
18
18
+
TransformationType::Rotate => {
19
19
+
Transformation::Rotate(transformation.parameters[0])
20
20
}
21
21
+
TransformationType::Skew => Transformation::Skew(
22
22
+
transformation.parameters[0],
23
23
+
transformation.parameters[1],
24
24
+
),
21
25
TransformationType::Matrix => Transformation::Matrix(
22
26
transformation.parameters[0],
23
27
transformation.parameters[1],