this repo has no description
3
fork

Configure Feed

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

✨ Add --start, continue clip

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
9fc5444d 1168e513

+292 -112
+172 -56
src/canvas.rs
··· 1 + use core::panic; 1 2 use std::{ 3 + cmp, 2 4 collections::HashMap, 3 5 io::Write, 4 6 ops::{Range, RangeInclusive}, ··· 80 82 } 81 83 } 82 84 85 + pub fn random_coordinates_within(&self) -> (i32, i32) { 86 + ( 87 + rand::thread_rng().gen_range(self.start.0..self.end.0) as i32, 88 + rand::thread_rng().gen_range(self.start.1..self.end.1) as i32, 89 + ) 90 + } 91 + 83 92 pub fn from_origin(end: (usize, usize)) -> Self { 84 93 Self::new(0, 0, end.0, end.1) 85 94 } ··· 132 141 .ensure_valid() 133 142 } 134 143 144 + /// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors) 145 + pub fn enlarged(&self, dx: i32, dy: i32) -> Self { 146 + Self { 147 + start: self.start, 148 + end: ( 149 + (self.end.0 as i32 + dx) as usize, 150 + (self.end.1 as i32 + dy) as usize, 151 + ), 152 + } 153 + .ensure_valid() 154 + } 155 + 156 + /// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy) 157 + pub fn resized(&self, dx: i32, dy: i32) -> Self { 158 + self.translated(-dx, -dy).enlarged(dx, dy) 159 + } 160 + 135 161 pub fn x_range(&self) -> Range<usize> { 136 162 self.start.0..self.end.0 137 163 } ··· 197 223 pub polygon_vertices_range: Range<usize>, 198 224 pub canvas_outter_padding: usize, 199 225 pub object_sizes: ObjectSizes, 200 - pub render_grid: bool, 201 226 pub colormap: ColorMapping, 202 227 /// The layers are in order of top to bottom: the first layer will be rendered on top of the second, etc. 203 228 pub layers: Vec<Layer>, ··· 219 244 layers: layer_names 220 245 .iter() 221 246 .map(|name| Layer { 247 + object_sizes: ObjectSizes::default(), 222 248 objects: HashMap::new(), 223 249 name: name.to_string(), 224 250 _render_cache: None, ··· 236 262 }; 237 263 } 238 264 239 - pub fn layer(&mut self, name: &str) -> Option<&mut Layer> { 265 + pub fn layer_safe(&mut self, name: &str) -> Option<&mut Layer> { 240 266 self.layers.iter_mut().find(|layer| layer.name == name) 241 267 } 242 268 269 + pub fn layer(&mut self, name: &str) -> &mut Layer { 270 + self.layer_safe(name).unwrap() 271 + } 272 + 273 + pub fn ensure_layer_exists(&self, name: &str) { 274 + self.layers 275 + .iter() 276 + .find(|layer| layer.name == name) 277 + .or_else(|| panic!("Layer {} does not exist", name)); 278 + } 279 + 280 + /// puts this layer on top, and the others below, without changing their order 281 + pub fn put_layer_on_top(&mut self, name: &str) { 282 + self.ensure_layer_exists(name); 283 + self.layers.sort_by(|a, _| { 284 + if a.name == name { 285 + cmp::Ordering::Greater 286 + } else { 287 + cmp::Ordering::Less 288 + } 289 + }) 290 + } 291 + 292 + /// puts this layer on bottom, and the others above, without changing their order 293 + pub fn put_layer_on_bottom(&mut self, name: &str) { 294 + self.ensure_layer_exists(name); 295 + self.layers.sort_by(|a, _| { 296 + if a.name == name { 297 + cmp::Ordering::Less 298 + } else { 299 + cmp::Ordering::Greater 300 + } 301 + }) 302 + } 303 + 243 304 pub fn root(&mut self) -> &mut Layer { 244 - self.layer("root") 305 + self.layer_safe("root") 245 306 .expect("Layer 'root' should always exist in a canvas") 246 307 } 247 308 ··· 252 313 object: Object, 253 314 fill: Option<Fill>, 254 315 ) -> Result<(), String> { 255 - match self.layer(layer) { 316 + match self.layer_safe(layer) { 256 317 None => Err(format!("Layer {} does not exist", layer)), 257 318 Some(layer) => { 258 319 layer.objects.insert(name.to_string(), (object, fill)); ··· 282 343 objects_count_range: 3..7, 283 344 polygon_vertices_range: 2..7, 284 345 canvas_outter_padding: 10, 285 - object_sizes: ObjectSizes { 286 - line_width: 2.0, 287 - empty_shape_stroke_width: 0.5, 288 - small_circle_radius: 5.0, 289 - dot_radius: 2.0, 290 - font_size: 20.0, 291 - }, 292 - render_grid: false, 346 + object_sizes: ObjectSizes::default(), 293 347 colormap: ColorMapping::default(), 294 348 layers: vec![], 295 349 world_region: Region::new(0, 0, 3, 3), ··· 306 360 } 307 361 308 362 pub fn replace_or_create_layer(&mut self, layer: Layer) { 309 - if let Some(existing_layer) = self.layer(&layer.name) { 363 + if let Some(existing_layer) = self.layer_safe(&layer.name) { 310 364 existing_layer.replace(layer); 311 365 } else { 312 366 self.layers.push(layer); ··· 331 385 ); 332 386 } 333 387 Layer { 388 + object_sizes: self.object_sizes.clone(), 334 389 name: name.to_string(), 335 390 objects, 336 391 _render_cache: None, 337 392 } 338 393 } 339 394 395 + pub fn random_linelikes_within(&self, layer_name: &'static str, region: &Region) -> Layer { 396 + let mut objects: HashMap<String, (Object, Option<Fill>)> = HashMap::new(); 397 + let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 398 + for i in 0..number_of_objects { 399 + let object = self.random_linelike_within(region); 400 + objects.insert( 401 + format!("{}#{}", layer_name, i), 402 + ( 403 + object, 404 + if rand::thread_rng().gen_bool(0.5) { 405 + Some(self.random_fill()) 406 + } else { 407 + None 408 + }, 409 + ), 410 + ); 411 + } 412 + Layer { 413 + object_sizes: self.object_sizes.clone(), 414 + name: layer_name.to_owned(), 415 + objects, 416 + _render_cache: None, 417 + } 418 + } 419 + 340 420 pub fn random_object_within(&self, region: &Region) -> Object { 341 421 let start = self.random_anchor(region); 342 422 match rand::thread_rng().gen_range(1..=7) { ··· 344 424 2 => Object::BigCircle(self.random_center_anchor(region)), 345 425 3 => Object::SmallCircle(start), 346 426 4 => Object::Dot(start), 347 - 5 => Object::CurveInward(start, self.random_end_anchor(start, region)), 348 - 6 => Object::CurveOutward(start, self.random_end_anchor(start, region)), 349 - 7 => Object::Line(self.random_anchor(region), self.random_anchor(region)), 427 + 5 => Object::CurveInward( 428 + start, 429 + self.random_end_anchor(start, region), 430 + self.object_sizes.default_line_width, 431 + ), 432 + 6 => Object::CurveOutward( 433 + start, 434 + self.random_end_anchor(start, region), 435 + self.object_sizes.default_line_width, 436 + ), 437 + 7 => Object::Line( 438 + self.random_anchor(region), 439 + self.random_anchor(region), 440 + self.object_sizes.default_line_width, 441 + ), 442 + _ => unreachable!(), 443 + } 444 + } 445 + 446 + pub fn random_linelike_within(&self, region: &Region) -> Object { 447 + let start = self.random_anchor(region); 448 + match rand::thread_rng().gen_range(1..=3) { 449 + 1 => Object::CurveInward( 450 + start, 451 + self.random_end_anchor(start, region), 452 + self.object_sizes.default_line_width, 453 + ), 454 + 2 => Object::CurveOutward( 455 + start, 456 + self.random_end_anchor(start, region), 457 + self.object_sizes.default_line_width, 458 + ), 459 + 3 => Object::Line( 460 + self.random_anchor(region), 461 + self.random_anchor(region), 462 + self.object_sizes.default_line_width, 463 + ), 350 464 _ => unreachable!(), 351 465 } 352 466 } ··· 526 640 .filter(|layer| layers.contains(&"*") || layers.contains(&layer.name.as_str())) 527 641 .rev() 528 642 { 529 - svg = svg.add(layer.render(self.colormap.clone(), self.cell_size, self.object_sizes)); 643 + svg = svg.add(layer.render(self.colormap.clone(), self.cell_size, layer.object_sizes)); 530 644 } 531 - // render a dotted grid 532 - if self.render_grid { 533 - for i in 0..self.grid_size.0 as i32 { 534 - for j in 0..self.grid_size.1 as i32 { 535 - let (x, y) = Anchor(i, j).coords(self.cell_size); 536 - svg = svg.add( 537 - svg::node::element::Circle::new() 538 - .set("cx", x) 539 - .set("cy", y) 540 - .set("r", self.object_sizes.line_width / 4.0) 541 - .set("fill", "#000"), 542 - ); 543 645 544 - // if i < canvas.grid_size.0 as i32 - 1 && j < canvas.grid_size.1 as i32 - 1 { 545 - // let (x, y) = CenterAnchor(i, j).coords(&canvas); 546 - // svg = svg.add( 547 - // svg::node::element::Circle::new() 548 - // .set("cx", x) 549 - // .set("cy", y) 550 - // .set("r", canvas.line_width / 4.0) 551 - // .set("fill", "#fff"), 552 - // ); 553 - // } 554 - } 555 - } 556 - } 557 646 svg.set( 558 647 "viewBox", 559 648 format!( ··· 583 672 pub empty_shape_stroke_width: f32, 584 673 pub small_circle_radius: f32, 585 674 pub dot_radius: f32, 586 - pub line_width: f32, 587 - pub font_size: f32, 675 + pub default_line_width: f32, 676 + } 677 + 678 + impl Default for ObjectSizes { 679 + fn default() -> Self { 680 + Self { 681 + empty_shape_stroke_width: 0.5, 682 + small_circle_radius: 5.0, 683 + dot_radius: 2.0, 684 + default_line_width: 2.0, 685 + } 686 + } 588 687 } 589 688 590 689 #[derive(Debug, Clone)] 591 690 pub enum Object { 592 691 Polygon(Anchor, Vec<LineSegment>), 593 - Line(Anchor, Anchor), 594 - CurveOutward(Anchor, Anchor), 595 - CurveInward(Anchor, Anchor), 692 + Line(Anchor, Anchor, f32), 693 + CurveOutward(Anchor, Anchor, f32), 694 + CurveInward(Anchor, Anchor, f32), 596 695 SmallCircle(Anchor), 597 696 Dot(Anchor), 598 697 BigCircle(CenterAnchor), 599 - Text(Anchor, String), 698 + Text(Anchor, String, f32), 600 699 Rectangle(Anchor, Anchor), 601 700 RawSVG(Box<dyn svg::Node>), 602 701 } ··· 614 713 } 615 714 } 616 715 } 617 - Object::Line(start, end) 618 - | Object::CurveInward(start, end) 619 - | Object::CurveOutward(start, end) 716 + Object::Line(start, end, _) 717 + | Object::CurveInward(start, end, _) 718 + | Object::CurveOutward(start, end, _) 620 719 | Object::Rectangle(start, end) => { 621 720 start.translate(dx, dy); 622 721 end.translate(dx, dy); 623 722 } 624 - Object::Text(anchor, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 723 + Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 625 724 anchor.translate(dx, dy) 626 725 } 627 726 Object::BigCircle(center) => center.translate(dx, dy), ··· 633 732 634 733 pub fn translate_with(&mut self, delta: (i32, i32)) { 635 734 self.translate(delta.0, delta.1) 735 + } 736 + 737 + pub fn teleport(&mut self, x: i32, y: i32) { 738 + let (current_x, current_y) = self.region().start; 739 + let delta_x = x - current_x as i32; 740 + let delta_y = y - current_y as i32; 741 + self.translate(delta_x, delta_y); 742 + } 743 + 744 + pub fn teleport_with(&mut self, position: (i32, i32)) { 745 + self.teleport(position.0, position.1) 636 746 } 637 747 638 748 pub fn region(&self) -> Region { ··· 650 760 } 651 761 region 652 762 } 653 - Object::Line(start, end) 654 - | Object::CurveInward(start, end) 655 - | Object::CurveOutward(start, end) 763 + Object::Line(start, end, _) 764 + | Object::CurveInward(start, end, _) 765 + | Object::CurveOutward(start, end, _) 656 766 | Object::Rectangle(start, end) => (start, end).into(), 657 - Object::Text(anchor, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 767 + Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 658 768 (anchor, anchor).into() 659 769 } 660 770 Object::BigCircle(center) => (center, center).into(), // FIXME will be wrong lmao, ··· 672 782 pub fn translate(&mut self, dx: i32, dy: i32) { 673 783 self.0 += dx; 674 784 self.1 += dy; 785 + } 786 + } 787 + 788 + impl From<(i32, i32)> for Anchor { 789 + fn from(value: (i32, i32)) -> Self { 790 + Anchor(value.0, value.1) 675 791 } 676 792 } 677 793
+3 -2
src/cli.rs
··· 37 37 --fps <fps> Frames per second [default: 30] 38 38 --audio <file> Audio file to use for the video 39 39 --duration <seconds> Number of seconds to render. If not set, the video will be as long as the audio file. 40 + --start <seconds> Start the video at this time in seconds. [default: 0] 40 41 --preview Only create preview.html, not the output video. Preview.html will be created in the same directory as <file>, but <file> will not be created. 41 42 --sync-with <directory> Directory containing the audio files to sync to. 42 43 The directory must contain: ··· 92 93 pub flag_resolution: Option<usize>, 93 94 pub flag_workers: Option<usize>, 94 95 pub flag_duration: Option<usize>, 96 + pub flag_start: Option<usize>, 95 97 pub flag_preview: bool, 96 98 } 97 99 ··· 109 111 canvas.canvas_outter_padding = canvas_padding; 110 112 } 111 113 if let Some(line_width) = args.flag_line_width { 112 - canvas.object_sizes.line_width = line_width; 114 + canvas.object_sizes.default_line_width = line_width; 113 115 } 114 116 if let Some(small_circle_radius) = args.flag_small_circle_radius { 115 117 canvas.object_sizes.small_circle_radius = small_circle_radius; ··· 120 122 if let Some(empty_shape_stroke) = args.flag_empty_shape_stroke { 121 123 canvas.object_sizes.empty_shape_stroke_width = empty_shape_stroke; 122 124 } 123 - canvas.render_grid = args.flag_render_grid; 124 125 if let Some(objects_count) = &args.flag_objects_count { 125 126 let mut split = objects_count.split(".."); 126 127 let min = split.next().unwrap().parse::<usize>().unwrap();
+39 -16
src/layer.rs
··· 1 1 use std::collections::HashMap; 2 2 3 + use chrono::format; 4 + 3 5 use crate::{ 4 6 canvas::{Coordinates, Fill, LineSegment, Object, ObjectSizes}, 5 7 Color, ColorMapping, ··· 7 9 8 10 #[derive(Debug, Clone, Default)] 9 11 pub struct Layer { 12 + pub object_sizes: ObjectSizes, 10 13 pub objects: HashMap<String, (Object, Option<Fill>)>, 11 14 pub name: String, 12 15 pub _render_cache: Option<svg::node::element::Group>, ··· 15 18 impl Layer { 16 19 pub fn new(name: &str) -> Self { 17 20 Layer { 21 + object_sizes: ObjectSizes::default(), 18 22 objects: HashMap::new(), 19 23 name: name.to_string(), 20 24 _render_cache: None, 21 25 } 22 26 } 23 27 28 + pub fn object(&mut self, name: &str) -> &mut (Object, Option<Fill>) { 29 + self.objects.get_mut(name).unwrap() 30 + } 31 + 24 32 // Flush the render cache. 25 33 pub fn flush(&mut self) -> () { 26 34 self._render_cache = None; ··· 28 36 29 37 pub fn replace(&mut self, with: Layer) -> () { 30 38 self.objects = with.objects.clone(); 31 - self._render_cache = None; 39 + self.flush(); 32 40 } 33 41 34 42 pub fn paint_all_objects(&mut self, fill: Fill) { 35 43 for (_id, (_, maybe_fill)) in &mut self.objects { 36 44 *maybe_fill = Some(fill.clone()); 37 45 } 38 - self._render_cache = None; 46 + self.flush(); 39 47 } 40 48 41 49 pub fn move_all_objects(&mut self, dx: i32, dy: i32) { 42 50 self.objects 43 51 .iter_mut() 44 52 .for_each(|(_, (obj, _))| obj.translate(dx, dy)); 45 - self._render_cache = None; 53 + self.flush(); 46 54 } 47 55 48 56 pub fn add_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { 49 57 self.objects.insert(name.to_string(), (object, fill)); 50 - self._render_cache = None; 58 + self.flush(); 51 59 } 52 60 53 61 pub fn remove_object(&mut self, name: &str) { 54 62 self.objects.remove(name); 55 - self._render_cache = None; 63 + self.flush(); 64 + } 65 + 66 + pub fn replace_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { 67 + self.remove_object(name); 68 + self.add_object(name, object, fill); 56 69 } 57 70 58 71 /// Render the layer to a SVG group element. ··· 78 91 // eprintln!("render: raw_svg [{}]", id); 79 92 group = group.add(svg.clone()); 80 93 } 81 - Object::Text(position, content) => { 94 + Object::Text(position, content, font_size) => { 82 95 group = group.add( 83 96 svg::node::element::Text::new(content.clone()) 84 97 .set("x", position.coords(cell_size).0) 85 98 .set("y", position.coords(cell_size).1) 86 - .set("font-size", format!("{}pt", object_sizes.font_size)) 99 + .set("font-size", format!("{}pt", font_size)) 87 100 .set("font-family", "sans-serif") 88 101 .set("text-anchor", "middle") 89 102 .set("dominant-baseline", "middle") ··· 165 178 }, 166 179 ); 167 180 } 168 - Object::Line(start, end) => { 181 + Object::Line(start, end, line_width) => { 169 182 // eprintln!("render: line({:?}, {:?}) [{}]", start, end, id); 170 183 group = group.add( 171 184 svg::node::element::Line::new() ··· 179 192 // TODO 180 193 Some(Fill::Solid(color)) => { 181 194 format!( 182 - "fill: none; stroke: {}; stroke-width: 2px;", 183 - color.to_string(&colormap) 195 + "fill: none; stroke: {}; stroke-width: {}px;", 196 + color.to_string(&colormap), 197 + line_width 184 198 ) 185 199 } 186 200 _ => format!( 187 - "fill: none; stroke: {}; stroke-width: 2px;", 188 - default_color 201 + "fill: none; stroke: {}; stroke-width: {}px;", 202 + default_color, line_width 189 203 ), 190 204 }, 191 205 ), 192 206 ); 193 207 } 194 - Object::CurveInward(start, end) | Object::CurveOutward(start, end) => { 195 - let inward = if matches!(object, Object::CurveInward(_, _)) { 208 + Object::CurveInward(start, end, line_width) 209 + | Object::CurveOutward(start, end, line_width) => { 210 + let inward = if matches!(object, Object::CurveInward(_, _, _)) { 196 211 // eprintln!("render: curve_inward({:?}, {:?}) [{}]", start, end, id); 197 212 true 198 213 } else { ··· 283 298 format!( 284 299 "fill: none; stroke: {}; stroke-width: {}px;", 285 300 color.to_string(&colormap), 286 - object_sizes.line_width 301 + line_width 302 + ) 303 + } 304 + Some(Fill::Translucent(color, opacity)) => { 305 + format!( 306 + "fill: none; stroke: {}; stroke-width: {}px; opacity: {}", 307 + color.to_string(&colormap), 308 + line_width, 309 + opacity 287 310 ) 288 311 } 289 312 _ => format!( 290 313 "fill: none; stroke: {}; stroke-width: {}px;", 291 - default_color, object_sizes.line_width 314 + default_color, object_sizes.default_line_width 292 315 ), 293 316 }, 294 317 ),
+22 -10
src/lib.rs
··· 52 52 pub audiofile: PathBuf, 53 53 pub resolution: usize, 54 54 pub duration_override: Option<usize>, 55 + pub start_rendering_at: usize, 55 56 } 56 57 57 58 pub struct Hook<C> { ··· 259 260 syncdata: SyncData::default(), 260 261 audiofile: PathBuf::new(), 261 262 duration_override: None, 263 + start_rendering_at: 0, 262 264 } 263 265 } 264 266 ··· 279 281 command 280 282 .args(["-hide_banner", "-loglevel", "error"]) 281 283 .args(["-framerate", &self.fps.to_string()]) 282 - // .args(["-pattern_type", "glob"]) // not available on Windows 284 + .args(["-pattern_type", "glob"]) // not available on Windows 283 285 .args([ 284 286 "-i", 285 287 &format!( 286 - "{}/%0{}d.png", 288 + "{}/*.png", 287 289 self.frames_output_directory, 288 - self.total_frames().to_string().len() 290 + // self.total_frames().to_string().len() 289 291 ), 290 292 ]) 293 + .args([ 294 + "-ss", 295 + &format!("{}", self.start_rendering_at as f32 / 1000.0), 296 + ]) 291 297 .args(["-i", self.audiofile.to_str().unwrap()]) 292 298 .args(["-t", &format!("{}", self.duration_ms() as f32 / 1000.0)]) 293 299 .args(["-c:v", "libx264"]) ··· 604 610 } 605 611 606 612 pub fn total_frames(&self) -> usize { 607 - self.fps * self.duration_ms() / 1000 613 + self.fps * (self.duration_ms() + self.start_rendering_at) / 1000 608 614 } 609 615 610 616 pub fn duration_ms(&self) -> usize { ··· 688 694 let mut previous_rendered_frame = 0; 689 695 let mut frames_to_write: Vec<(String, usize, usize)> = vec![]; 690 696 691 - for _ in 0..self.duration_ms() { 697 + for _ in 0..self.duration_ms() + self.start_rendering_at { 692 698 context.ms += 1_usize; 693 699 context.timestamp = milliseconds_to_timestamp(context.ms).to_string(); 694 700 context.beat_fractional = (context.bpm * context.ms) as f32 / (1000.0 * 60.0); ··· 792 798 progress_bar.set_message("Rendering frames to SVG"); 793 799 794 800 for (frame, no, ms) in self.render_frames(&progress_bar, composition, render_background) { 795 - std::fs::write( 796 - format!("{}/{}.svg", self.frames_output_directory, no), 797 - &frame, 798 - ); 799 801 frames_to_write.push((frame, no, ms)); 800 802 } 801 803 802 - progress_bar.println("Rendered frames to SVG"); 804 + progress_bar.println(format!("Rendered {} frames to SVG", frames_to_write.len())); 803 805 progress_bar.set_message("Rendering SVG frames to PNG"); 804 806 progress_bar.set_position(0); 807 + 808 + frames_to_write.retain(|(_, _, ms)| *ms >= self.start_rendering_at); 809 + progress_bar.set_length(frames_to_write.len() as u64); 810 + 811 + for (frame, no, _) in &frames_to_write { 812 + std::fs::write( 813 + format!("{}/{}.svg", self.frames_output_directory, no), 814 + &frame, 815 + ); 816 + } 805 817 806 818 let chunk_size = (frames_to_write.len() as f32 / workers_count as f32).ceil() as usize; 807 819 let frames_to_write = Arc::new(frames_to_write);
+56 -28
src/main.rs
··· 1 + use std::f32::consts::E; 2 + 3 + use itertools::Itertools; 1 4 use shapemaker::{Anchor, Canvas, CenterAnchor, Color, Fill, Layer, Object, Region, Video}; 2 5 mod cli; 3 6 pub use cli::{canvas_from_cli, cli_args}; ··· 24 27 25 28 let mut video = Video::<State>::new(canvas); 26 29 video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 30 + video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000; 27 31 video.fps = args.flag_fps.unwrap_or(30); 28 32 video.audiofile = args.flag_audio.unwrap().into(); 29 33 video = video ··· 50 54 canvas.replace_or_create_layer(kicks); 51 55 52 56 let mut ch = Layer::new("ch"); 53 - ch.add_object("dot", Object::SmallCircle(Anchor(2, 1)), Some(Fill::Solid(Color::Gray))); 57 + ch.add_object("0", Object::Dot(Anchor(0, 0)), None); 54 58 canvas.replace_or_create_layer(ch); 55 59 }) 56 60 .sync_audio_with(&args.flag_sync_with.unwrap()) ··· 58 62 // ctx.extra.bass_pattern_at = region_cycle(&canvas.world_region, None); 59 63 canvas 60 64 .layer("anchor kick") 61 - .unwrap() 62 65 .paint_all_objects(Fill::Translucent(Color::White, 1.0)); 63 66 64 - canvas.layer("anchor kick").unwrap().flush(); 67 + canvas.layer("anchor kick").flush(); 65 68 66 69 ctx.later_ms(200, &fade_out_kick_circles) 67 70 }) ··· 76 79 new_layer.paint_all_objects(Fill::Solid(Color::Red)); 77 80 canvas.replace_or_create_layer(new_layer) 78 81 }) 82 + .on_note( 83 + "rimshot, glitchy percs, hitting percs, glitchy percs", 84 + &|canvas, ctx| { 85 + let mut new_layer = canvas 86 + .random_layer_within("percs", &ctx.extra.bass_pattern_at.translated(2, 0)); 87 + new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5)); 88 + canvas.replace_or_create_layer(new_layer); 89 + }, 90 + ) 79 91 .on_note("qanda", &|canvas, ctx| { 80 - let mut new_layer = 81 - canvas.random_layer_within("qanda", &ctx.extra.bass_pattern_at.translated(-2, 0)); 92 + let mut new_layer = canvas.random_linelikes_within( 93 + "qanda", 94 + &ctx.extra.bass_pattern_at.translated(-1, -1).enlarged(1, 1), 95 + ); 82 96 new_layer.paint_all_objects(Fill::Solid(Color::Orange)); 97 + new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 98 + * 4.0 99 + * ctx.stem("qanda").velocity_relative(); 83 100 canvas.replace_or_create_layer(new_layer) 84 101 }) 85 102 .on_note("brokenup", &|canvas, ctx| { 86 103 let mut new_layer = canvas 87 - .random_layer_within("brokenup", &ctx.extra.bass_pattern_at.translated(0, -2)); 104 + .random_linelikes_within("brokenup", &ctx.extra.bass_pattern_at.translated(0, -2)); 88 105 new_layer.paint_all_objects(Fill::Solid(Color::Yellow)); 106 + new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 107 + * 4.0 108 + * ctx.stem("brokenup").velocity_relative(); 89 109 canvas.replace_or_create_layer(new_layer); 90 110 }) 91 111 .on_note("goup", &|canvas, ctx| { 92 112 let mut new_layer = 93 - canvas.random_layer_within("goup", &ctx.extra.bass_pattern_at.translated(0, 2)); 113 + canvas.random_linelikes_within("goup", &ctx.extra.bass_pattern_at.translated(0, 2)); 94 114 new_layer.paint_all_objects(Fill::Solid(Color::Green)); 115 + new_layer.object_sizes.default_line_width = 116 + canvas.object_sizes.default_line_width * 4.0 * ctx.stem("goup").velocity_relative(); 95 117 canvas.replace_or_create_layer(new_layer); 96 118 }) 97 - .on_note("ch", &|canvas, _| { 119 + .on_note("ch", &|canvas, ctx| { 98 120 let world = canvas.world_region.clone(); 99 - let layer = canvas.layer("ch").unwrap(); 100 - let (obj, _) = layer.objects.get_mut("dot").unwrap(); 101 - obj.translate_with(hat_region_cycle(&world, &obj.region())); 102 - layer.flush(); 103 - }) 104 - .on_note("flavor kick", &|canvas, _| { 105 - let mut new_layer = canvas.random_layer_within( 106 - "flavor kick", 107 - &Region::from_origin_and_size((14, 0), (1, 1)), 121 + 122 + // keep only the last 2 dots 123 + let dots_to_keep = canvas 124 + .layer("ch") 125 + .objects 126 + .iter() 127 + .sorted_by_key(|(name, _)| name.parse::<usize>().unwrap()) 128 + .rev() 129 + .take(2) 130 + .map(|(name, _)| (name.clone())) 131 + .collect::<Vec<_>>(); 132 + 133 + let layer = canvas.layer("ch"); 134 + layer.object_sizes.empty_shape_stroke_width = 2.0; 135 + layer.objects.retain(|name, _| dots_to_keep.contains(name)); 136 + 137 + let object_name = format!("{}", ctx.ms); 138 + layer.add_object( 139 + &object_name, 140 + Object::Dot(world.resized(-1, -1).random_coordinates_within().into()), 141 + Some(Fill::Solid(Color::Cyan)), 108 142 ); 109 - new_layer.paint_all_objects(Fill::Solid(Color::White)); 110 - canvas.replace_or_create_layer(new_layer); 111 - }) 112 - .on_note("rimshot, glitchy percs", &|canvas, ctx| { 113 - let mut new_layer = 114 - canvas.random_layer_within("percs", &ctx.extra.bass_pattern_at.translated(2, 1)); 115 - new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5)); 116 - canvas.replace_or_create_layer(new_layer); 143 + 144 + canvas.put_layer_on_top("ch"); 145 + canvas.layer("ch").flush(); 117 146 }) 118 147 .when_remaining(10, &|canvas, _| { 119 148 canvas.root().add_object( ··· 137 166 fn fade_out_kick_circles(canvas: &mut Canvas) { 138 167 canvas 139 168 .layer("anchor kick") 140 - .unwrap() 141 169 .paint_all_objects(Fill::Translucent(Color::White, 0.0)); 142 170 143 - canvas.layer("anchor kick").unwrap().flush(); 171 + canvas.layer("anchor kick").flush(); 144 172 } 145 173 146 174 fn update_stem_position( ··· 155 183 Some(&ctx.extra.bass_pattern_at), 156 184 offset, 157 185 ); 158 - match canvas.layer(layer_name) { 186 + match canvas.layer_safe(layer_name) { 159 187 Some(l) => l.move_all_objects(dx, dy), 160 188 _ => (), 161 189 }