This repository has no description
0

Configure Feed

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

🍱 Continue Schedule Hell

+218 -41
+1
Cargo.lock
··· 2494 2494 dependencies = [ 2495 2495 "anyhow", 2496 2496 "itertools 0.14.0", 2497 + "notify-rust", 2497 2498 "pico-args", 2498 2499 "rand 0.9.2", 2499 2500 "serde-aux",
+3
Justfile
··· 5 5 s *args: 6 6 just schedule-hell --resolution 480 {{args}} 7 7 8 + backbone *args: 9 + just schedule-hell --resolution 480 --marker "\"end first break\"" {{args}} 10 + 8 11 [working-directory: 'examples/schedule-hell'] 9 12 schedule-hell *args: 10 13 cargo run -- {{args}}
+1
examples/schedule-hell/Cargo.toml
··· 6 6 [dependencies] 7 7 anyhow = "1.0.97" 8 8 itertools = "0.14.0" 9 + notify-rust = "4.11.7" 9 10 pico-args = { version = "0.5.0", features = ["combined-flags", "eq-separator"] } 10 11 rand = "0.9.0" 11 12 serde-aux = "4.7.0"
+44 -30
examples/schedule-hell/src/main.rs
··· 1 1 mod scenes; 2 2 3 3 use anyhow::anyhow; 4 + use itertools::Itertools; 4 5 use rand::{SeedableRng, rngs::SmallRng}; 5 - use shapemaker::{ui::Log, *}; 6 + use shapemaker::{ui::Log, video::engine::EngineControl, *}; 6 7 use std::{fs, path::PathBuf, time::Duration}; 7 8 8 9 pub struct State { 9 10 bass_pattern_at: Region, 10 11 kick_color: Color, 11 12 rng: SmallRng, 12 - kick_counter: u32, 13 + cranks: u32, 13 14 } 14 15 15 16 impl Default for State { ··· 18 19 bass_pattern_at: Region::from_topleft(Point(1, 1), (2, 2)).unwrap(), 19 20 kick_color: Color::White, 20 21 rng: SmallRng::seed_from_u64(0), 21 - kick_counter: 0, 22 + cranks: 0, 22 23 } 23 24 } 24 25 } 25 26 26 27 #[tokio::main] 27 28 pub async fn main() { 28 - let mut canvas = Canvas::with_layers(vec![]); 29 - 30 - canvas.set_grid_size(16, 9); 31 - canvas.colormap = ColorMapping { 32 - black: "#000000".into(), 33 - white: "#ffffff".into(), 34 - red: "#cf0a2b".into(), 35 - green: "#22e753".into(), 36 - blue: "#2734e6".into(), 37 - yellow: "#f8e21e".into(), 38 - orange: "#f05811".into(), 39 - purple: "#6a24ec".into(), 40 - brown: "#a05634".into(), 41 - pink: "#e92e76".into(), 42 - gray: "#81a0a8".into(), 43 - cyan: "#4fecec".into(), 44 - }; 29 + let canvas = Canvas::new(16, 9); 45 30 46 31 let mut video = Video::<State>::new(canvas); 47 32 let mut args = pico_args::Arguments::from_env(); ··· 61 46 .map(Timestamp::from_seconds) 62 47 .unwrap_or_default(); 63 48 49 + video = video 50 + // Sync inputs // 51 + .sync_audio_with("schedule-hell.midi") 52 + .sync_audio_with("schedule-hell.wav"); 53 + 54 + if let Ok(marker) = args.value_from_str::<_, String>("--marker") { 55 + let marker_start = video 56 + .syncdata 57 + .markers 58 + .iter() 59 + .find_map(|(&ms, m)| if m == &marker { Some(ms) } else { None }) 60 + .expect("Marker not found"); 61 + 62 + let marker_end = video 63 + .syncdata 64 + .markers 65 + .iter() 66 + .filter(|&(&ms, _)| ms > marker_start) 67 + .sorted_by_key(|&(&ms, _)| ms) 68 + .find_map(|(&ms, m)| if m != &marker { Some(ms) } else { None }); 69 + 70 + video.start_rendering_at = Timestamp::from_ms(marker_start as _); 71 + video.duration_override = 72 + marker_end.map(|end| Duration::from_millis((end - marker_start) as _)) 73 + } 74 + 64 75 video.resolution = args.value_from_str("--resolution").ok().unwrap_or(480); 65 76 video.fps = args.value_from_str("--fps").ok().unwrap_or(30); 66 77 67 78 video.audiofile = PathBuf::from("schedule-hell.wav"); 68 79 video = video 69 - // Sync inputs // 70 - .sync_audio_with("schedule-hell.midi") 71 - .sync_audio_with("schedule-hell.wav") 72 80 // Scenes // 73 81 .with_scene(scenes::starry_sky()) 74 82 .with_init_scene(scenes::intro()) 75 83 .with_marked_scene(scenes::first_break()) 76 - .assign_scene_to("end of first break", "starry sky") 77 - .assign_scene_to("second break", "intro") 84 + .with_scene(scenes::backbone()) 85 + .assign_scene_to("end of first break", "intro") 86 + .assign_scene_to("second break", "starry sky") 87 + // "end first break" means "end of second break" lol 88 + .assign_scene_to("end first break", "backbone") 78 89 // Credits // 79 90 .when_remaining(10, &|canvas, _| { 80 91 let world = canvas.world_region; ··· 112 123 ), 113 124 ); 114 125 115 - video.render_frame(frame_no, render_ahead).and_then(|svg| { 116 - fs::write(destination, svg.to_string()) 117 - .map_err(|e| anyhow!("{e:?}")) 118 - }) 126 + video 127 + .render_frame(frame_no, render_ahead) 128 + .and_then(|svg| { 129 + fs::write(destination, svg.to_string()) 130 + .map_err(|e| anyhow!("{e:?}")) 131 + }) 132 + .map(|_| Duration::default()) 119 133 } else { 120 - video.encode(destination).map(|_| ()) 134 + video.encode(destination) 121 135 }; 122 136 123 137 match result {
+143
examples/schedule-hell/src/scenes/backbone.rs
··· 1 + use anyhow::Result; 2 + use rand::{Rng, rngs::SmallRng, seq::IteratorRandom}; 3 + use shapemaker::*; 4 + 5 + use crate::State; 6 + 7 + pub fn backbone() -> Scene<State> { 8 + Scene::<State>::new("backbone") 9 + .init(&|canvas, ctx| { 10 + canvas.clear(); 11 + 12 + canvas.colormap = ColorMapping { 13 + black: "#000000".to_string(), 14 + white: "#FFFFFF".to_string(), 15 + purple: "#da40f5".to_string(), 16 + ..Default::default() 17 + }; 18 + 19 + canvas.set_grid_size(16, 10); 20 + canvas.set_background(Black); 21 + canvas.object_sizes.dot_radius = 7.5; 22 + 23 + iterate(&mut ctx.extra.rng, canvas)?; 24 + Ok(()) 25 + }) 26 + .each_n_frame(3, &|canvas, ctx| { 27 + canvas.clear(); 28 + iterate(&mut ctx.extra.rng, canvas)?; 29 + Ok(()) 30 + }) 31 + .on_note("anchor kick", &|canvas, ctx| { 32 + canvas.clear(); 33 + iterate(&mut ctx.extra.rng, canvas)?; 34 + 35 + let world = canvas.world_region.clone(); 36 + let flickers = canvas.layer("flickers")?; 37 + 38 + let point = world.iter().choose(&mut ctx.extra.rng).unwrap(); 39 + 40 + flickers.tag_objects("rotate", |id, _| { 41 + id == &format!("crosses-SWNE-{point}") 42 + || id == &format!("crosses-NWSE-{point}") 43 + }); 44 + 45 + ctx.animate(700, &move |t, canvas, _| { 46 + canvas 47 + .layer("flickers")? 48 + .objects_with_tag("rotate") 49 + .for_each(|(_, obj)| { 50 + obj.recolor(Cyan); 51 + obj.set_rotation(Angle::from_degrees(t * 45.0)); 52 + }); 53 + 54 + Ok(()) 55 + }); 56 + 57 + Ok(()) 58 + }) 59 + } 60 + 61 + fn iterate(rng: &mut SmallRng, canvas: &mut Canvas) -> Result<()> { 62 + // let mut rng = canvas.rng.clone(); 63 + let world = canvas.world_region.clone(); 64 + 65 + let grid_thickness = 2.0; 66 + 67 + for point in 68 + Region::from((world.topleft(), world.topright().translated(1, 1))) 69 + { 70 + canvas.root().set( 71 + format!("grid-rows-{point}"), 72 + Object::Line( 73 + Point(point.0, world.topleft().1), 74 + Point(point.0, world.bottomleft().1 + 1), 75 + grid_thickness * 0.75, 76 + ) 77 + .filled(White.translucent(0.05 + rng.random_range(0.0..0.3))), 78 + ); 79 + } 80 + 81 + for point in 82 + Region::from((world.topleft(), world.bottomleft().translated(1, 1))) 83 + { 84 + canvas.root().set( 85 + format!("grid-cols-{point}"), 86 + Object::Line( 87 + Point(world.topleft().0, point.1), 88 + Point(world.bottomright().0 + 1, point.1), 89 + grid_thickness * 0.75, 90 + ) 91 + .filled(White.translucent(0.005 + rng.random_range(0.0..0.3))), 92 + ); 93 + } 94 + 95 + let occlusions = canvas.layer_or_empty("occlusions"); 96 + 97 + for point in world.enlarged(1, 1) { 98 + occlusions.set( 99 + format!("occlusion-{point}"), 100 + Object::Dot(point).colored(Color::Black), 101 + ); 102 + } 103 + 104 + let flickers = canvas.layer_or_empty("flickers"); 105 + 106 + for point in world { 107 + flickers.set( 108 + format!("crosses-SWNE-{point}"), 109 + Object::Line(point, point.translated(1, 1), grid_thickness) 110 + .colored(Color::Purple) 111 + .opacified(0.25 + rng.random_range(0.5..1.0)), 112 + ); 113 + flickers.set( 114 + format!("crosses-NWSE-{point}"), 115 + Object::Line( 116 + point.translated(0, 1), 117 + point.translated(1, 0), 118 + grid_thickness, 119 + ) 120 + .colored(Color::Purple) 121 + .opacified(0.25 + rng.random_range(0.5..1.0)), 122 + ); 123 + } 124 + 125 + let flickers_occlusions = canvas.layer_or_empty("flickers_occlusions"); 126 + flickers_occlusions.object_sizes.dot_radius = 10.0; 127 + 128 + for point in world.enlarged(1, 1) { 129 + flickers_occlusions.set( 130 + format!("crosses-occlusions-{point}"), 131 + Object::Dot(point).colored(Color::Black), 132 + ) 133 + } 134 + 135 + canvas.reorder_layers(vec![ 136 + "flickers_occlusions", 137 + "flickers", 138 + "occlusions", 139 + "root", 140 + ]); 141 + 142 + Ok(()) 143 + }
+16 -2
examples/schedule-hell/src/scenes/intro.rs
··· 7 7 canvas.clear(); 8 8 canvas.set_grid_size(16, 9); 9 9 canvas.set_background(Black); 10 + canvas.colormap = ColorMapping { 11 + black: "#000000".into(), 12 + white: "#ffffff".into(), 13 + red: "#cf0a2b".into(), 14 + green: "#22e753".into(), 15 + blue: "#2734e6".into(), 16 + yellow: "#f8e21e".into(), 17 + orange: "#f05811".into(), 18 + purple: "#6a24ec".into(), 19 + brown: "#a05634".into(), 20 + pink: "#e92e76".into(), 21 + gray: "#81a0a8".into(), 22 + cyan: "#4fecec".into(), 23 + }; 10 24 11 25 let mut kicks = Layer::new("anchor kick"); 12 26 ··· 25 39 .layer("anchor kick")? 26 40 .paint_all_objects(Fill::Translucent(ctx.extra.kick_color, 1.0)); 27 41 28 - ctx.animate_layer("anchor kick", 200, &|t, layer, _| { 29 - layer.objects.values_mut().for_each( 42 + ctx.animate(200, &|t, canvas, _| { 43 + canvas.layer("anchor kick")?.objects.values_mut().for_each( 30 44 |ColoredObject { fill, .. }| { 31 45 *fill = fill.opacify(1.0 - t); 32 46 },
+2
examples/schedule-hell/src/scenes/mod.rs
··· 4 4 pub use first_break::first_break; 5 5 pub mod starry_sky; 6 6 pub use starry_sky::starry_sky; 7 + pub mod backbone; 8 + pub use backbone::backbone;
+8 -9
examples/schedule-hell/src/scenes/starry_sky.rs
··· 6 6 pub fn starry_sky() -> Scene<State> { 7 7 Scene::<State>::new("starry sky") 8 8 .init(&|canvas, ctx| { 9 - ctx.extra.kick_counter = 0; 10 - sky(ctx.extra.kick_counter, canvas) 9 + ctx.extra.cranks = 0; 10 + sky(ctx.extra.cranks, canvas) 11 11 }) 12 - .on_note("anchor kick", &|canvas, ctx| { 12 + .on_note("brokenup", &|canvas, ctx| { 13 13 // Move spacecraft on each kick 14 - ctx.extra.kick_counter += 1; 15 - sky(ctx.extra.kick_counter, canvas) 14 + ctx.extra.cranks += 1; 15 + sky(ctx.extra.cranks, canvas) 16 16 }) 17 17 .each_n_frame(3, &|canvas, ctx| { 18 18 // Keep spacecraft alive, by animating on threes 19 - sky(ctx.extra.kick_counter, canvas) 19 + sky(ctx.extra.cranks, canvas) 20 20 }) 21 21 } 22 22 23 - fn sky(kick_hits_count: u32, canvas: &mut Canvas) -> Result<()> { 24 - // Make a full rotation every 32 kicks 25 - let theta = Angle::from_ratio(kick_hits_count as f32, 32.0); 23 + fn sky(cranks: u32, canvas: &mut Canvas) -> Result<()> { 24 + let theta = Angle::from_ratio(cranks as f32, 72.0); 26 25 27 26 canvas.clear(); 28 27 canvas.colormap = ColorMapping {