This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

🎨 Format code

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