src
cli
geometry
graphics
random
rendering
video
vst
wasm
···
2
2
pub mod run;
3
3
pub mod watch;
4
4
5
5
-
use crate::{enabled_features, Canvas, ColorMapping};
5
5
+
use crate::{Canvas, ColorMapping, enabled_features};
6
6
use docopt::Docopt;
7
7
use measure_time::debug_time;
8
8
use serde::Deserialize;
···
1
1
-
use anyhow::anyhow;
2
2
-
use cargo::{
3
3
-
core::{dependency::DepKind, EitherManifest, Package, SourceId, Workspace},
4
4
-
ops::{
5
5
-
self,
6
6
-
cargo_add::{self, AddOptions, DepOp},
7
7
-
NewOptions, VersionControl,
8
8
-
},
9
9
-
util::{
10
10
-
context::GlobalContext, toml::read_manifest, toml_mut::manifest::DepTable,
11
11
-
},
12
12
-
};
13
13
-
use std::{env, fs, path::Path};
14
14
-
15
15
-
use crate::cli::run;
16
16
-
17
17
-
pub fn new_project(name: String) -> anyhow::Result<()> {
18
18
-
let cargoctx = GlobalContext::default()?;
19
19
-
let package_path = Path::new(&env::current_dir()?).join(&name);
20
20
-
println!("Creating project at {:?}", package_path);
21
21
-
22
22
-
// Create a bin crate with the name of the directory
23
23
-
ops::new(
24
24
-
&NewOptions {
25
25
-
version_control: Some(VersionControl::NoVcs),
26
26
-
kind: ops::NewProjectKind::Bin,
27
27
-
auto_detect_kind: false,
28
28
-
path: package_path.clone(),
29
29
-
name: None,
30
30
-
edition: None,
31
31
-
registry: None,
32
32
-
},
33
33
-
&cargoctx,
34
34
-
)?;
35
35
-
36
36
-
println!("Reading manifest");
37
37
-
38
38
-
let manifest = read_manifest(
39
39
-
&package_path.clone().join("Cargo.toml"),
40
40
-
SourceId::crates_io(&cargoctx)?,
41
41
-
&cargoctx,
42
42
-
)?;
43
43
-
44
44
-
let manifest = match manifest {
45
45
-
EitherManifest::Real(manifest) => manifest,
46
46
-
EitherManifest::Virtual(_) => {
47
47
-
return Err(anyhow!("Virtual manifests not supported, run the command outside of a workspace, or create your project manually with cargo new <name> && cd <name> && cargo add shapemaker && cargo add rand"))
48
48
-
}
49
49
-
};
50
50
-
51
51
-
println!("Adding dependencies to Cargo.toml");
52
52
-
53
53
-
let workspace = Workspace::new(&package_path.join("Cargo.toml"), &cargoctx)?;
54
54
-
// Add deps
55
55
-
cargo_add::add(
56
56
-
&workspace,
57
57
-
&AddOptions {
58
58
-
dry_run: false,
59
59
-
honor_rust_version: None,
60
60
-
gctx: &cargoctx,
61
61
-
spec: &Package::new(manifest, &package_path.join("Cargo.toml")),
62
62
-
dependencies: vec![
63
63
-
DepOp {
64
64
-
crate_spec: Some("shapemaker".to_string()),
65
65
-
rename: None,
66
66
-
features: None,
67
67
-
default_features: Some(true),
68
68
-
optional: Some(false),
69
69
-
public: None,
70
70
-
registry: None,
71
71
-
path: None,
72
72
-
base: None,
73
73
-
git: Some(
74
74
-
"https://github.com/gwennlbh/shapemaker".to_string(),
75
75
-
),
76
76
-
branch: None,
77
77
-
rev: None,
78
78
-
tag: match env::var("CARGO_PKG_VERSION") {
79
79
-
Ok(version) => Some(format!("v{version}")),
80
80
-
Err(_) => None,
81
81
-
},
82
82
-
},
83
83
-
DepOp {
84
84
-
crate_spec: Some("rand".to_string()),
85
85
-
rename: None,
86
86
-
features: None,
87
87
-
default_features: Some(true),
88
88
-
optional: Some(false),
89
89
-
public: None,
90
90
-
registry: None,
91
91
-
path: None,
92
92
-
base: None,
93
93
-
git: None,
94
94
-
branch: None,
95
95
-
rev: None,
96
96
-
tag: None,
97
97
-
},
98
98
-
],
99
99
-
section: DepTable::new().set_kind(DepKind::Normal),
100
100
-
},
101
101
-
)?;
102
102
-
103
103
-
println!("Writing main.rs");
104
104
-
105
105
-
// Write template main.rs
106
106
-
fs::write(
107
107
-
package_path.join("src/main.rs"),
108
108
-
format!(
109
109
-
"
110
110
-
use shapemaker::*;
111
111
-
use rand;
112
112
-
113
113
-
pub fn main() {{
114
114
-
let mut canvas = Canvas::with_layers(vec![]);
115
115
-
116
116
-
// Make your canvas beautiful <3
117
117
-
118
118
-
canvas.render_to_png(\"{}.png\", 2000).unwrap();
119
119
-
}}",
120
120
-
name
121
121
-
)
122
122
-
.trim(),
123
123
-
)?;
124
124
-
125
125
-
run::run_project(&package_path)?;
126
126
-
127
127
-
std::env::set_current_dir(&package_path)?;
128
128
-
129
129
-
std::process::Command::new(
130
130
-
std::env::var("SHAPEMAKER_EDITOR").unwrap_or_else(|_| {
131
131
-
std::env::var("EDITOR").unwrap_or_else(|_| "code".to_string())
132
132
-
}),
133
133
-
)
134
134
-
.arg(".")
135
135
-
.spawn()?;
136
136
-
137
137
-
return Ok(());
138
138
-
}
1
1
+
use anyhow::anyhow;
2
2
+
use cargo::{
3
3
+
core::{EitherManifest, Package, SourceId, Workspace, dependency::DepKind},
4
4
+
ops::{
5
5
+
self, NewOptions, VersionControl,
6
6
+
cargo_add::{self, AddOptions, DepOp},
7
7
+
},
8
8
+
util::{
9
9
+
context::GlobalContext, toml::read_manifest, toml_mut::manifest::DepTable,
10
10
+
},
11
11
+
};
12
12
+
use std::{env, fs, path::Path};
13
13
+
14
14
+
use crate::cli::run;
15
15
+
16
16
+
pub fn new_project(name: String) -> anyhow::Result<()> {
17
17
+
let cargoctx = GlobalContext::default()?;
18
18
+
let package_path = Path::new(&env::current_dir()?).join(&name);
19
19
+
println!("Creating project at {:?}", package_path);
20
20
+
21
21
+
// Create a bin crate with the name of the directory
22
22
+
ops::new(
23
23
+
&NewOptions {
24
24
+
version_control: Some(VersionControl::NoVcs),
25
25
+
kind: ops::NewProjectKind::Bin,
26
26
+
auto_detect_kind: false,
27
27
+
path: package_path.clone(),
28
28
+
name: None,
29
29
+
edition: None,
30
30
+
registry: None,
31
31
+
},
32
32
+
&cargoctx,
33
33
+
)?;
34
34
+
35
35
+
println!("Reading manifest");
36
36
+
37
37
+
let manifest = read_manifest(
38
38
+
&package_path.clone().join("Cargo.toml"),
39
39
+
SourceId::crates_io(&cargoctx)?,
40
40
+
&cargoctx,
41
41
+
)?;
42
42
+
43
43
+
let manifest = match manifest {
44
44
+
EitherManifest::Real(manifest) => manifest,
45
45
+
EitherManifest::Virtual(_) => {
46
46
+
return Err(anyhow!(
47
47
+
"Virtual manifests not supported, run the command outside of a workspace, or create your project manually with cargo new <name> && cd <name> && cargo add shapemaker && cargo add rand"
48
48
+
));
49
49
+
}
50
50
+
};
51
51
+
52
52
+
println!("Adding dependencies to Cargo.toml");
53
53
+
54
54
+
let workspace = Workspace::new(&package_path.join("Cargo.toml"), &cargoctx)?;
55
55
+
// Add deps
56
56
+
cargo_add::add(
57
57
+
&workspace,
58
58
+
&AddOptions {
59
59
+
dry_run: false,
60
60
+
honor_rust_version: None,
61
61
+
gctx: &cargoctx,
62
62
+
spec: &Package::new(manifest, &package_path.join("Cargo.toml")),
63
63
+
dependencies: vec![
64
64
+
DepOp {
65
65
+
crate_spec: Some("shapemaker".to_string()),
66
66
+
rename: None,
67
67
+
features: None,
68
68
+
default_features: Some(true),
69
69
+
optional: Some(false),
70
70
+
public: None,
71
71
+
registry: None,
72
72
+
path: None,
73
73
+
base: None,
74
74
+
git: Some(
75
75
+
"https://github.com/gwennlbh/shapemaker".to_string(),
76
76
+
),
77
77
+
branch: None,
78
78
+
rev: None,
79
79
+
tag: match env::var("CARGO_PKG_VERSION") {
80
80
+
Ok(version) => Some(format!("v{version}")),
81
81
+
Err(_) => None,
82
82
+
},
83
83
+
},
84
84
+
DepOp {
85
85
+
crate_spec: Some("rand".to_string()),
86
86
+
rename: None,
87
87
+
features: None,
88
88
+
default_features: Some(true),
89
89
+
optional: Some(false),
90
90
+
public: None,
91
91
+
registry: None,
92
92
+
path: None,
93
93
+
base: None,
94
94
+
git: None,
95
95
+
branch: None,
96
96
+
rev: None,
97
97
+
tag: None,
98
98
+
},
99
99
+
],
100
100
+
section: DepTable::new().set_kind(DepKind::Normal),
101
101
+
},
102
102
+
)?;
103
103
+
104
104
+
println!("Writing main.rs");
105
105
+
106
106
+
// Write template main.rs
107
107
+
fs::write(
108
108
+
package_path.join("src/main.rs"),
109
109
+
format!(
110
110
+
"
111
111
+
use shapemaker::*;
112
112
+
use rand;
113
113
+
114
114
+
pub fn main() {{
115
115
+
let mut canvas = Canvas::with_layers(vec![]);
116
116
+
117
117
+
// Make your canvas beautiful <3
118
118
+
119
119
+
canvas.render_to_png(\"{}.png\", 2000).unwrap();
120
120
+
}}",
121
121
+
name
122
122
+
)
123
123
+
.trim(),
124
124
+
)?;
125
125
+
126
126
+
run::run_project(&package_path)?;
127
127
+
128
128
+
std::env::set_current_dir(&package_path)?;
129
129
+
130
130
+
std::process::Command::new(
131
131
+
std::env::var("SHAPEMAKER_EDITOR").unwrap_or_else(|_| {
132
132
+
std::env::var("EDITOR").unwrap_or_else(|_| "code".to_string())
133
133
+
}),
134
134
+
)
135
135
+
.arg(".")
136
136
+
.spawn()?;
137
137
+
138
138
+
return Ok(());
139
139
+
}
···
1
1
-
use cargo::{
2
2
-
core::{
3
3
-
compiler::{BuildConfig, UserIntent},
4
4
-
resolver::CliFeatures,
5
5
-
Workspace,
6
6
-
},
7
7
-
ops::{self, CompileFilter, Packages},
8
8
-
GlobalContext,
9
9
-
};
10
10
-
use std::path::Path;
11
11
-
12
12
-
pub fn run_project(package_path: &Path) -> anyhow::Result<()> {
13
13
-
let cargoctx = GlobalContext::default()?;
14
14
-
let workspace = Workspace::new(&package_path.join("Cargo.toml"), &cargoctx)?;
15
15
-
16
16
-
ops::run(
17
17
-
&workspace,
18
18
-
&ops::CompileOptions {
19
19
-
build_config: BuildConfig::new(
20
20
-
&cargoctx,
21
21
-
None,
22
22
-
false,
23
23
-
&[],
24
24
-
UserIntent::Build,
25
25
-
)?,
26
26
-
cli_features: CliFeatures::new_all(false),
27
27
-
spec: Packages::Default,
28
28
-
filter: CompileFilter::new_all_targets(),
29
29
-
target_rustdoc_args: None,
30
30
-
target_rustc_args: None,
31
31
-
target_rustc_crate_types: None,
32
32
-
rustdoc_document_private_items: false,
33
33
-
honor_rust_version: None,
34
34
-
},
35
35
-
&[],
36
36
-
)?;
37
37
-
Ok(())
38
38
-
}
1
1
+
use cargo::{
2
2
+
GlobalContext,
3
3
+
core::{
4
4
+
Workspace,
5
5
+
compiler::{BuildConfig, UserIntent},
6
6
+
resolver::CliFeatures,
7
7
+
},
8
8
+
ops::{self, CompileFilter, Packages},
9
9
+
};
10
10
+
use std::path::Path;
11
11
+
12
12
+
pub fn run_project(package_path: &Path) -> anyhow::Result<()> {
13
13
+
let cargoctx = GlobalContext::default()?;
14
14
+
let workspace = Workspace::new(&package_path.join("Cargo.toml"), &cargoctx)?;
15
15
+
16
16
+
ops::run(
17
17
+
&workspace,
18
18
+
&ops::CompileOptions {
19
19
+
build_config: BuildConfig::new(
20
20
+
&cargoctx,
21
21
+
None,
22
22
+
false,
23
23
+
&[],
24
24
+
UserIntent::Build,
25
25
+
)?,
26
26
+
cli_features: CliFeatures::new_all(false),
27
27
+
spec: Packages::Default,
28
28
+
filter: CompileFilter::new_all_targets(),
29
29
+
target_rustdoc_args: None,
30
30
+
target_rustc_args: None,
31
31
+
target_rustc_crate_types: None,
32
32
+
rustdoc_document_private_items: false,
33
33
+
honor_rust_version: None,
34
34
+
},
35
35
+
&[],
36
36
+
)?;
37
37
+
Ok(())
38
38
+
}
···
1
1
use crate::{Object, Point};
2
2
-
use anyhow::{format_err, Error, Result};
2
2
+
use anyhow::{Error, Result, format_err};
3
3
use backtrace::Backtrace;
4
4
#[cfg(feature = "web")]
5
5
use wasm_bindgen::prelude::*;
···
181
181
}
182
182
183
183
pub fn max<'a>(&'a self, other: &'a Region) -> &'a Region {
184
184
-
if self.within(other) {
185
185
-
other
186
186
-
} else {
187
187
-
self
188
188
-
}
184
184
+
if self.within(other) { other } else { self }
189
185
}
190
186
191
187
pub fn merge<'a>(&'a self, other: &'a Region) -> Region {
···
6
6
use measure_time::debug_time;
7
7
8
8
use crate::{
9
9
-
fonts::{load_fonts, FontOptions},
10
9
Color, ColorMapping, Fill, Filter, Layer, Object, ObjectSizes, Point, Region,
10
10
+
fonts::{FontOptions, load_fonts},
11
11
};
12
12
13
13
use super::ColoredObject;
···
1
1
-
use crate::{rendering::svg, Angle, Color, ColorMapping};
1
1
+
use crate::{Angle, Color, ColorMapping, rendering::svg};
2
2
3
3
#[derive(Debug, Clone, Copy)]
4
4
pub enum Fill {
···
128
128
.attr("height", box_size)
129
129
.attr("width", box_size)
130
130
.attr("viewBox", format!("0,0,{},{}", box_size, box_size))
131
131
-
.wrapping(vec![svg::tag("circle")
132
132
-
.fill(*color, colormapping)
133
133
-
.attr("cx", box_size / 2.0)
134
134
-
.attr("cy", box_size / 2.0)
135
135
-
.attr("r", diameter / 2.0)])
131
131
+
.wrapping(vec![
132
132
+
svg::tag("circle")
133
133
+
.fill(*color, colormapping)
134
134
+
.attr("cx", box_size / 2.0)
135
135
+
.attr("cy", box_size / 2.0)
136
136
+
.attr("r", diameter / 2.0),
137
137
+
])
136
138
.node();
137
139
138
140
Some(pattern)
···
2
2
#[cfg(feature = "web")]
3
3
use wasm_bindgen::prelude::*;
4
4
5
5
-
use super::{fill::FillOperations, Color};
5
5
+
use super::{Color, fill::FillOperations};
6
6
7
7
#[derive(Debug, Clone, PartialEq, Eq)]
8
8
pub enum LineSegment {
···
38
38
Transformation,
39
39
};
40
40
pub use rendering::{
41
41
-
fonts, CSSRenderable, SVGAttributesRenderable, SVGRenderable,
41
41
+
CSSRenderable, SVGAttributesRenderable, SVGRenderable, fonts,
42
42
};
43
43
pub use video::{
44
44
-
animation, context, Animation, AttachHooks, Scene, Timestamp, Video,
44
44
+
Animation, AttachHooks, Scene, Timestamp, Video, animation, context,
45
45
};
46
46
47
47
trait Toggleable {
···
1
1
use rand::{
2
2
-
distr::{Distribution, StandardUniform, Uniform},
3
2
Rng,
3
3
+
distr::{Distribution, StandardUniform, Uniform},
4
4
};
5
5
6
6
use crate::Angle;
···
1
1
use crate::{Canvas, ColoredObject, Fill, Layer, Object, Region};
2
2
-
use rand::{distr::uniform::SampleRange, Rng};
2
2
+
use rand::{Rng, distr::uniform::SampleRange};
3
3
use std::collections::HashMap;
4
4
5
5
impl Canvas {
···
1
1
use crate::Color;
2
2
use rand::{
3
3
-
distr::{Distribution, StandardUniform},
4
3
Rng,
4
4
+
distr::{Distribution, StandardUniform},
5
5
};
6
6
7
7
impl Distribution<Color> for StandardUniform {
···
1
1
-
use rand::{distr::uniform::SampleRange, Rng};
1
1
+
use rand::{Rng, distr::uniform::SampleRange};
2
2
3
3
use crate::{LineSegment, Object, Point, Region};
4
4
···
1
1
use crate::{Containable, Point, Region};
2
2
-
use rand::{distr::uniform::SampleRange, seq::IteratorRandom, Rng};
2
2
+
use rand::{Rng, distr::uniform::SampleRange, seq::IteratorRandom};
3
3
4
4
impl Region {
5
5
pub fn random_end(&self, rng: &mut impl Rng, start: Point) -> Point {
···
2
2
3
3
use crate::{ColorMapping, Filter, FilterType};
4
4
5
5
-
use super::{renderable::SVGRenderable, svg, CSSRenderable};
5
5
+
use super::{CSSRenderable, renderable::SVGRenderable, svg};
6
6
7
7
impl SVGRenderable for Filter {
8
8
fn render_to_svg(
···
70
70
<feColorMatrix type="saturate" values="0.5"/>
71
71
</filter>
72
72
*/
73
73
-
svg::tag("filter").wrapping(vec![svg::tag("feColorMatrix")
74
74
-
.attr("type", "saturate")
75
75
-
.attr("values", self.parameter)])
73
73
+
svg::tag("filter").wrapping(vec![
74
74
+
svg::tag("feColorMatrix")
75
75
+
.attr("type", "saturate")
76
76
+
.attr("values", self.parameter),
77
77
+
])
76
78
}
77
79
}
78
80
.attr("id", self.id())
···
2
2
use measure_time::debug_time;
3
3
4
4
use crate::{
5
5
-
graphics::objects::{LineSegment, ObjectSizes},
6
5
ColoredObject, Object,
6
6
+
graphics::objects::{LineSegment, ObjectSizes},
7
7
};
8
8
9
9
use super::{
10
10
-
renderable::SVGRenderable, svg, CSSRenderable, SVGAttributesRenderable,
10
10
+
CSSRenderable, SVGAttributesRenderable, renderable::SVGRenderable, svg,
11
11
};
12
12
13
13
impl SVGRenderable for ColoredObject {
···
1
1
use super::svg;
2
2
-
use crate::{graphics::objects::ObjectSizes, ColorMapping};
2
2
+
use crate::{ColorMapping, graphics::objects::ObjectSizes};
3
3
use anyhow::Result;
4
4
use itertools::Itertools;
5
5
use std::collections::HashMap;
···
1
1
-
use std::{collections::HashMap, fmt::Display};
2
2
-
3
3
-
use itertools::Itertools;
4
4
-
use measure_time::debug_time;
5
5
-
6
6
-
use crate::{Color, ColorMapping, Point, Region};
7
7
-
8
8
-
#[derive(Debug, Clone)]
9
9
-
pub struct Element {
10
10
-
pub tag: String,
11
11
-
pub attributes: HashMap<String, String>,
12
12
-
pub styles: HashMap<String, String>,
13
13
-
pub children: Vec<Node>,
14
14
-
}
15
15
-
16
16
-
#[derive(Debug, Clone)]
17
17
-
pub enum Node {
18
18
-
Element(Element),
19
19
-
Text(String),
20
20
-
SVG(String),
21
21
-
}
22
22
-
23
23
-
impl Into<Node> for Element {
24
24
-
fn into(self) -> Node {
25
25
-
self.node()
26
26
-
}
27
27
-
}
28
28
-
29
29
-
pub fn tag(tag_name: &str) -> Element {
30
30
-
Element::new(tag_name)
31
31
-
}
32
32
-
33
33
-
pub fn node(tag_name: &str) -> Node {
34
34
-
tag(tag_name).node()
35
35
-
}
36
36
-
37
37
-
impl Node {
38
38
-
pub fn is_empty(&self) -> bool {
39
39
-
match self {
40
40
-
Node::Element(e) => e.is_empty(),
41
41
-
Node::Text(t) => t.is_empty(),
42
42
-
Node::SVG(s) => s.is_empty(),
43
43
-
}
44
44
-
}
45
45
-
}
46
46
-
47
47
-
impl Element {
48
48
-
pub fn node(self) -> Node {
49
49
-
Node::Element(self)
50
50
-
}
51
51
-
52
52
-
pub fn new(tag: &str) -> Self {
53
53
-
Element {
54
54
-
tag: tag.to_string(),
55
55
-
attributes: HashMap::new(),
56
56
-
styles: HashMap::new(),
57
57
-
children: Vec::new(),
58
58
-
}
59
59
-
}
60
60
-
61
61
-
pub fn attr(self, key: &str, value: impl Display) -> Self {
62
62
-
// assert!(
63
63
-
// key != "style",
64
64
-
// "Use `style` method instead of `attr` for style attributes."
65
65
-
// );
66
66
-
let mut attributes = self.attributes.clone();
67
67
-
attributes.insert(key.to_string(), value.to_string());
68
68
-
Element { attributes, ..self }
69
69
-
}
70
70
-
71
71
-
/// Sets x and y
72
72
-
pub fn coords(self, p: impl Into<(f32, f32)>) -> Self {
73
73
-
let (x, y) = p.into();
74
74
-
self.attr("x", x).attr("y", y)
75
75
-
}
76
76
-
77
77
-
pub fn fill(self, c: Color, colormap: &ColorMapping) -> Self {
78
78
-
self.attr("fill", c.render(colormap))
79
79
-
}
80
80
-
81
81
-
/// Sets cx and cy
82
82
-
pub fn center_position(self, p: impl Into<Point>, cell_size: usize) -> Self {
83
83
-
let (x, y) = p.into().coords(cell_size);
84
84
-
self.attr("cx", x).attr("cy", y)
85
85
-
}
86
86
-
87
87
-
/// Sets x1, y1 and x2, y2
88
88
-
pub fn position_pair(
89
89
-
self,
90
90
-
p1: impl Into<Point>,
91
91
-
p2: impl Into<Point>,
92
92
-
cell_size: usize,
93
93
-
) -> Self {
94
94
-
let (x1, y1) = p1.into().coords(cell_size);
95
95
-
let (x2, y2) = p2.into().coords(cell_size);
96
96
-
self.attr("x1", x1)
97
97
-
.attr("y1", y1)
98
98
-
.attr("x2", x2)
99
99
-
.attr("y2", y2)
100
100
-
}
101
101
-
102
102
-
/// Sets x and y
103
103
-
pub fn position(self, p: impl Into<Point>, cell_size: usize) -> Self {
104
104
-
self.coords(p.into().coords(cell_size))
105
105
-
}
106
106
-
107
107
-
/// Sets width and height
108
108
-
pub fn dimensions(self, p: impl Into<(usize, usize)>) -> Self {
109
109
-
let (w, h) = p.into();
110
110
-
self.attr("width", w).attr("height", h)
111
111
-
}
112
112
-
113
113
-
/// Sets width and height
114
114
-
pub fn size(self, r: impl Into<Region>, cell_size: usize) -> Self {
115
115
-
self.dimensions(r.into().size(cell_size))
116
116
-
}
117
117
-
118
118
-
/// Sets x, y, width and height according to the region
119
119
-
pub fn region(self, r: impl Into<Region>, cell_size: usize) -> Self {
120
120
-
let region: Region = r.into();
121
121
-
self.position(region.start, cell_size)
122
122
-
.size(region, cell_size)
123
123
-
}
124
124
-
125
125
-
pub fn style(self, key: &str, value: &str) -> Self {
126
126
-
let mut styles = self.styles.clone();
127
127
-
styles.insert(key.to_string(), value.to_string());
128
128
-
Element { styles, ..self }
129
129
-
}
130
130
-
131
131
-
pub fn dataset(self, key: &str, value: &str) -> Self {
132
132
-
self.attr(&format!("data-{key}"), value)
133
133
-
}
134
134
-
135
135
-
pub fn class(self, class: &str) -> Self {
136
136
-
self.attr("class", class)
137
137
-
}
138
138
-
139
139
-
pub fn add(&mut self, child: impl Into<Node>) -> &mut Self {
140
140
-
self.children.push(child.into());
141
141
-
self
142
142
-
}
143
143
-
144
144
-
pub fn with_attributes(self, attributes: HashMap<String, String>) -> Self {
145
145
-
Element { attributes, ..self }
146
146
-
}
147
147
-
148
148
-
pub fn wrapping(
149
149
-
self,
150
150
-
children: impl IntoIterator<Item = impl Into<Node>>,
151
151
-
) -> Self {
152
152
-
Element {
153
153
-
children: children.into_iter().map(|n| n.into()).collect(),
154
154
-
..self
155
155
-
}
156
156
-
}
157
157
-
158
158
-
pub fn wrap(self, tag: &str, attrs: HashMap<String, String>) -> Self {
159
159
-
Element {
160
160
-
tag: tag.to_string(),
161
161
-
styles: HashMap::new(),
162
162
-
attributes: attrs,
163
163
-
children: vec![Node::Element(self)],
164
164
-
}
165
165
-
}
166
166
-
167
167
-
pub fn is_empty(&self) -> bool {
168
168
-
self.children.is_empty()
169
169
-
}
170
170
-
}
171
171
-
172
172
-
pub enum PathInstruction {
173
173
-
MoveTo((f32, f32)),
174
174
-
LineTo((f32, f32)),
175
175
-
HorizontalLineTo(f32),
176
176
-
VerticalLineTo(f32),
177
177
-
CurveTo((f32, f32), (f32, f32), (f32, f32)),
178
178
-
SmoothCurveTo((f32, f32), (f32, f32)),
179
179
-
QuadraticCurveTo((f32, f32), (f32, f32)),
180
180
-
SmoothQuadraticCurveTo((f32, f32)),
181
181
-
ArcTo((f32, f32), f32, bool, bool, (f32, f32)),
182
182
-
ClosePath,
183
183
-
}
184
184
-
185
185
-
pub struct Path(Vec<PathInstruction>);
186
186
-
187
187
-
impl Path {
188
188
-
pub fn new() -> Self {
189
189
-
Path(Vec::new())
190
190
-
}
191
191
-
192
192
-
pub fn node(self) -> Node {
193
193
-
self.element().node()
194
194
-
}
195
195
-
196
196
-
pub fn element(self) -> Element {
197
197
-
tag("path").attr("d", self.to_string())
198
198
-
}
199
199
-
200
200
-
pub fn move_to(
201
201
-
&mut self,
202
202
-
p: impl Into<Point>,
203
203
-
cell_size: usize,
204
204
-
) -> &mut Self {
205
205
-
self.0
206
206
-
.push(PathInstruction::MoveTo(p.into().coords(cell_size)));
207
207
-
self
208
208
-
}
209
209
-
210
210
-
pub fn line_to(
211
211
-
&mut self,
212
212
-
p: impl Into<Point>,
213
213
-
cell_size: usize,
214
214
-
) -> &mut Self {
215
215
-
self.0
216
216
-
.push(PathInstruction::LineTo(p.into().coords(cell_size)));
217
217
-
self
218
218
-
}
219
219
-
220
220
-
pub fn quadratic_curve_to(
221
221
-
&mut self,
222
222
-
control: impl Into<(f32, f32)>,
223
223
-
end: impl Into<Point>,
224
224
-
cell_size: usize,
225
225
-
) -> &mut Self {
226
226
-
self.0.push(PathInstruction::QuadraticCurveTo(
227
227
-
control.into(),
228
228
-
end.into().coords(cell_size),
229
229
-
));
230
230
-
self
231
231
-
}
232
232
-
233
233
-
pub fn close(&mut self) -> &mut Self {
234
234
-
self.0.push(PathInstruction::ClosePath);
235
235
-
self
236
236
-
}
237
237
-
}
238
238
-
239
239
-
impl Display for Path {
240
240
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241
241
-
f.write_str(
242
242
-
&self
243
243
-
.0
244
244
-
.iter()
245
245
-
.map(|i| i.to_string())
246
246
-
.collect::<Vec<_>>()
247
247
-
.join(" "),
248
248
-
)
249
249
-
}
250
250
-
}
251
251
-
252
252
-
impl Display for PathInstruction {
253
253
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254
254
-
match self {
255
255
-
Self::MoveTo((x, y)) => write!(f, "M {x} {y}"),
256
256
-
Self::LineTo((x, y)) => write!(f, "L {x} {y}"),
257
257
-
Self::HorizontalLineTo(x) => write!(f, "H {x}"),
258
258
-
Self::VerticalLineTo(y) => write!(f, "V {y}"),
259
259
-
Self::CurveTo((x1, y1), (x2, y2), (x3, y3)) => {
260
260
-
write!(f, "C {x1} {y1} {x2} {y2} {x3} {y3}")
261
261
-
}
262
262
-
Self::SmoothCurveTo((x2, y2), (x3, y3)) => {
263
263
-
write!(f, "S {x2} {y2} {x3} {y3}")
264
264
-
}
265
265
-
Self::QuadraticCurveTo((x1, y1), (x2, y2)) => {
266
266
-
write!(f, "Q {x1} {y1} {x2} {y2}")
267
267
-
}
268
268
-
Self::SmoothQuadraticCurveTo((x2, y2)) => {
269
269
-
write!(f, "T {x2} {y2}")
270
270
-
}
271
271
-
Self::ArcTo(
272
272
-
(rx, ry),
273
273
-
angle,
274
274
-
large_arc_flag,
275
275
-
sweep_flag,
276
276
-
(x2, y2),
277
277
-
) => {
278
278
-
write!(
279
279
-
f,
280
280
-
"A {rx} {ry} {angle} {large_arc_flag} {sweep_flag} {x2} {y2}"
281
281
-
)
282
282
-
}
283
283
-
Self::ClosePath => write!(f, "Z"),
284
284
-
}
285
285
-
}
286
286
-
}
287
287
-
288
288
-
fn space_if(add_space: bool) -> &'static str {
289
289
-
if add_space {
290
290
-
" "
291
291
-
} else {
292
292
-
""
293
293
-
}
294
294
-
}
295
295
-
296
296
-
impl Display for Node {
297
297
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298
298
-
debug_time!("svg::Node::fmt");
299
299
-
300
300
-
match self {
301
301
-
Node::Text(text) => write!(f, "{}", quick_xml::escape::escape(text)),
302
302
-
Node::SVG(svg) => write!(f, "{}", svg),
303
303
-
Node::Element(Element {
304
304
-
tag,
305
305
-
attributes,
306
306
-
styles,
307
307
-
children,
308
308
-
}) => {
309
309
-
write!(f, "<{tag} ")?;
310
310
-
311
311
-
let non_style_attributes: Vec<_> = attributes
312
312
-
.iter()
313
313
-
.filter(|(k, _)| *k != "style")
314
314
-
.sorted_by_key(|(k, _)| *k)
315
315
-
.collect();
316
316
-
317
317
-
for (i, (key, value)) in non_style_attributes.iter().enumerate() {
318
318
-
write!(
319
319
-
f,
320
320
-
r#"{spacing}{key}="{value}""#,
321
321
-
spacing = space_if(i > 0),
322
322
-
key = key,
323
323
-
value = value
324
324
-
.replace("&", "&")
325
325
-
.replace('"', """)
326
326
-
.replace("'", "'")
327
327
-
)?;
328
328
-
}
329
329
-
330
330
-
if attributes.contains_key("style") || !styles.is_empty() {
331
331
-
write!(
332
332
-
f,
333
333
-
r#"{spacing}style="{value}""#,
334
334
-
spacing = space_if(non_style_attributes.len() > 0),
335
335
-
value = styles
336
336
-
.iter()
337
337
-
.map(|(k, v)| format!("{k}: {v};"))
338
338
-
.chain::<Option<String>>(
339
339
-
attributes.get("style").map(|s| s.to_string()),
340
340
-
)
341
341
-
.collect::<Vec<_>>()
342
342
-
.join(" ")
343
343
-
)?;
344
344
-
}
345
345
-
346
346
-
if children.is_empty() {
347
347
-
write!(f, "/>\n")?;
348
348
-
} else {
349
349
-
write!(f, ">\n")?;
350
350
-
351
351
-
for child in children {
352
352
-
write!(f, "{}", child)?;
353
353
-
}
354
354
-
355
355
-
write!(f, "</{tag}>")?;
356
356
-
}
357
357
-
358
358
-
Ok(())
359
359
-
}
360
360
-
}
361
361
-
}
362
362
-
}
1
1
+
use std::{collections::HashMap, fmt::Display};
2
2
+
3
3
+
use itertools::Itertools;
4
4
+
use measure_time::debug_time;
5
5
+
6
6
+
use crate::{Color, ColorMapping, Point, Region};
7
7
+
8
8
+
#[derive(Debug, Clone)]
9
9
+
pub struct Element {
10
10
+
pub tag: String,
11
11
+
pub attributes: HashMap<String, String>,
12
12
+
pub styles: HashMap<String, String>,
13
13
+
pub children: Vec<Node>,
14
14
+
}
15
15
+
16
16
+
#[derive(Debug, Clone)]
17
17
+
pub enum Node {
18
18
+
Element(Element),
19
19
+
Text(String),
20
20
+
SVG(String),
21
21
+
}
22
22
+
23
23
+
impl Into<Node> for Element {
24
24
+
fn into(self) -> Node {
25
25
+
self.node()
26
26
+
}
27
27
+
}
28
28
+
29
29
+
pub fn tag(tag_name: &str) -> Element {
30
30
+
Element::new(tag_name)
31
31
+
}
32
32
+
33
33
+
pub fn node(tag_name: &str) -> Node {
34
34
+
tag(tag_name).node()
35
35
+
}
36
36
+
37
37
+
impl Node {
38
38
+
pub fn is_empty(&self) -> bool {
39
39
+
match self {
40
40
+
Node::Element(e) => e.is_empty(),
41
41
+
Node::Text(t) => t.is_empty(),
42
42
+
Node::SVG(s) => s.is_empty(),
43
43
+
}
44
44
+
}
45
45
+
}
46
46
+
47
47
+
impl Element {
48
48
+
pub fn node(self) -> Node {
49
49
+
Node::Element(self)
50
50
+
}
51
51
+
52
52
+
pub fn new(tag: &str) -> Self {
53
53
+
Element {
54
54
+
tag: tag.to_string(),
55
55
+
attributes: HashMap::new(),
56
56
+
styles: HashMap::new(),
57
57
+
children: Vec::new(),
58
58
+
}
59
59
+
}
60
60
+
61
61
+
pub fn attr(self, key: &str, value: impl Display) -> Self {
62
62
+
// assert!(
63
63
+
// key != "style",
64
64
+
// "Use `style` method instead of `attr` for style attributes."
65
65
+
// );
66
66
+
let mut attributes = self.attributes.clone();
67
67
+
attributes.insert(key.to_string(), value.to_string());
68
68
+
Element { attributes, ..self }
69
69
+
}
70
70
+
71
71
+
/// Sets x and y
72
72
+
pub fn coords(self, p: impl Into<(f32, f32)>) -> Self {
73
73
+
let (x, y) = p.into();
74
74
+
self.attr("x", x).attr("y", y)
75
75
+
}
76
76
+
77
77
+
pub fn fill(self, c: Color, colormap: &ColorMapping) -> Self {
78
78
+
self.attr("fill", c.render(colormap))
79
79
+
}
80
80
+
81
81
+
/// Sets cx and cy
82
82
+
pub fn center_position(self, p: impl Into<Point>, cell_size: usize) -> Self {
83
83
+
let (x, y) = p.into().coords(cell_size);
84
84
+
self.attr("cx", x).attr("cy", y)
85
85
+
}
86
86
+
87
87
+
/// Sets x1, y1 and x2, y2
88
88
+
pub fn position_pair(
89
89
+
self,
90
90
+
p1: impl Into<Point>,
91
91
+
p2: impl Into<Point>,
92
92
+
cell_size: usize,
93
93
+
) -> Self {
94
94
+
let (x1, y1) = p1.into().coords(cell_size);
95
95
+
let (x2, y2) = p2.into().coords(cell_size);
96
96
+
self.attr("x1", x1)
97
97
+
.attr("y1", y1)
98
98
+
.attr("x2", x2)
99
99
+
.attr("y2", y2)
100
100
+
}
101
101
+
102
102
+
/// Sets x and y
103
103
+
pub fn position(self, p: impl Into<Point>, cell_size: usize) -> Self {
104
104
+
self.coords(p.into().coords(cell_size))
105
105
+
}
106
106
+
107
107
+
/// Sets width and height
108
108
+
pub fn dimensions(self, p: impl Into<(usize, usize)>) -> Self {
109
109
+
let (w, h) = p.into();
110
110
+
self.attr("width", w).attr("height", h)
111
111
+
}
112
112
+
113
113
+
/// Sets width and height
114
114
+
pub fn size(self, r: impl Into<Region>, cell_size: usize) -> Self {
115
115
+
self.dimensions(r.into().size(cell_size))
116
116
+
}
117
117
+
118
118
+
/// Sets x, y, width and height according to the region
119
119
+
pub fn region(self, r: impl Into<Region>, cell_size: usize) -> Self {
120
120
+
let region: Region = r.into();
121
121
+
self.position(region.start, cell_size)
122
122
+
.size(region, cell_size)
123
123
+
}
124
124
+
125
125
+
pub fn style(self, key: &str, value: &str) -> Self {
126
126
+
let mut styles = self.styles.clone();
127
127
+
styles.insert(key.to_string(), value.to_string());
128
128
+
Element { styles, ..self }
129
129
+
}
130
130
+
131
131
+
pub fn dataset(self, key: &str, value: &str) -> Self {
132
132
+
self.attr(&format!("data-{key}"), value)
133
133
+
}
134
134
+
135
135
+
pub fn class(self, class: &str) -> Self {
136
136
+
self.attr("class", class)
137
137
+
}
138
138
+
139
139
+
pub fn add(&mut self, child: impl Into<Node>) -> &mut Self {
140
140
+
self.children.push(child.into());
141
141
+
self
142
142
+
}
143
143
+
144
144
+
pub fn with_attributes(self, attributes: HashMap<String, String>) -> Self {
145
145
+
Element { attributes, ..self }
146
146
+
}
147
147
+
148
148
+
pub fn wrapping(
149
149
+
self,
150
150
+
children: impl IntoIterator<Item = impl Into<Node>>,
151
151
+
) -> Self {
152
152
+
Element {
153
153
+
children: children.into_iter().map(|n| n.into()).collect(),
154
154
+
..self
155
155
+
}
156
156
+
}
157
157
+
158
158
+
pub fn wrap(self, tag: &str, attrs: HashMap<String, String>) -> Self {
159
159
+
Element {
160
160
+
tag: tag.to_string(),
161
161
+
styles: HashMap::new(),
162
162
+
attributes: attrs,
163
163
+
children: vec![Node::Element(self)],
164
164
+
}
165
165
+
}
166
166
+
167
167
+
pub fn is_empty(&self) -> bool {
168
168
+
self.children.is_empty()
169
169
+
}
170
170
+
}
171
171
+
172
172
+
pub enum PathInstruction {
173
173
+
MoveTo((f32, f32)),
174
174
+
LineTo((f32, f32)),
175
175
+
HorizontalLineTo(f32),
176
176
+
VerticalLineTo(f32),
177
177
+
CurveTo((f32, f32), (f32, f32), (f32, f32)),
178
178
+
SmoothCurveTo((f32, f32), (f32, f32)),
179
179
+
QuadraticCurveTo((f32, f32), (f32, f32)),
180
180
+
SmoothQuadraticCurveTo((f32, f32)),
181
181
+
ArcTo((f32, f32), f32, bool, bool, (f32, f32)),
182
182
+
ClosePath,
183
183
+
}
184
184
+
185
185
+
pub struct Path(Vec<PathInstruction>);
186
186
+
187
187
+
impl Path {
188
188
+
pub fn new() -> Self {
189
189
+
Path(Vec::new())
190
190
+
}
191
191
+
192
192
+
pub fn node(self) -> Node {
193
193
+
self.element().node()
194
194
+
}
195
195
+
196
196
+
pub fn element(self) -> Element {
197
197
+
tag("path").attr("d", self.to_string())
198
198
+
}
199
199
+
200
200
+
pub fn move_to(
201
201
+
&mut self,
202
202
+
p: impl Into<Point>,
203
203
+
cell_size: usize,
204
204
+
) -> &mut Self {
205
205
+
self.0
206
206
+
.push(PathInstruction::MoveTo(p.into().coords(cell_size)));
207
207
+
self
208
208
+
}
209
209
+
210
210
+
pub fn line_to(
211
211
+
&mut self,
212
212
+
p: impl Into<Point>,
213
213
+
cell_size: usize,
214
214
+
) -> &mut Self {
215
215
+
self.0
216
216
+
.push(PathInstruction::LineTo(p.into().coords(cell_size)));
217
217
+
self
218
218
+
}
219
219
+
220
220
+
pub fn quadratic_curve_to(
221
221
+
&mut self,
222
222
+
control: impl Into<(f32, f32)>,
223
223
+
end: impl Into<Point>,
224
224
+
cell_size: usize,
225
225
+
) -> &mut Self {
226
226
+
self.0.push(PathInstruction::QuadraticCurveTo(
227
227
+
control.into(),
228
228
+
end.into().coords(cell_size),
229
229
+
));
230
230
+
self
231
231
+
}
232
232
+
233
233
+
pub fn close(&mut self) -> &mut Self {
234
234
+
self.0.push(PathInstruction::ClosePath);
235
235
+
self
236
236
+
}
237
237
+
}
238
238
+
239
239
+
impl Display for Path {
240
240
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241
241
+
f.write_str(
242
242
+
&self
243
243
+
.0
244
244
+
.iter()
245
245
+
.map(|i| i.to_string())
246
246
+
.collect::<Vec<_>>()
247
247
+
.join(" "),
248
248
+
)
249
249
+
}
250
250
+
}
251
251
+
252
252
+
impl Display for PathInstruction {
253
253
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254
254
+
match self {
255
255
+
Self::MoveTo((x, y)) => write!(f, "M {x} {y}"),
256
256
+
Self::LineTo((x, y)) => write!(f, "L {x} {y}"),
257
257
+
Self::HorizontalLineTo(x) => write!(f, "H {x}"),
258
258
+
Self::VerticalLineTo(y) => write!(f, "V {y}"),
259
259
+
Self::CurveTo((x1, y1), (x2, y2), (x3, y3)) => {
260
260
+
write!(f, "C {x1} {y1} {x2} {y2} {x3} {y3}")
261
261
+
}
262
262
+
Self::SmoothCurveTo((x2, y2), (x3, y3)) => {
263
263
+
write!(f, "S {x2} {y2} {x3} {y3}")
264
264
+
}
265
265
+
Self::QuadraticCurveTo((x1, y1), (x2, y2)) => {
266
266
+
write!(f, "Q {x1} {y1} {x2} {y2}")
267
267
+
}
268
268
+
Self::SmoothQuadraticCurveTo((x2, y2)) => {
269
269
+
write!(f, "T {x2} {y2}")
270
270
+
}
271
271
+
Self::ArcTo(
272
272
+
(rx, ry),
273
273
+
angle,
274
274
+
large_arc_flag,
275
275
+
sweep_flag,
276
276
+
(x2, y2),
277
277
+
) => {
278
278
+
write!(
279
279
+
f,
280
280
+
"A {rx} {ry} {angle} {large_arc_flag} {sweep_flag} {x2} {y2}"
281
281
+
)
282
282
+
}
283
283
+
Self::ClosePath => write!(f, "Z"),
284
284
+
}
285
285
+
}
286
286
+
}
287
287
+
288
288
+
fn space_if(add_space: bool) -> &'static str {
289
289
+
if add_space { " " } else { "" }
290
290
+
}
291
291
+
292
292
+
impl Display for Node {
293
293
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294
294
+
debug_time!("svg::Node::fmt");
295
295
+
296
296
+
match self {
297
297
+
Node::Text(text) => write!(f, "{}", quick_xml::escape::escape(text)),
298
298
+
Node::SVG(svg) => write!(f, "{}", svg),
299
299
+
Node::Element(Element {
300
300
+
tag,
301
301
+
attributes,
302
302
+
styles,
303
303
+
children,
304
304
+
}) => {
305
305
+
write!(f, "<{tag} ")?;
306
306
+
307
307
+
let non_style_attributes: Vec<_> = attributes
308
308
+
.iter()
309
309
+
.filter(|(k, _)| *k != "style")
310
310
+
.sorted_by_key(|(k, _)| *k)
311
311
+
.collect();
312
312
+
313
313
+
for (i, (key, value)) in non_style_attributes.iter().enumerate() {
314
314
+
write!(
315
315
+
f,
316
316
+
r#"{spacing}{key}="{value}""#,
317
317
+
spacing = space_if(i > 0),
318
318
+
key = key,
319
319
+
value = value
320
320
+
.replace("&", "&")
321
321
+
.replace('"', """)
322
322
+
.replace("'", "'")
323
323
+
)?;
324
324
+
}
325
325
+
326
326
+
if attributes.contains_key("style") || !styles.is_empty() {
327
327
+
write!(
328
328
+
f,
329
329
+
r#"{spacing}style="{value}""#,
330
330
+
spacing = space_if(non_style_attributes.len() > 0),
331
331
+
value = styles
332
332
+
.iter()
333
333
+
.map(|(k, v)| format!("{k}: {v};"))
334
334
+
.chain::<Option<String>>(
335
335
+
attributes.get("style").map(|s| s.to_string()),
336
336
+
)
337
337
+
.collect::<Vec<_>>()
338
338
+
.join(" ")
339
339
+
)?;
340
340
+
}
341
341
+
342
342
+
if children.is_empty() {
343
343
+
write!(f, "/>\n")?;
344
344
+
} else {
345
345
+
write!(f, ">\n")?;
346
346
+
347
347
+
for child in children {
348
348
+
write!(f, "{}", child)?;
349
349
+
}
350
350
+
351
351
+
write!(f, "</{tag}>")?;
352
352
+
}
353
353
+
354
354
+
Ok(())
355
355
+
}
356
356
+
}
357
357
+
}
358
358
+
}
···
9
9
use std::thread::{self, JoinHandle};
10
10
use std::time::{self, Duration};
11
11
12
12
-
pub const PROGRESS_BARS_STYLE: &str =
13
13
-
"\x1b]9;4;1;{percent}\x1b\\{prefix:>12.bold.cyan} {percent:03}% [{bar:25}] {msg} ({elapsed} ago)";
12
12
+
pub const PROGRESS_BARS_STYLE: &str = "\x1b]9;4;1;{percent}\x1b\\{prefix:>12.bold.cyan} {percent:03}% [{bar:25}] {msg} ({elapsed} ago)";
14
13
15
14
pub struct Spinner {
16
15
pub spinner: ProgressBar,
···
1
1
+
use super::Animation;
1
2
use super::animation::{AnimationUpdateFunction, LayerAnimationUpdateFunction};
2
3
use super::hooks::{LaterHook, LaterRenderFunction};
3
3
-
use super::Animation;
4
4
use crate::synchronization::audio::{Note, StemAtInstant};
5
5
use crate::synchronization::sync::SyncData;
6
6
use crate::ui;
···
2
2
use crate::rendering::svg;
3
3
use crate::ui::format_duration;
4
4
use crate::video::engine::EngineOutput;
5
5
-
use crate::{ui::Log, Canvas};
5
5
+
use crate::{Canvas, ui::Log};
6
6
use anyhow::Result;
7
7
use itertools::Itertools;
8
8
use measure_time::debug_time;
···
232
232
233
233
#[allow(dead_code)]
234
234
fn add_audio_track(&mut self, _output_file: String) -> Result<()> {
235
235
-
todo!("Look into https://github.com/zmwangx/rust-ffmpeg/blob/master/examples/transcode-x264.rs and maybe contribute to video-rs (see https://github.com/oddity-ai/video-rs/issues/44)");
235
235
+
todo!(
236
236
+
"Look into https://github.com/zmwangx/rust-ffmpeg/blob/master/examples/transcode-x264.rs and maybe contribute to video-rs (see https://github.com/oddity-ai/video-rs/issues/44)"
237
237
+
);
236
238
}
237
239
}
238
240
···
1
1
-
use super::{context::Context, Video};
2
2
-
use crate::rendering::svg;
3
3
-
use crate::ui::{format_duration, format_timestamp_range, Log};
1
1
+
use super::{Video, context::Context};
4
2
use crate::SVGRenderable;
3
3
+
use crate::rendering::svg;
4
4
+
use crate::ui::{Log, format_duration, format_timestamp_range};
5
5
use anyhow::Result;
6
6
use measure_time::debug_time;
7
7
use std::sync::mpsc::SyncSender;
···
1
1
-
use crate::Video;
2
2
-
use axum::{extract::Path, response::Html, routing, Router};
3
3
-
use std::sync::Arc;
4
4
-
5
5
-
pub struct VideoServer {
6
6
-
pub router: Router,
7
7
-
}
8
8
-
9
9
-
const PREVIEW_HTML: &str = include_str!("preview.html");
10
10
-
11
11
-
impl VideoServer {
12
12
-
pub fn new<C: 'static + Default>(video: Arc<Video<C>>) -> Self {
13
13
-
let _ = video.progress.clear();
14
14
-
15
15
-
let router = Router::new()
16
16
-
.route("/", routing::get(async || Html(PREVIEW_HTML)))
17
17
-
.route("/frame/{number_dot_svg}",
18
18
-
routing::get(async move |Path(number_dot_svg): Path<String>| {
19
19
-
let number: usize = number_dot_svg
20
20
-
.strip_suffix(".svg")
21
21
-
.expect("Expecting /frame/{number}.svg, didn't find .svg at the end")
22
22
-
.parse()
23
23
-
.expect("Expecting /frame/{number}.svg, couldn't parse {number} to an integer");
24
24
-
25
25
-
println!("");
26
26
-
println!("Frame number requested: {number}");
27
27
-
28
28
-
match video.render_single_frame(number) {
29
29
-
// Ok((timecode, svg)) => svg.to_string().replace(
30
30
-
// "</svg>",
31
31
-
// &format!(r#"<meta name="shapemaker:timecode" content="{timecode}" /></svg>"#)
32
32
-
// ),
33
33
-
Ok(svg) => svg.to_string(),
34
34
-
Err(err) => format!("{err:?}"),
35
35
-
}
36
36
-
}),
37
37
-
);
38
38
-
39
39
-
Self { router }
40
40
-
}
41
41
-
42
42
-
pub async fn start(self, address: &str) {
43
43
-
axum::serve(
44
44
-
tokio::net::TcpListener::bind(address).await.unwrap(),
45
45
-
self.router,
46
46
-
)
47
47
-
.await
48
48
-
.unwrap();
49
49
-
}
50
50
-
}
51
51
-
52
52
-
impl<C: 'static + Default> Video<C> {
53
53
-
pub async fn serve(self, address: &str) {
54
54
-
VideoServer::new(Arc::new(self)).start(address).await;
55
55
-
}
56
56
-
}
1
1
+
use crate::Video;
2
2
+
use axum::{Router, extract::Path, response::Html, routing};
3
3
+
use std::sync::Arc;
4
4
+
5
5
+
pub struct VideoServer {
6
6
+
pub router: Router,
7
7
+
}
8
8
+
9
9
+
const PREVIEW_HTML: &str = include_str!("preview.html");
10
10
+
11
11
+
impl VideoServer {
12
12
+
pub fn new<C: 'static + Default>(video: Arc<Video<C>>) -> Self {
13
13
+
let _ = video.progress.clear();
14
14
+
15
15
+
let router = Router::new()
16
16
+
.route("/", routing::get(async || Html(PREVIEW_HTML)))
17
17
+
.route("/frame/{number_dot_svg}",
18
18
+
routing::get(async move |Path(number_dot_svg): Path<String>| {
19
19
+
let number: usize = number_dot_svg
20
20
+
.strip_suffix(".svg")
21
21
+
.expect("Expecting /frame/{number}.svg, didn't find .svg at the end")
22
22
+
.parse()
23
23
+
.expect("Expecting /frame/{number}.svg, couldn't parse {number} to an integer");
24
24
+
25
25
+
println!("");
26
26
+
println!("Frame number requested: {number}");
27
27
+
28
28
+
match video.render_single_frame(number) {
29
29
+
// Ok((timecode, svg)) => svg.to_string().replace(
30
30
+
// "</svg>",
31
31
+
// &format!(r#"<meta name="shapemaker:timecode" content="{timecode}" /></svg>"#)
32
32
+
// ),
33
33
+
Ok(svg) => svg.to_string(),
34
34
+
Err(err) => format!("{err:?}"),
35
35
+
}
36
36
+
}),
37
37
+
);
38
38
+
39
39
+
Self { router }
40
40
+
}
41
41
+
42
42
+
pub async fn start(self, address: &str) {
43
43
+
axum::serve(
44
44
+
tokio::net::TcpListener::bind(address).await.unwrap(),
45
45
+
self.router,
46
46
+
)
47
47
+
.await
48
48
+
.unwrap();
49
49
+
}
50
50
+
}
51
51
+
52
52
+
impl<C: 'static + Default> Video<C> {
53
53
+
pub async fn serve(self, address: &str) {
54
54
+
VideoServer::new(Arc::new(self)).start(address).await;
55
55
+
}
56
56
+
}
···
1
1
-
use crate::{
2
2
-
synchronization::{
3
3
-
cue_markers::CueMarkersSynchronizer,
4
4
-
midi::MidiSynchronizer,
5
5
-
sync::{SyncData, Syncable},
6
6
-
},
7
7
-
ui::{self, display_counts, format_duration, format_filepath, Log},
8
8
-
video::hooks::{AttachHooks, CommandAction, Hook},
9
9
-
Canvas, Scene,
10
10
-
};
11
11
-
use measure_time::debug_time;
12
12
-
use std::{
13
13
-
collections::HashMap, fmt::Formatter, ops::Range, path::PathBuf,
14
14
-
time::Duration,
15
15
-
};
16
16
-
17
17
-
pub struct Command<C> {
18
18
-
pub name: String,
19
19
-
pub action: Box<CommandAction<C>>,
20
20
-
}
21
21
-
22
22
-
impl<C> std::fmt::Debug for Command<C> {
23
23
-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24
24
-
f.debug_struct("Command")
25
25
-
.field("name", &self.name)
26
26
-
.field("action", &"Box<CommandAction>")
27
27
-
.finish()
28
28
-
}
29
29
-
}
30
30
-
31
31
-
pub struct Timestamp(pub usize);
32
32
-
33
33
-
impl Timestamp {
34
34
-
pub fn ms(&self) -> usize {
35
35
-
self.0
36
36
-
}
37
37
-
38
38
-
pub fn seconds(&self) -> f64 {
39
39
-
self.0 as f64 / 1000.0
40
40
-
}
41
41
-
42
42
-
pub fn seconds_string(&self) -> String {
43
43
-
format!("{:.3}", self.seconds())
44
44
-
}
45
45
-
46
46
-
pub fn from_seconds(seconds: f64) -> Self {
47
47
-
Self((seconds * 1000.0) as usize)
48
48
-
}
49
49
-
50
50
-
pub fn from_ms(ms: usize) -> Self {
51
51
-
Self(ms)
52
52
-
}
53
53
-
}
54
54
-
55
55
-
impl Default for Timestamp {
56
56
-
fn default() -> Self {
57
57
-
Self(0)
58
58
-
}
59
59
-
}
60
60
-
61
61
-
impl std::fmt::Display for Timestamp {
62
62
-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63
63
-
write!(f, "{}", ui::format_timestamp(self.ms()))
64
64
-
}
65
65
-
}
66
66
-
67
67
-
pub struct VideoProgressBars {
68
68
-
pub loading: indicatif::ProgressBar,
69
69
-
pub rendering: indicatif::ProgressBar,
70
70
-
pub encoding: indicatif::ProgressBar,
71
71
-
}
72
72
-
73
73
-
pub struct Video<C> {
74
74
-
pub fps: usize,
75
75
-
pub initial_canvas: Canvas,
76
76
-
pub hooks: Vec<Hook<C>>,
77
77
-
pub commands: Vec<Box<Command<C>>>,
78
78
-
pub frames: Vec<Canvas>,
79
79
-
pub frames_output_directory: &'static str,
80
80
-
pub syncdata: SyncData,
81
81
-
pub audiofile: PathBuf,
82
82
-
pub resolution: u32,
83
83
-
pub duration_override: Option<Duration>,
84
84
-
pub start_rendering_at: Timestamp,
85
85
-
pub progress_bars: VideoProgressBars,
86
86
-
pub progress: indicatif::MultiProgress,
87
87
-
}
88
88
-
89
89
-
impl<C: Default> AttachHooks<C> for Video<C> {
90
90
-
fn with_hook(self, hook: Hook<C>) -> Self {
91
91
-
let mut hooks = self.hooks;
92
92
-
hooks.push(hook);
93
93
-
Self { hooks, ..self }
94
94
-
}
95
95
-
}
96
96
-
97
97
-
impl<C: Default> Default for Video<C> {
98
98
-
fn default() -> Self {
99
99
-
Self::new(Canvas::with_layers(vec!["root"]))
100
100
-
}
101
101
-
}
102
102
-
103
103
-
impl<C: Default> Video<C> {
104
104
-
pub fn new(canvas: Canvas) -> Self {
105
105
-
let progress_bars = VideoProgressBars {
106
106
-
loading: ui::setup_progress_bar(0, "Loading"),
107
107
-
rendering: ui::setup_progress_bar(0, "Rendering"),
108
108
-
encoding: ui::setup_progress_bar(0, "Encoding"),
109
109
-
};
110
110
-
111
111
-
let progress = indicatif::MultiProgress::new();
112
112
-
progress.add(progress_bars.loading.clone());
113
113
-
progress.add(progress_bars.rendering.clone());
114
114
-
progress.add(progress_bars.encoding.clone());
115
115
-
116
116
-
Self {
117
117
-
fps: 30,
118
118
-
initial_canvas: canvas,
119
119
-
hooks: vec![],
120
120
-
commands: vec![],
121
121
-
frames: vec![],
122
122
-
frames_output_directory: "frames/",
123
123
-
resolution: 1920,
124
124
-
syncdata: SyncData::default(),
125
125
-
audiofile: PathBuf::new(),
126
126
-
duration_override: None,
127
127
-
start_rendering_at: Timestamp::from_ms(0),
128
128
-
progress_bars,
129
129
-
progress,
130
130
-
}
131
131
-
}
132
132
-
133
133
-
pub fn sync_audio_with(self, sync_data_path: impl Into<PathBuf>) -> Self {
134
134
-
debug_time!("sync_audio_with");
135
135
-
136
136
-
let file_path: PathBuf = sync_data_path.into();
137
137
-
let pb = Some(&self.progress_bars.loading);
138
138
-
139
139
-
let syncdata = match file_path.extension().and_then(|s| s.to_str()) {
140
140
-
Some("mid" | "midi") => {
141
141
-
MidiSynchronizer::new(file_path.clone()).load(pb)
142
142
-
}
143
143
-
Some("flac" | "wav") => {
144
144
-
CueMarkersSynchronizer::new(file_path.clone()).load(pb)
145
145
-
}
146
146
-
_ => panic!("Unsupported sync data format"),
147
147
-
};
148
148
-
149
149
-
let pb = pb.unwrap();
150
150
-
151
151
-
pb.finish();
152
152
-
153
153
-
if let Some(bpm) = syncdata.bpm {
154
154
-
pb.log(
155
155
-
"BPM",
156
156
-
&format!("set to {bpm} from {}", format_filepath(&file_path)),
157
157
-
);
158
158
-
}
159
159
-
160
160
-
pb.log(
161
161
-
"Loaded",
162
162
-
&format!(
163
163
-
"{things} from {path} in {elapsed}",
164
164
-
path = format_filepath(&file_path),
165
165
-
elapsed = format_duration(pb.elapsed()),
166
166
-
things = display_counts(HashMap::from([
167
167
-
("markers", syncdata.markers.len()),
168
168
-
("stems", syncdata.stems.len()),
169
169
-
(
170
170
-
"notes",
171
171
-
syncdata
172
172
-
.stems
173
173
-
.values()
174
174
-
.map(|v| v.notes.len())
175
175
-
.sum::<usize>()
176
176
-
),
177
177
-
])),
178
178
-
),
179
179
-
);
180
180
-
181
181
-
return Self {
182
182
-
syncdata: self.syncdata.union(syncdata),
183
183
-
..self
184
184
-
};
185
185
-
}
186
186
-
187
187
-
pub fn ms_to_frames(&self, ms: usize) -> usize {
188
188
-
self.fps * ms / 1000
189
189
-
}
190
190
-
191
191
-
// Duration of the video, taking into account a possible duration override.
192
192
-
pub fn duration_ms(&self) -> usize {
193
193
-
match self.duration_override {
194
194
-
Some(duration) => duration.as_millis() as _,
195
195
-
None => self.total_duration_ms(),
196
196
-
}
197
197
-
}
198
198
-
199
199
-
pub fn constrained_ms_range(&self) -> Range<usize> {
200
200
-
let start_ms = self.start_rendering_at.ms();
201
201
-
let end_ms = start_ms + self.duration_ms();
202
202
-
start_ms..end_ms.min(self.total_duration_ms())
203
203
-
}
204
204
-
205
205
-
pub fn total_ms_range(&self) -> Range<usize> {
206
206
-
0..self.total_duration_ms()
207
207
-
}
208
208
-
209
209
-
/// Duration of the video, without taking into account a possible duration override.
210
210
-
pub fn total_duration_ms(&self) -> usize {
211
211
-
self.syncdata
212
212
-
.stems
213
213
-
.values()
214
214
-
.map(|stem| stem.duration_ms)
215
215
-
.max()
216
216
-
.expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.")
217
217
-
}
218
218
-
219
219
-
/// Adds hooks from the given scene to the video.
220
220
-
/// Hooks will be triggered when the current scene matches the scene's name.
221
221
-
/// Use Context#switch_scene to change scenes during rendering.
222
222
-
/// See also `with_marked_scene` for a more ergonomic way to add scenes.
223
223
-
pub fn with_scene(self, mut scene: Scene<C>) -> Self {
224
224
-
for hook in self.hooks {
225
225
-
scene.hooks.push(hook);
226
226
-
}
227
227
-
Self {
228
228
-
hooks: scene.hooks,
229
229
-
..self
230
230
-
}
231
231
-
}
232
232
-
233
233
-
/// Adds the given scene and a hook that switches to it immediately.
234
234
-
pub fn with_init_scene(self, scene: Scene<C>) -> Self {
235
235
-
let scene_name = scene.name.clone();
236
236
-
self.with_scene(scene).with_hook(Hook {
237
237
-
when: Box::new(|_, ctx, _, _| ctx.frame() == 0),
238
238
-
render_function: Box::new(move |_, ctx| {
239
239
-
ctx.switch_scene(&scene_name);
240
240
-
Ok(())
241
241
-
}),
242
242
-
})
243
243
-
}
244
244
-
245
245
-
/// Adds the given scene, and a hook that switches to it when a marker with the same name is reached
246
246
-
pub fn with_marked_scene(self, scene: Scene<C>) -> Self {
247
247
-
let scene_name = scene.name.clone();
248
248
-
249
249
-
self.with_scene(scene).with_hook(Hook {
250
250
-
when: Box::new(move |_, ctx, _, _| ctx.marker() == scene_name),
251
251
-
render_function: Box::new(move |_, ctx| {
252
252
-
ctx.switch_scene(ctx.marker());
253
253
-
Ok(())
254
254
-
}),
255
255
-
})
256
256
-
}
257
257
-
258
258
-
pub fn command(
259
259
-
self,
260
260
-
command_name: &'static str,
261
261
-
action: &'static CommandAction<C>,
262
262
-
) -> Self {
263
263
-
let mut commands = self.commands;
264
264
-
commands.push(Box::new(Command {
265
265
-
name: command_name.to_string(),
266
266
-
action: Box::new(action),
267
267
-
}));
268
268
-
Self { commands, ..self }
269
269
-
}
270
270
-
}
1
1
+
use crate::{
2
2
+
Canvas, Scene,
3
3
+
synchronization::{
4
4
+
cue_markers::CueMarkersSynchronizer,
5
5
+
midi::MidiSynchronizer,
6
6
+
sync::{SyncData, Syncable},
7
7
+
},
8
8
+
ui::{self, Log, display_counts, format_duration, format_filepath},
9
9
+
video::hooks::{AttachHooks, CommandAction, Hook},
10
10
+
};
11
11
+
use measure_time::debug_time;
12
12
+
use std::{
13
13
+
collections::HashMap, fmt::Formatter, ops::Range, path::PathBuf,
14
14
+
time::Duration,
15
15
+
};
16
16
+
17
17
+
pub struct Command<C> {
18
18
+
pub name: String,
19
19
+
pub action: Box<CommandAction<C>>,
20
20
+
}
21
21
+
22
22
+
impl<C> std::fmt::Debug for Command<C> {
23
23
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24
24
+
f.debug_struct("Command")
25
25
+
.field("name", &self.name)
26
26
+
.field("action", &"Box<CommandAction>")
27
27
+
.finish()
28
28
+
}
29
29
+
}
30
30
+
31
31
+
pub struct Timestamp(pub usize);
32
32
+
33
33
+
impl Timestamp {
34
34
+
pub fn ms(&self) -> usize {
35
35
+
self.0
36
36
+
}
37
37
+
38
38
+
pub fn seconds(&self) -> f64 {
39
39
+
self.0 as f64 / 1000.0
40
40
+
}
41
41
+
42
42
+
pub fn seconds_string(&self) -> String {
43
43
+
format!("{:.3}", self.seconds())
44
44
+
}
45
45
+
46
46
+
pub fn from_seconds(seconds: f64) -> Self {
47
47
+
Self((seconds * 1000.0) as usize)
48
48
+
}
49
49
+
50
50
+
pub fn from_ms(ms: usize) -> Self {
51
51
+
Self(ms)
52
52
+
}
53
53
+
}
54
54
+
55
55
+
impl Default for Timestamp {
56
56
+
fn default() -> Self {
57
57
+
Self(0)
58
58
+
}
59
59
+
}
60
60
+
61
61
+
impl std::fmt::Display for Timestamp {
62
62
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63
63
+
write!(f, "{}", ui::format_timestamp(self.ms()))
64
64
+
}
65
65
+
}
66
66
+
67
67
+
pub struct VideoProgressBars {
68
68
+
pub loading: indicatif::ProgressBar,
69
69
+
pub rendering: indicatif::ProgressBar,
70
70
+
pub encoding: indicatif::ProgressBar,
71
71
+
}
72
72
+
73
73
+
pub struct Video<C> {
74
74
+
pub fps: usize,
75
75
+
pub initial_canvas: Canvas,
76
76
+
pub hooks: Vec<Hook<C>>,
77
77
+
pub commands: Vec<Box<Command<C>>>,
78
78
+
pub frames: Vec<Canvas>,
79
79
+
pub frames_output_directory: &'static str,
80
80
+
pub syncdata: SyncData,
81
81
+
pub audiofile: PathBuf,
82
82
+
pub resolution: u32,
83
83
+
pub duration_override: Option<Duration>,
84
84
+
pub start_rendering_at: Timestamp,
85
85
+
pub progress_bars: VideoProgressBars,
86
86
+
pub progress: indicatif::MultiProgress,
87
87
+
}
88
88
+
89
89
+
impl<C: Default> AttachHooks<C> for Video<C> {
90
90
+
fn with_hook(self, hook: Hook<C>) -> Self {
91
91
+
let mut hooks = self.hooks;
92
92
+
hooks.push(hook);
93
93
+
Self { hooks, ..self }
94
94
+
}
95
95
+
}
96
96
+
97
97
+
impl<C: Default> Default for Video<C> {
98
98
+
fn default() -> Self {
99
99
+
Self::new(Canvas::with_layers(vec!["root"]))
100
100
+
}
101
101
+
}
102
102
+
103
103
+
impl<C: Default> Video<C> {
104
104
+
pub fn new(canvas: Canvas) -> Self {
105
105
+
let progress_bars = VideoProgressBars {
106
106
+
loading: ui::setup_progress_bar(0, "Loading"),
107
107
+
rendering: ui::setup_progress_bar(0, "Rendering"),
108
108
+
encoding: ui::setup_progress_bar(0, "Encoding"),
109
109
+
};
110
110
+
111
111
+
let progress = indicatif::MultiProgress::new();
112
112
+
progress.add(progress_bars.loading.clone());
113
113
+
progress.add(progress_bars.rendering.clone());
114
114
+
progress.add(progress_bars.encoding.clone());
115
115
+
116
116
+
Self {
117
117
+
fps: 30,
118
118
+
initial_canvas: canvas,
119
119
+
hooks: vec![],
120
120
+
commands: vec![],
121
121
+
frames: vec![],
122
122
+
frames_output_directory: "frames/",
123
123
+
resolution: 1920,
124
124
+
syncdata: SyncData::default(),
125
125
+
audiofile: PathBuf::new(),
126
126
+
duration_override: None,
127
127
+
start_rendering_at: Timestamp::from_ms(0),
128
128
+
progress_bars,
129
129
+
progress,
130
130
+
}
131
131
+
}
132
132
+
133
133
+
pub fn sync_audio_with(self, sync_data_path: impl Into<PathBuf>) -> Self {
134
134
+
debug_time!("sync_audio_with");
135
135
+
136
136
+
let file_path: PathBuf = sync_data_path.into();
137
137
+
let pb = Some(&self.progress_bars.loading);
138
138
+
139
139
+
let syncdata = match file_path.extension().and_then(|s| s.to_str()) {
140
140
+
Some("mid" | "midi") => {
141
141
+
MidiSynchronizer::new(file_path.clone()).load(pb)
142
142
+
}
143
143
+
Some("flac" | "wav") => {
144
144
+
CueMarkersSynchronizer::new(file_path.clone()).load(pb)
145
145
+
}
146
146
+
_ => panic!("Unsupported sync data format"),
147
147
+
};
148
148
+
149
149
+
let pb = pb.unwrap();
150
150
+
151
151
+
pb.finish();
152
152
+
153
153
+
if let Some(bpm) = syncdata.bpm {
154
154
+
pb.log(
155
155
+
"BPM",
156
156
+
&format!("set to {bpm} from {}", format_filepath(&file_path)),
157
157
+
);
158
158
+
}
159
159
+
160
160
+
pb.log(
161
161
+
"Loaded",
162
162
+
&format!(
163
163
+
"{things} from {path} in {elapsed}",
164
164
+
path = format_filepath(&file_path),
165
165
+
elapsed = format_duration(pb.elapsed()),
166
166
+
things = display_counts(HashMap::from([
167
167
+
("markers", syncdata.markers.len()),
168
168
+
("stems", syncdata.stems.len()),
169
169
+
(
170
170
+
"notes",
171
171
+
syncdata
172
172
+
.stems
173
173
+
.values()
174
174
+
.map(|v| v.notes.len())
175
175
+
.sum::<usize>()
176
176
+
),
177
177
+
])),
178
178
+
),
179
179
+
);
180
180
+
181
181
+
return Self {
182
182
+
syncdata: self.syncdata.union(syncdata),
183
183
+
..self
184
184
+
};
185
185
+
}
186
186
+
187
187
+
pub fn ms_to_frames(&self, ms: usize) -> usize {
188
188
+
self.fps * ms / 1000
189
189
+
}
190
190
+
191
191
+
// Duration of the video, taking into account a possible duration override.
192
192
+
pub fn duration_ms(&self) -> usize {
193
193
+
match self.duration_override {
194
194
+
Some(duration) => duration.as_millis() as _,
195
195
+
None => self.total_duration_ms(),
196
196
+
}
197
197
+
}
198
198
+
199
199
+
pub fn constrained_ms_range(&self) -> Range<usize> {
200
200
+
let start_ms = self.start_rendering_at.ms();
201
201
+
let end_ms = start_ms + self.duration_ms();
202
202
+
start_ms..end_ms.min(self.total_duration_ms())
203
203
+
}
204
204
+
205
205
+
pub fn total_ms_range(&self) -> Range<usize> {
206
206
+
0..self.total_duration_ms()
207
207
+
}
208
208
+
209
209
+
/// Duration of the video, without taking into account a possible duration override.
210
210
+
pub fn total_duration_ms(&self) -> usize {
211
211
+
self.syncdata
212
212
+
.stems
213
213
+
.values()
214
214
+
.map(|stem| stem.duration_ms)
215
215
+
.max()
216
216
+
.expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.")
217
217
+
}
218
218
+
219
219
+
/// Adds hooks from the given scene to the video.
220
220
+
/// Hooks will be triggered when the current scene matches the scene's name.
221
221
+
/// Use Context#switch_scene to change scenes during rendering.
222
222
+
/// See also `with_marked_scene` for a more ergonomic way to add scenes.
223
223
+
pub fn with_scene(self, mut scene: Scene<C>) -> Self {
224
224
+
for hook in self.hooks {
225
225
+
scene.hooks.push(hook);
226
226
+
}
227
227
+
Self {
228
228
+
hooks: scene.hooks,
229
229
+
..self
230
230
+
}
231
231
+
}
232
232
+
233
233
+
/// Adds the given scene and a hook that switches to it immediately.
234
234
+
pub fn with_init_scene(self, scene: Scene<C>) -> Self {
235
235
+
let scene_name = scene.name.clone();
236
236
+
self.with_scene(scene).with_hook(Hook {
237
237
+
when: Box::new(|_, ctx, _, _| ctx.frame() == 0),
238
238
+
render_function: Box::new(move |_, ctx| {
239
239
+
ctx.switch_scene(&scene_name);
240
240
+
Ok(())
241
241
+
}),
242
242
+
})
243
243
+
}
244
244
+
245
245
+
/// Adds the given scene, and a hook that switches to it when a marker with the same name is reached
246
246
+
pub fn with_marked_scene(self, scene: Scene<C>) -> Self {
247
247
+
let scene_name = scene.name.clone();
248
248
+
249
249
+
self.with_scene(scene).with_hook(Hook {
250
250
+
when: Box::new(move |_, ctx, _, _| ctx.marker() == scene_name),
251
251
+
render_function: Box::new(move |_, ctx| {
252
252
+
ctx.switch_scene(ctx.marker());
253
253
+
Ok(())
254
254
+
}),
255
255
+
})
256
256
+
}
257
257
+
258
258
+
pub fn command(
259
259
+
self,
260
260
+
command_name: &'static str,
261
261
+
action: &'static CommandAction<C>,
262
262
+
) -> Self {
263
263
+
let mut commands = self.commands;
264
264
+
commands.push(Box::new(Command {
265
265
+
name: command_name.to_string(),
266
266
+
action: Box::new(action),
267
267
+
}));
268
268
+
Self { commands, ..self }
269
269
+
}
270
270
+
}
···
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.
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.
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.
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.
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
-
}
1
1
+
use super::{Probe, beacon::connect_to_beacon, probe::Datapoint};
2
2
+
use anyhow::Result;
3
3
+
use nih_plug::params::FloatParam;
4
4
+
use std::{fmt::Display, net::TcpStream};
5
5
+
use tungstenite::{WebSocket, stream::MaybeTlsStream};
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.
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.
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.
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.
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
+
}
···
160
160
161
161
impl ClapPlugin for ShapemakerVST {
162
162
const CLAP_ID: &'static str = "works.gwen.shapemakervst";
163
163
-
const CLAP_DESCRIPTION: Option<&'static str> =
164
164
-
Some("A VST plugin for Shapemaker, an experimental audiovisual SVG-based rendering engine");
163
163
+
const CLAP_DESCRIPTION: Option<&'static str> = Some(
164
164
+
"A VST plugin for Shapemaker, an experimental audiovisual SVG-based rendering engine",
165
165
+
);
165
166
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
166
167
const CLAP_SUPPORT_URL: Option<&'static str> = None;
167
168
···
1
1
use super::canvas;
2
2
use crate::{
3
3
-
wasm::{append_new_div_inside, render_canvas, replace_content_with, RNG},
4
3
Color, Fill, Filter, Layer, Object, Point,
4
4
+
wasm::{RNG, append_new_div_inside, render_canvas, replace_content_with},
5
5
};
6
6
use wasm_bindgen::prelude::wasm_bindgen;
7
7
···
1
1
-
use crate::{graphics::TransformationType, Transformation};
1
1
+
use crate::{Transformation, graphics::TransformationType};
2
2
use wasm_bindgen::prelude::*;
3
3
4
4
#[wasm_bindgen(getter_with_clone)]
···
3
3
use std::sync::Mutex;
4
4
5
5
use once_cell::sync::Lazy;
6
6
-
use rand::rngs::SmallRng;
7
6
use rand::SeedableRng;
7
7
+
use rand::rngs::SmallRng;
8
8
use wasm_bindgen::prelude::wasm_bindgen;
9
9
use wasm_bindgen::{JsValue, UnwrapThrowExt};
10
10