this repo has no description
3
fork

Configure Feed

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

✨ Introduce Regions, and other stuff

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
feec5e80 6c527db5

+259 -96
+3
Justfile
··· 2 2 cargo build 3 3 cp target/debug/shapemaker . 4 4 5 + install: 6 + cp shapemaker ~/.local/bin/ 7 + 5 8 example-video: 6 9 ./shapemaker video --colors colorschemes/afterglow.json out.mp4 --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x9
+167 -43
src/canvas.rs
··· 2 2 collections::HashMap, 3 3 fs::File, 4 4 io::{BufReader, Write}, 5 - ops::Range, 5 + ops::{Range, RangeInclusive}, 6 6 }; 7 7 8 8 use chrono::DateTime; ··· 11 11 12 12 use crate::layer::Layer; 13 13 14 + #[derive(Debug, Clone, Default)] 15 + pub struct Region { 16 + pub start: (usize, usize), 17 + pub end: (usize, usize), 18 + } 19 + 20 + impl Region { 21 + pub fn new(start_x: usize, start_y: usize, end_x: usize, end_y: usize) -> Self { 22 + let region = Self { 23 + start: (start_x, start_y), 24 + end: (end_x, end_y), 25 + }; 26 + region.ensure_valid(); 27 + region 28 + } 29 + 30 + pub fn from_origin(end: (usize, usize)) -> Self { 31 + Self::new(0, 0, end.0, end.1) 32 + } 33 + 34 + // panics if the region is invalid 35 + pub fn ensure_valid(&self) { 36 + if self.start.0 >= self.end.0 || self.start.1 >= self.end.1 { 37 + panic!( 38 + "Invalid region: start ({:?}) >= end ({:?})", 39 + self.start, self.end 40 + ) 41 + } 42 + } 43 + 44 + pub fn translate(&mut self, dx: i32, dy: i32) { 45 + self.start.0 = (self.start.0 as i32 + dx) as usize; 46 + self.start.1 = (self.start.1 as i32 + dy) as usize; 47 + self.end.0 = (self.end.0 as i32 + dx) as usize; 48 + self.end.1 = (self.end.1 as i32 + dy) as usize; 49 + self.ensure_valid(); 50 + } 51 + 52 + pub fn x_range(&self) -> Range<usize> { 53 + self.start.0..self.end.0 54 + } 55 + pub fn y_range(&self) -> Range<usize> { 56 + self.start.1..self.end.1 57 + } 58 + 59 + pub fn x_range_without_last(&self) -> Range<usize> { 60 + self.start.0..self.end.0 - 1 61 + } 62 + 63 + pub fn y_range_without_last(&self) -> Range<usize> { 64 + self.start.1..self.end.1 - 1 65 + } 66 + 67 + pub fn within(&self, other: &Region) -> bool { 68 + self.start.0 >= other.start.0 69 + && self.start.1 >= other.start.1 70 + && self.end.0 <= other.end.0 71 + && self.end.1 <= other.end.1 72 + } 73 + 74 + pub fn clamped(&self, within: &Region) -> Region { 75 + Region { 76 + start: ( 77 + self.start.0.max(within.start.0), 78 + self.start.1.max(within.start.1), 79 + ), 80 + end: (self.end.0.min(within.end.0), self.end.1.min(within.end.1)), 81 + } 82 + } 83 + 84 + pub fn width(&self) -> usize { 85 + self.end.0 - self.start.0 86 + } 87 + 88 + pub fn height(&self) -> usize { 89 + self.end.1 - self.start.1 90 + } 91 + 92 + // goes from -width to width (inclusive on both ends) 93 + pub fn mirrored_width_range(&self) -> RangeInclusive<i32> { 94 + let w = self.width() as i32; 95 + -w..=w 96 + } 97 + 98 + pub fn mirrored_height_range(&self) -> RangeInclusive<i32> { 99 + let h = self.height() as i32; 100 + -h..=h 101 + } 102 + 103 + fn contains(&self, anchor: &Anchor) -> bool { 104 + self.x_range().contains(&(anchor.0 as usize)) 105 + && self.y_range().contains(&(anchor.1 as usize)) 106 + } 107 + } 108 + 14 109 #[derive(Debug, Clone)] 15 110 pub struct Canvas { 16 111 pub grid_size: (usize, usize), ··· 24 119 /// The layers are in order of top to bottom: the first layer will be rendered on top of the second, etc. 25 120 pub layers: Vec<Layer>, 26 121 pub background: Option<Color>, 122 + 123 + pub world_region: Region, 27 124 } 28 125 29 126 impl Canvas { ··· 32 129 /// A layer named "root" will be added below all layers if you don't add it yourself. 33 130 pub fn new(layer_names: Vec<&str>) -> Self { 34 131 let mut layer_names = layer_names; 35 - if let None = layer_names.iter().find(|&&name| name == "root") { 132 + if !layer_names.iter().any(|&name| name == "root") { 36 133 layer_names.push("root"); 37 134 } 38 135 Self { ··· 48 145 } 49 146 } 50 147 148 + pub fn set_grid_size(&mut self, new_width: usize, new_height: usize) { 149 + self.grid_size = (new_width, new_height); 150 + self.world_region = Region { 151 + start: (0, 0), 152 + end: self.grid_size, 153 + }; 154 + } 155 + 51 156 pub fn layer(&mut self, name: &str) -> Option<&mut Layer> { 52 157 self.layers.iter_mut().find(|layer| layer.name == name) 53 158 } ··· 64 169 object: Object, 65 170 fill: Option<Fill>, 66 171 ) -> Result<(), String> { 67 - match self.layer(&layer) { 172 + match self.layer(layer) { 68 173 None => Err(format!("Layer {} does not exist", layer)), 69 174 Some(layer) => { 70 175 layer.objects.insert(name.to_string(), (object, fill)); ··· 104 209 render_grid: false, 105 210 colormap: ColorMapping::default(), 106 211 layers: vec![], 212 + world_region: Region::new(0, 0, 3, 3), 107 213 background: None, 108 214 } 109 215 } 216 + 110 217 pub fn random_layer(&self, name: &'static str) -> Layer { 218 + self.random_layer_within(name, &self.world_region) 219 + } 220 + 221 + pub fn random_object(&self) -> Object { 222 + self.random_object_within(&self.world_region) 223 + } 224 + 225 + pub fn replace_or_create_layer(&mut self, name: &'static str, layer: Layer) { 226 + if let Some(existing_layer) = self.layer(name) { 227 + existing_layer.replace(layer); 228 + } else { 229 + self.layers.push(layer); 230 + } 231 + } 232 + 233 + pub fn random_layer_within(&self, name: &'static str, region: &Region) -> Layer { 111 234 let mut objects: HashMap<String, (Object, Option<Fill>)> = HashMap::new(); 112 235 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 113 236 for i in 0..number_of_objects { 114 - let object = self.random_object(); 237 + let object = self.random_object_within(region); 115 238 objects.insert( 116 239 format!("{}#{}", name, i), 117 240 ( ··· 131 254 } 132 255 } 133 256 134 - pub fn random_object(&self) -> Object { 135 - let start = self.random_anchor(); 257 + pub fn random_object_within(&self, region: &Region) -> Object { 258 + let start = self.random_anchor(region); 136 259 match rand::thread_rng().gen_range(1..=7) { 137 - 1 => self.random_polygon(), 138 - 2 => Object::BigCircle(self.random_center_anchor()), 260 + 1 => self.random_polygon(region), 261 + 2 => Object::BigCircle(self.random_center_anchor(region)), 139 262 3 => Object::SmallCircle(start), 140 263 4 => Object::Dot(start), 141 - 5 => Object::CurveInward(start, self.random_end_anchor(start)), 142 - 6 => Object::CurveOutward(start, self.random_end_anchor(start)), 143 - 7 => Object::Line(self.random_anchor(), self.random_anchor()), 264 + 5 => Object::CurveInward(start, self.random_end_anchor(start, region)), 265 + 6 => Object::CurveOutward(start, self.random_end_anchor(start, region)), 266 + 7 => Object::Line(self.random_anchor(region), self.random_anchor(region)), 144 267 _ => unreachable!(), 145 268 } 146 269 } 147 270 148 - pub fn random_end_anchor(&self, start: Anchor) -> Anchor { 271 + pub fn random_end_anchor(&self, start: Anchor, region: &Region) -> Anchor { 149 272 // End anchors are always a square diagonal from the start anchor (for now) 150 273 // that means taking steps of the form n * (one of (1, 1), (1, -1), (-1, 1), (-1, -1)) 151 274 // Except that the end anchor needs to stay in the bounds of the shape. 152 275 153 276 // Determine all possible end anchors that are in a square diagonal from the start anchor 154 277 let mut possible_end_anchors = vec![]; 155 - let grid_width = self.grid_size.0 as i32; 156 - let grid_height = self.grid_size.1 as i32; 157 278 158 - for x in -grid_width..=grid_width { 159 - for y in -grid_height..=grid_height { 279 + for x in region.mirrored_width_range() { 280 + for y in region.mirrored_height_range() { 160 281 let end_anchor = Anchor(start.0 + x, start.1 + y); 161 282 162 283 if end_anchor == start { ··· 164 285 } 165 286 166 287 // Check that the end anchor is in a square diagonal from the start anchor and that the end anchor is in bounds 167 - if x.abs() == y.abs() 168 - && end_anchor.0.abs() < grid_width 169 - && end_anchor.1.abs() < grid_height 170 - && end_anchor.0 >= 0 171 - && end_anchor.1 >= 0 172 - { 288 + if x.abs() == y.abs() && region.contains(&end_anchor) { 173 289 possible_end_anchors.push(end_anchor); 174 290 } 175 291 } ··· 179 295 possible_end_anchors[rand::thread_rng().gen_range(0..possible_end_anchors.len())] 180 296 } 181 297 182 - pub fn random_polygon(&self) -> Object { 298 + pub fn random_polygon(&self, region: &Region) -> Object { 183 299 let number_of_anchors = rand::thread_rng().gen_range(self.polygon_vertices_range.clone()); 184 - let start = self.random_anchor(); 185 - let mut lines: Vec<Line> = vec![]; 300 + let start = self.random_anchor(region); 301 + let mut lines: Vec<LineSegment> = vec![]; 186 302 for _ in 0..number_of_anchors { 187 - let next_anchor = self.random_anchor(); 303 + let next_anchor = self.random_anchor(region); 188 304 lines.push(self.random_line(next_anchor)); 189 305 } 190 306 Object::Polygon(start, lines) 191 307 } 192 308 193 - pub fn random_line(&self, end: Anchor) -> Line { 309 + pub fn random_line(&self, end: Anchor) -> LineSegment { 194 310 match rand::thread_rng().gen_range(1..=3) { 195 - 1 => Line::Line(end), 196 - 2 => Line::InwardCurve(end), 197 - 3 => Line::OutwardCurve(end), 311 + 1 => LineSegment::Straight(end), 312 + 2 => LineSegment::InwardCurve(end), 313 + 3 => LineSegment::OutwardCurve(end), 198 314 _ => unreachable!(), 199 315 } 200 316 } 201 317 202 - pub fn random_anchor(&self) -> Anchor { 203 - if rand::thread_rng().gen_bool(1.0 / (self.grid_size.0 * self.grid_size.1) as f64) { 318 + pub fn region_is_whole_grid(&self, region: &Region) -> bool { 319 + region.start == (0, 0) && region.end == self.grid_size 320 + } 321 + 322 + pub fn random_anchor(&self, region: &Region) -> Anchor { 323 + if self.region_is_whole_grid(region) 324 + && rand::thread_rng().gen_bool(1.0 / (self.grid_size.0 * self.grid_size.1) as f64) 325 + { 204 326 // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 4x4) 205 327 Anchor(-1, -1) 206 328 } else { 207 329 Anchor( 208 - rand::thread_rng().gen_range(0..=self.grid_size.0 - 1) as i32, 209 - rand::thread_rng().gen_range(0..=self.grid_size.1 - 1) as i32, 330 + rand::thread_rng().gen_range(region.x_range()) as i32, 331 + rand::thread_rng().gen_range(region.y_range()) as i32, 210 332 ) 211 333 } 212 334 } 213 335 214 - pub fn random_center_anchor(&self) -> CenterAnchor { 215 - if rand::thread_rng() 216 - .gen_bool(1.0 / ((self.grid_size.0 as i32 - 1) * (self.grid_size.1 as i32 - 1)) as f64) 336 + pub fn random_center_anchor(&self, region: &Region) -> CenterAnchor { 337 + if self.region_is_whole_grid(region) 338 + && rand::thread_rng().gen_bool( 339 + 1.0 / ((self.grid_size.0 as i32 - 1) * (self.grid_size.1 as i32 - 1)) as f64, 340 + ) 217 341 { 218 342 // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 3x3) 219 343 CenterAnchor(-1, -1) 220 344 } else { 221 345 CenterAnchor( 222 - rand::thread_rng().gen_range(0..=self.grid_size.0 - 2) as i32, 223 - rand::thread_rng().gen_range(0..=self.grid_size.1 - 2) as i32, 346 + rand::thread_rng().gen_range(region.x_range_without_last()) as i32, 347 + rand::thread_rng().gen_range(region.y_range_without_last()) as i32, 224 348 ) 225 349 } 226 350 } ··· 301 425 } 302 426 303 427 pub fn render(&mut self, layers: &Vec<&str>, render_background: bool) -> String { 304 - let background_color = self.background.unwrap_or(Color::default()); 428 + let background_color = self.background.unwrap_or_default(); 305 429 let mut svg = svg::Document::new(); 306 430 if render_background { 307 431 svg = svg.add( ··· 382 506 383 507 #[derive(Debug, Clone)] 384 508 pub enum Object { 385 - Polygon(Anchor, Vec<Line>), 509 + Polygon(Anchor, Vec<LineSegment>), 386 510 Line(Anchor, Anchor), 387 511 CurveOutward(Anchor, Anchor), 388 512 CurveInward(Anchor, Anchor), ··· 440 564 } 441 565 442 566 #[derive(Debug, Clone, PartialEq, Eq)] 443 - pub enum Line { 444 - Line(Anchor), 567 + pub enum LineSegment { 568 + Straight(Anchor), 445 569 InwardCurve(Anchor), 446 570 OutwardCurve(Anchor), 447 571 }
+6 -6
src/cli.rs
··· 1 1 use docopt::Docopt; 2 2 use serde::Deserialize; 3 - use serde_json; 3 + 4 4 use shapemaker::{Canvas, ColorMapping}; 5 5 use std::collections::HashMap; 6 6 use std::fs::File; 7 7 use std::io::BufReader; 8 8 9 - const USAGE: &'static str = " 9 + const USAGE: &str = " 10 10 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 11 █░▄▄█░████░▄▄▀█▀▄▄▀█░▄▄█░▄▀▄░█░▄▄▀█░█▀█░▄▄█░▄▄▀█ 12 12 █▄▄▀█░▄▄░█░▀▀░█░▀▀░█░▄▄█░█▄█░█░▀▀░█░▄▀█░▄▄█░▀▀▄█ ··· 63 63 } 64 64 65 65 let mut canvas = Canvas::new(vec![]); 66 - canvas.colormap = load_colormap(&args); 67 - set_canvas_settings_from_args(&args, &mut canvas); 66 + canvas.colormap = load_colormap(args); 67 + set_canvas_settings_from_args(args, &mut canvas); 68 68 canvas 69 69 } 70 70 ··· 98 98 let mut split = dimensions.split('x'); 99 99 let width = split.next().unwrap().parse::<usize>().unwrap(); 100 100 let height = split.next().unwrap().parse::<usize>().unwrap(); 101 - canvas.grid_size = (width, height); 101 + canvas.set_grid_size(width, height); 102 102 } 103 103 if let Some(cell_size) = args.flag_cell_size { 104 104 canvas.cell_size = cell_size; ··· 142 142 } else { 143 143 let mut colormap: HashMap<String, String> = HashMap::new(); 144 144 for mapping in &args.flag_color { 145 - if !mapping.contains(":") { 145 + if !mapping.contains(':') { 146 146 println!("Invalid color mapping: {}", mapping); 147 147 std::process::exit(1); 148 148 }
+14 -2
src/layer.rs
··· 1 1 use std::collections::HashMap; 2 2 3 - use crate::canvas::{Color, ColorMapping, Coordinates, Fill, Line, Object, ObjectSizes}; 3 + use crate::canvas::{Color, ColorMapping, Coordinates, Fill, LineSegment, Object, ObjectSizes}; 4 4 5 5 #[derive(Debug, Clone, Default)] 6 6 pub struct Layer { ··· 16 16 name: name.to_string(), 17 17 _render_cache: None, 18 18 } 19 + } 20 + 21 + pub fn replace(&mut self, with: Layer) -> () { 22 + self.objects = with.objects.clone(); 23 + self._render_cache = None; 24 + } 25 + 26 + pub fn paint_all_objects(&mut self, fill: Fill) { 27 + for (_id, (_, maybe_fill)) in &mut self.objects { 28 + *maybe_fill = Some(fill.clone()); 29 + } 30 + self._render_cache = None; 19 31 } 20 32 21 33 pub fn add_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { ··· 108 120 path = path.move_to(start.coords(cell_size)); 109 121 for line in lines { 110 122 path = match line { 111 - Line::Line(end) | Line::InwardCurve(end) | Line::OutwardCurve(end) => { 123 + LineSegment::Straight(end) | LineSegment::InwardCurve(end) | LineSegment::OutwardCurve(end) => { 112 124 path.line_to(end.coords(cell_size)) 113 125 } 114 126 };
+19 -13
src/lib.rs
··· 20 20 use std::thread::{self, JoinHandle}; 21 21 use std::time; 22 22 23 - const PROGRESS_BARS_STYLE: &'static str = 23 + const PROGRESS_BARS_STYLE: &str = 24 24 "{spinner:.cyan} {percent:03.bold.cyan}% {msg:<30} [{bar:100.bold.blue/dim.blue}] {eta:.cyan}"; 25 25 26 26 pub type RenderFunction<C> = dyn Fn(&mut Canvas, &mut Context<C>); ··· 128 128 } 129 129 } 130 130 131 - pub fn dump_stems(&self, to: PathBuf) -> () { 131 + pub fn dump_stems(&self, to: PathBuf) { 132 132 std::fs::create_dir_all(&to); 133 133 for (name, stem) in self.syncdata.stems.iter() { 134 134 fs::write(to.join(name), format!("{:?}", stem)); ··· 226 226 227 227 Self { 228 228 spinner: spinner.clone(), 229 - finished: finished, 229 + finished, 230 230 thread: spinner_thread, 231 231 } 232 232 } ··· 237 237 } 238 238 } 239 239 240 + impl<AdditionalContext: Default> Default for Video<AdditionalContext> { 241 + fn default() -> Self { 242 + Self::new() 243 + } 244 + } 245 + 240 246 impl<AdditionalContext: Default> Video<AdditionalContext> { 241 247 pub fn new() -> Self { 242 248 Self { ··· 433 439 let mut hooks = self.hooks; 434 440 hooks.push(Hook { 435 441 when: Box::new(move |_, ctx, _, _| { 436 - for stem_name in stems.split(",") { 442 + for stem_name in stems.split(',').map(|s| s.trim()) { 437 443 let stem = ctx.stem(stem_name); 438 444 if stem.notes.iter().any(|note| note.is_on()) { 439 445 return true; 440 446 } 441 447 } 442 - return false; 448 + false 443 449 }), 444 450 render_function: Box::new(render_function), 445 451 }); ··· 455 461 let mut hooks = self.hooks; 456 462 hooks.push(Hook { 457 463 when: Box::new(move |_, ctx, _, _| { 458 - for stem_name in stems.split(",") { 464 + for stem_name in stems.split(',') { 459 465 let stem = ctx.stem(stem_name); 460 466 if stem.notes.iter().any(|note| note.is_off()) { 461 467 return true; 462 468 } 463 469 } 464 - return false; 470 + false 465 471 }), 466 472 render_function: Box::new(render_function), 467 473 }); ··· 484 490 hooks.push(Hook { 485 491 when: Box::new(move |_, ctx, _, _| { 486 492 stems 487 - .split(",") 493 + .split(',') 488 494 .any(|stem_name| ctx.stem(stem_name).notes.iter().any(|note| note.is_on())) 489 495 }), 490 496 render_function: Box::new(move |canvas, ctx| { ··· 494 500 }); 495 501 hooks.push(Hook { 496 502 when: Box::new(move |_, ctx, _, _| { 497 - stems.split(",").any(|stem_name| { 503 + stems.split(',').any(|stem_name| { 498 504 ctx.stem(stem_name).amplitude_relative() < cutoff_amplitude 499 505 || ctx.stem(stem_name).notes.iter().any(|note| note.is_off()) 500 506 }) ··· 686 692 progress_bar.set_message("Rendering frames to SVG"); 687 693 688 694 for _ in 0..self.duration_ms() { 689 - context.ms += 1 as usize; 690 - context.timestamp = format!("{}", milliseconds_to_timestamp(context.ms)); 695 + context.ms += 1_usize; 696 + context.timestamp = milliseconds_to_timestamp(context.ms).to_string(); 691 697 context.beat_fractional = (context.bpm * context.ms) as f32 / (1000.0 * 60.0); 692 698 context.beat = context.beat_fractional as usize; 693 699 context.frame = ((self.fps * context.ms) as f64 / 1000.0) as usize; ··· 700 706 )); 701 707 } 702 708 703 - if context.marker().starts_with(":") { 709 + if context.marker().starts_with(':') { 704 710 let marker_text = context.marker(); 705 - let commandline = marker_text.trim_start_matches(":").to_string(); 711 + let commandline = marker_text.trim_start_matches(':').to_string(); 706 712 707 713 for command in &self.commands { 708 714 if commandline.starts_with(&command.name) {
+49 -31
src/main.rs
··· 1 - use shapemaker::{Anchor, Canvas, CenterAnchor, Color, Fill, Object, Video}; 1 + use shapemaker::{Canvas, Color, Fill, Layer, Object, Region, Video}; 2 2 mod cli; 3 3 pub use cli::{canvas_from_cli, cli_args}; 4 4 ··· 7 7 let mut canvas = canvas_from_cli(&args); 8 8 9 9 if args.cmd_image && !args.cmd_video { 10 - canvas.layers.push(canvas.random_layer("main")); 10 + canvas.layers.push(canvas.random_layer("root")); 11 11 canvas.set_background(Color::White); 12 12 let aspect_ratio = canvas.grid_size.0 as f32 / canvas.grid_size.1 as f32; 13 13 match Canvas::save_as_png( ··· 22 22 return; 23 23 } 24 24 25 - Video::<(Anchor, CenterAnchor, Color, Color)>::new() 25 + Video::<State>::new() 26 26 .set_fps(args.flag_fps.unwrap_or(30)) 27 27 .set_initial_canvas(canvas) 28 28 .init(&|canvas: _, context: _| { 29 - context.extra = ( 30 - canvas.random_anchor(), 31 - canvas.random_center_anchor(), 32 - canvas.random_color(), 33 - canvas.random_color(), 34 - ); 35 - canvas.set_background(context.extra.3); 29 + context.extra = State { 30 + kick_region: Region::from_origin((3, 3)), 31 + background: Color::Black, 32 + }; 33 + canvas.set_background(context.extra.background); 36 34 }) 37 35 .set_audio(args.flag_audio.unwrap().into()) 38 36 .sync_audio_with(&args.flag_sync_with.unwrap()) 39 - .on_stem( 40 - "bass", 41 - 0.7, 42 - &|canvas, _| { 43 - let mut layer = canvas.random_layer("root"); 44 - for obj in layer.objects.iter_mut() { 45 - if let Some(_) = obj.1 .1 { 46 - obj.1 .1 = Some(Fill::Solid(Color::Black)) 47 - } 48 - } 49 - canvas.layers[0] = layer; 50 - }, 51 - &|_, _| {}, 52 - ) 53 - .on_stem( 54 - "anchor kick", 55 - 0.7, 56 - &|canvas, _| canvas.set_background(color_cycle(canvas.background.unwrap())), 57 - &|_, _| {}, 58 - ) 37 + .on_note("bass", &|canvas, ctx| { 38 + let mut new_layer = canvas.random_layer_within("bass", &ctx.extra.kick_region); 39 + new_layer.paint_all_objects(Fill::Solid(Color::White)); 40 + canvas.replace_or_create_layer("bass", new_layer); 41 + }) 42 + .on_note("anchor kick", &|canvas, ctx| { 43 + ctx.extra.kick_region = region_cycle(&canvas.world_region, &ctx.extra.kick_region) 44 + }) 45 + .on_note("powerful clap hit, clap", &|canvas, ctx| { 46 + let mut new_layer = canvas.random_layer_within( 47 + "claps", 48 + &region_cycle(&canvas.world_region, &ctx.extra.kick_region), 49 + ); 50 + new_layer.paint_all_objects(Fill::Solid(Color::Red)); 51 + canvas.replace_or_create_layer("claps", new_layer) 52 + }) 59 53 // .on_stem( 60 54 // "bass", 61 55 // 0.7, ··· 77 71 "clap", 78 72 0.7, 79 73 &|canvas, _| { 80 - let polygon = canvas.random_polygon(); 74 + let polygon = canvas.random_polygon(&canvas.world_region); 81 75 let fill = Some(Fill::Solid(canvas.random_color())); 82 76 canvas.root().add_object("clap", polygon, fill); 83 77 }, ··· 101 95 .unwrap(); 102 96 } 103 97 98 + #[derive(Default)] 99 + struct State { 100 + kick_region: Region, 101 + background: Color, 102 + } 103 + 104 104 fn color_cycle(current_color: Color) -> Color { 105 105 match current_color { 106 106 Color::Blue => Color::Cyan, ··· 115 115 _ => unreachable!(), 116 116 } 117 117 } 118 + 119 + fn region_cycle(world: &Region, current: &Region) -> Region { 120 + let size = (current.width(), current.height()); 121 + let mut new_region = current.clone(); 122 + // Move along x axis if possible 123 + if current.end.0 + size.0 <= world.end.0 { 124 + new_region.translate(size.0 as i32, 0) 125 + } 126 + // Else go to x=0 and move along y axis 127 + else if current.end.1 + size.1 <= world.end.1 { 128 + new_region = Region::new(0, current.end.1, size.0, current.end.1 + size.1) 129 + } 130 + // Else go to origin 131 + else { 132 + new_region = Region::from_origin(size) 133 + } 134 + new_region 135 + }
+1 -1
src/midi.rs
··· 19 19 } 20 20 21 21 fn is_kick_channel(name: &str) -> bool { 22 - return name.contains("kick"); 22 + name.contains("kick") 23 23 } 24 24 25 25 impl Syncable for MidiSynchronizer {