this repo has no description
3
fork

Configure Feed

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

✨ WASM + MIDI WebAPI ♥️♥️

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
a0bb8514 6881fe3d

+190 -18
+15
Cargo.lock
··· 214 214 checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" 215 215 dependencies = [ 216 216 "cfg-if", 217 + "js-sys", 217 218 "libc", 218 219 "wasi", 220 + "wasm-bindgen", 219 221 ] 220 222 221 223 [[package]] ··· 599 601 "chrono", 600 602 "chrono-human-duration", 601 603 "docopt", 604 + "getrandom", 602 605 "handlebars", 603 606 "hound", 604 607 "indicatif", ··· 611 614 "serde_json", 612 615 "svg", 613 616 "tiny_http", 617 + "wasm-bindgen", 618 + "web-sys", 614 619 ] 615 620 616 621 [[package]] ··· 757 762 version = "0.2.92" 758 763 source = "registry+https://github.com/rust-lang/crates.io-index" 759 764 checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 765 + 766 + [[package]] 767 + name = "web-sys" 768 + version = "0.3.69" 769 + source = "registry+https://github.com/rust-lang/crates.io-index" 770 + checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 771 + dependencies = [ 772 + "js-sys", 773 + "wasm-bindgen", 774 + ] 760 775 761 776 [[package]] 762 777 name = "windows-core"
+13
Cargo.toml
··· 3 3 version = "1.1.0" 4 4 edition = "2021" 5 5 6 + [lib] 7 + crate-type = ["cdylib"] 8 + 6 9 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 10 8 11 [dependencies] ··· 21 24 chrono-human-duration = "0.1.1" 22 25 handlebars = "5.1.2" 23 26 tiny_http = "0.12.0" 27 + wasm-bindgen = "0.2.92" 28 + getrandom = { version = "0.2", features = ["js"] } 29 + web-sys = { version = "0.3.4", features = [ 30 + 'Document', 31 + 'Element', 32 + 'HtmlElement', 33 + 'Node', 34 + 'Window', 35 + ] } 36 + 24 37 25 38 [dev-dependencies] 26 39 rust-analyzer = "0.0.1"
+7
Justfile
··· 2 2 cargo build 3 3 cp target/debug/shapemaker . 4 4 5 + web: 6 + wasm-pack build --target web -d web 7 + 8 + start-web: 9 + just web 10 + python3 -m http.server --directory web 11 + 5 12 install: 6 13 cp shapemaker ~/.local/bin/ 7 14
+3 -3
src/canvas.rs
··· 145 145 } 146 146 } 147 147 148 - pub fn random_layer(&self, name: &'static str) -> Layer { 148 + pub fn random_layer(&self, name: &str) -> Layer { 149 149 self.random_layer_within(name, &self.world_region) 150 150 } 151 151 ··· 153 153 self.random_object_within(&self.world_region) 154 154 } 155 155 156 - pub fn replace_or_create_layer(&mut self, layer: Layer) { 156 + pub fn add_or_replace_layer(&mut self, layer: Layer) { 157 157 if let Some(existing_layer) = self.layer_safe(&layer.name) { 158 158 existing_layer.replace(layer); 159 159 } else { ··· 161 161 } 162 162 } 163 163 164 - pub fn random_layer_within(&self, name: &'static str, region: &Region) -> Layer { 164 + pub fn random_layer_within(&self, name: &str, region: &Region) -> Layer { 165 165 let mut objects: HashMap<String, (Object, Option<Fill>)> = HashMap::new(); 166 166 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 167 167 for i in 0..number_of_objects {
+20
src/color.rs
··· 30 30 } 31 31 } 32 32 33 + impl From<&str> for Color { 34 + fn from(s: &str) -> Self { 35 + match s { 36 + "black" => Color::Black, 37 + "white" => Color::White, 38 + "red" => Color::Red, 39 + "green" => Color::Green, 40 + "blue" => Color::Blue, 41 + "yellow" => Color::Yellow, 42 + "orange" => Color::Orange, 43 + "purple" => Color::Purple, 44 + "brown" => Color::Brown, 45 + "cyan" => Color::Cyan, 46 + "pink" => Color::Pink, 47 + "gray" => Color::Gray, 48 + _ => panic!("Invalid color: {}", s), 49 + } 50 + } 51 + } 52 + 33 53 #[derive(Debug, Deserialize, Clone)] 34 54 pub struct ColorMapping { 35 55 pub black: String,
+2 -2
src/fill.rs
··· 78 78 opacity 79 79 ) 80 80 } 81 - Fill::Dotted => unimplemented!(), 82 - Fill::Hatched => unimplemented!(), 81 + Fill::Dotted(..) => unimplemented!(), 82 + Fill::Hatched(..) => unimplemented!(), 83 83 } 84 84 } 85 85 }
+2 -2
src/lib.rs
··· 8 8 pub use fill::*; 9 9 mod region; 10 10 pub use region::*; 11 + mod web; 12 + pub use web::main; 11 13 mod audio; 12 14 pub use audio::*; 13 15 mod sync; 14 - use itertools::Itertools; 15 - 16 16 use sync::SyncData; 17 17 pub use sync::Syncable; 18 18 mod layer;
+13 -11
src/main.rs example/main.rs
··· 1 - use itertools::Itertools; 2 - use shapemaker::{Anchor, Canvas, Color, Fill, Layer, Object, Region, Video}; 3 1 mod cli; 4 2 pub use cli::{canvas_from_cli, cli_args}; 3 + use shapemaker::{Anchor, Canvas, Color, Fill, Layer, Object, Region, Video}; 5 4 6 5 fn main() { 7 - let args = cli_args(); 6 + run(cli_args()); 7 + } 8 + 9 + pub fn run(args: cli::Args) { 8 10 let mut canvas = canvas_from_cli(&args); 9 11 10 12 if args.cmd_image && !args.cmd_video { ··· 49 51 kicks.add_object("top right", circle_at(end_x, 1), fill); 50 52 kicks.add_object("bottom left", circle_at(1, end_y), fill); 51 53 kicks.add_object("bottom right", circle_at(end_x, end_y), fill); 52 - canvas.replace_or_create_layer(kicks); 54 + canvas.add_or_replace_layer(kicks); 53 55 54 56 let mut ch = Layer::new("ch"); 55 57 ch.add_object("0", Object::Dot(Anchor(0, 0)), None); 56 - canvas.replace_or_create_layer(ch); 58 + canvas.add_or_replace_layer(ch); 57 59 }) 58 60 .sync_audio_with(&args.flag_sync_with.unwrap()) 59 61 .on_note("anchor kick", &|canvas, ctx| { ··· 69 71 .on_note("bass", &|canvas, ctx| { 70 72 let mut new_layer = canvas.random_layer_within("bass", &ctx.extra.bass_pattern_at); 71 73 new_layer.paint_all_objects(Fill::Solid(Color::White)); 72 - canvas.replace_or_create_layer(new_layer); 74 + canvas.add_or_replace_layer(new_layer); 73 75 }) 74 76 .on_note("powerful clap hit, clap, perclap", &|canvas, ctx| { 75 77 let mut new_layer = 76 78 canvas.random_layer_within("claps", &ctx.extra.bass_pattern_at.translated(2, 0)); 77 79 new_layer.paint_all_objects(Fill::Solid(Color::Red)); 78 - canvas.replace_or_create_layer(new_layer) 80 + canvas.add_or_replace_layer(new_layer) 79 81 }) 80 82 .on_note( 81 83 "rimshot, glitchy percs, hitting percs, glitchy percs", ··· 83 85 let mut new_layer = canvas 84 86 .random_layer_within("percs", &ctx.extra.bass_pattern_at.translated(2, 0)); 85 87 new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5)); 86 - canvas.replace_or_create_layer(new_layer); 88 + canvas.add_or_replace_layer(new_layer); 87 89 }, 88 90 ) 89 91 .on_note("qanda", &|canvas, ctx| { ··· 95 97 new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 96 98 * 4.0 97 99 * ctx.stem("qanda").velocity_relative(); 98 - canvas.replace_or_create_layer(new_layer) 100 + canvas.add_or_replace_layer(new_layer) 99 101 }) 100 102 .on_note("brokenup", &|canvas, ctx| { 101 103 let mut new_layer = canvas ··· 104 106 new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 105 107 * 4.0 106 108 * ctx.stem("brokenup").velocity_relative(); 107 - canvas.replace_or_create_layer(new_layer); 109 + canvas.add_or_replace_layer(new_layer); 108 110 }) 109 111 .on_note("goup", &|canvas, ctx| { 110 112 let mut new_layer = ··· 112 114 new_layer.paint_all_objects(Fill::Solid(Color::Green)); 113 115 new_layer.object_sizes.default_line_width = 114 116 canvas.object_sizes.default_line_width * 4.0 * ctx.stem("goup").velocity_relative(); 115 - canvas.replace_or_create_layer(new_layer); 117 + canvas.add_or_replace_layer(new_layer); 116 118 }) 117 119 .on_note("ch", &|canvas, ctx| { 118 120 let world = canvas.world_region.clone();
+44
src/web.rs
··· 1 + use wasm_bindgen::prelude::wasm_bindgen; 2 + use wasm_bindgen::JsValue; 3 + 4 + use crate::{Canvas, Color, ColorMapping, Fill}; 5 + 6 + #[wasm_bindgen(start)] 7 + pub fn main() -> Result<(), JsValue> { 8 + render_image(0.0, "black")?; 9 + Ok(()) 10 + } 11 + 12 + #[wasm_bindgen] 13 + pub fn render_image(opacity: f32, color: &str) -> Result<(), JsValue> { 14 + let mut canvas = Canvas::default_settings(); 15 + canvas.colormap = ColorMapping { 16 + black: "#ffffff".into(), 17 + white: "#ffffff".into(), 18 + red: "#cf0a2b".into(), 19 + green: "#22e753".into(), 20 + blue: "#2734e6".into(), 21 + yellow: "#f8e21e".into(), 22 + orange: "#f05811".into(), 23 + purple: "#6a24ec".into(), 24 + brown: "#a05634".into(), 25 + pink: "#e92e76".into(), 26 + gray: "#81a0a8".into(), 27 + cyan: "#4fecec".into(), 28 + }; 29 + 30 + canvas.set_grid_size(4, 4); 31 + 32 + let mut layer = canvas.random_layer(&color); 33 + layer.paint_all_objects(Fill::Translucent(color.into(), opacity)); 34 + canvas.add_or_replace_layer(layer); 35 + 36 + let window = web_sys::window().expect("no global `window` exists"); 37 + let document = window.document().expect("should have a document on window"); 38 + let body = document.body().expect("document should have a body"); 39 + 40 + let output = document.create_element("div")?; 41 + output.set_inner_html(&canvas.render(&vec!["*"], false)); 42 + body.append_child(&output)?; 43 + Ok(()) 44 + }
+1
web/.gitignore
··· 1 + *
+70
web/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Shapemaker Web</title> 7 + </head> 8 + <body> 9 + <script type="module"> 10 + import init, { render_image } from "./shapemaker.js" 11 + async function run() { 12 + await init() 13 + window.render_image = (vel, col) => { 14 + console.log([...arguments]) 15 + document.querySelector("body > div").remove() 16 + render_image(vel, col) 17 + } 18 + } 19 + run() 20 + 21 + window.pedal_held = false 22 + 23 + console.log("requesting midi access") 24 + navigator.requestMIDIAccess().then((midiAccess) => { 25 + Array.from(midiAccess.inputs).forEach((input) => { 26 + input[1].onmidimessage = (msg) => { 27 + const [cmd, ...args] = [...msg.data] 28 + if (cmd === 248) return 29 + if (cmd === 176) { 30 + window.pedal_held = args[1] > 0 31 + return 32 + } 33 + if (cmd !== 144) return 34 + const [pitch, velocity] = args 35 + if (velocity === 0) return 36 + const colors = [ 37 + "blue", 38 + "purple", 39 + "pink", 40 + "red", 41 + "orange", 42 + "yellow", 43 + "white", 44 + ] 45 + // get color uniformly in this range from 28 (lowest pitch) to 103 (highest pitch) 46 + 47 + const color = 48 + colors[Math.floor(((pitch - 28) / (103 - 28)) * colors.length)] 49 + console.log(pitch, color) 50 + window.render_image(velocity / 128, color ?? "white") 51 + } 52 + }) 53 + }) 54 + </script> 55 + <style> 56 + body > div { 57 + position: fixed; 58 + top: 0; 59 + left: 0; 60 + right: 0; 61 + bottom: 0; 62 + background: black; 63 + display: flex; 64 + justify-content: center; 65 + align-items: center; 66 + } 67 + </style> 68 + </body> 69 + <!-- <button onclick="render_image('white')">Render Image</button> --> 70 + </html>