//! Offline compilation and software-rendered visual QA for the shared editor. pub use egui; #[path = "../../../plugins/rivet/plugin/src/editor.rs"] mod editor; use std::sync::Arc; fn main() { let args: Vec<_> = std::env::args().collect(); let plugin = args .windows(2) .find(|a| a[0] == "--plugin") .map(|a| a[1].as_str()) .unwrap_or("rivet"); if !["rivet", "patina", "anvil", "crucible", "lathe", "temper", "weld"].contains(&plugin) { eprintln!("Use --plugin rivet, patina, anvil, crucible, lathe, temper or weld"); std::process::exit(2); } let patina = plugin == "patina" || args.iter().any(|arg| arg == "--resonance"); let compact = std::env::args().any(|arg| arg == "--compact"); let anvil = plugin == "anvil"; let crucible = plugin == "crucible"; let lathe = plugin == "lathe"; let temper = plugin == "temper"; let weld = plugin == "weld"; let (width, height) = if weld { (weld_ui::WIDTH, weld_ui::HEIGHT) } else if temper { (temper_ui::WIDTH, temper_ui::HEIGHT) } else if lathe { (lathe_ui::WIDTH, lathe_ui::HEIGHT) } else if crucible { (crucible_ui::WIDTH, crucible_ui::HEIGHT) } else if anvil { (anvil_ui::WIDTH, anvil_ui::HEIGHT) } else if patina { if compact { (1040, 700) } else { (1180, 760) } } else { (1100, 850) }; let mut patina_settings = patina_core::Settings::preset(1); patina_settings.focus[0] = patina_core::Focus { enabled: true, frequency: 380., width: 0.5, sensitivity: -12., }; patina_settings.focus[1] = patina_core::Focus { enabled: true, frequency: 3000., width: 0.3, sensitivity: 6., }; let mut patina_state = patina_ui::State::default(); patina_state.selected = Some(1); patina_state.preset = 1; patina_state.delta = std::env::args().any(|arg| arg == "--delta"); patina_settings.bypass = std::env::args().any(|arg| arg == "--bypass"); if patina && !std::env::args().any(|arg| arg == "--silent") { let mut engine = patina_core::Engine::new(48000., 2).unwrap(); engine.set_settings(patina_settings).unwrap(); for i in 0..24000 { let t = i as f32 / 48000.; // A 220 Hz harmonic tone with a ring around 3 kHz. let x = (1..80) .map(|k| { let f = 220. * k as f32; let ring = 1. + 5. * (-((f / 3000.).log2() / 0.12).powi(2)).exp(); (std::f32::consts::TAU * f * t).sin() * ring / k as f32 }) .sum::() * 0.12; engine.process_sample([x, x * 0.85], patina_core::Monitor::default()); if let Some(frame) = engine.take_analyzer() { patina_state.accept_frame(frame); } } } let mut anvil_settings = anvil_core::Settings::default(); let mut anvil_state = anvil_ui::State::default(); if anvil { let mut engine = anvil_core::Engine::new(48000., 2).unwrap(); engine.set_settings(anvil_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { // A distorted F1 bass whose own sub drifts in phase and level. let x = (saw.sample() * 1.6 + 0.3 * (i as f32 * 0.0009).sin()).tanh() * 0.4; engine.process_sample([x, x], anvil_core::Monitor::default()); if i % 1600 == 0 { anvil_state.accept_frame(engine.take_analyzer()); } } anvil_state.accept_frame(engine.take_analyzer()); } let mut lathe_settings = lathe_core::Settings::default(); let mut lathe_state = lathe_ui::State::default(); if lathe { // A growl-like map: cut the hollow region, lift the 5th, darker top. lathe_settings.gains[1] = -4.; lathe_settings.gains[2] = -12.; lathe_settings.gains[4] = 6.; lathe_settings.gains[6] = 3.; lathe_settings.gains[10] = -8.; lathe_settings.gains[11] = -8.; lathe_settings.tilt = -1.5; lathe_settings.between = -6.; let mut engine = lathe_core::Engine::new(48000., 2).unwrap(); engine.set_settings(lathe_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { let x = (saw.sample() * 1.6).tanh() * 0.4; engine.process_sample([x, x]); if i % 1600 == 0 { lathe_state.accept_frame(engine.take_analyzer()); } } lathe_state.accept_frame(engine.take_analyzer()); } let mut temper_settings = temper_core::Settings { drive: 20., curve: temper_core::Curve::Hard, bias: 20., floor: 0.5, push_harmonic: 5, push_gain: 6., bright: 12., ..Default::default() }; let mut temper_state = temper_ui::State::default(); if temper { let mut engine = temper_core::Engine::new(48000., 2).unwrap(); engine.set_settings(temper_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { let x = saw.sample() * 0.3; engine.process_sample([x, x]); if i % 1600 == 0 { temper_state.accept_frame(engine.take_analyzer()); } } temper_state.accept_frame(engine.take_analyzer()); } let mut weld_settings = weld_core::Settings::default(); let mut weld_state = weld_ui::State::default(); if weld { // Two layers of an F1 saw bass; the sidechain layer is polarity-flipped. let mut engine = weld_core::Engine::new(48000., 2).unwrap(); engine.set_settings(weld_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { let x = (saw.sample() * 1.6).tanh() * 0.4; engine.process_sample([x, x], [-0.7 * x, -0.7 * x]); if i % 1600 == 0 { weld_state.accept_frame(engine.take_analyzer()); } } weld_state.accept_frame(engine.take_analyzer()); } let mut crucible_settings = crucible_core::Settings { folder: "C:\\Users\\you\\Documents\\Crucible".into(), ..Default::default() }; let mut crucible_state = crucible_ui::State::default(); let crucible_link = crucible_ui::Link { ring: Some(Arc::new(crucible_core::Ring::new( 48000., crucible_core::HISTORY, ))), queues: Arc::new(crucible_core::Queues::default()), }; if crucible { // Four bars of a 150 BPM riddim pattern: square-ish bass hits on varied notes. let rate = 48000.; let beat = 60. / 150. * rate; let frames = (16. * beat) as usize; let mut audio = vec![0f32; frames * 2]; let pattern = [ (0., 29), (1.5, 29), (2.75, 32), (4., 27), (5.5, 29), (6.5, 34), (8., 29), (9.5, 29), (10.75, 36), (12., 27), (13.5, 32), (14.5, 29), ]; for (at, note) in pattern { let start = (at * beat) as usize; let hz = common_dsp::midi_to_hz(note as f32); let mut saw = common_dsp::Saw::new(rate, hz); for i in 0..(0.6 * beat) as usize { let f = start + i; if f >= frames { break; } let env = (1. - i as f32 / (0.6 * beat)).powf(0.4); let v = (saw.sample() * 2.5).tanh() * 0.6 * env; audio[2 * f] += v; audio[2 * f + 1] += v; } } let transport = crucible_core::Transport { playing: true, tempo: 150., beats: 0., numerator: 4, denominator: 4, }; let segments = vec![crucible_core::Segment { frame: 0., quarters: 0., tempo: 150., }]; let take = crucible_core::Take::from_audio(rate, audio, 4, transport, segments, true); crucible_settings.mode = crucible_core::Mode::Transient; crucible_state.add(take.clone(), &crucible_settings); crucible_state.add(take, &crucible_settings); crucible_state.slice = Some(2); crucible_state.takes[0].slices[5].enabled = false; crucible_state.transport = Some(transport); crucible_state.status = "Captured 4 bars on the bar grid.".into(); } let shared = Arc::new(rivet_app::Shared::default()); let mut session = rivet_core::Session::demo(); session.motifs[0].phrase.notes[0].locked = true; session.realize_all(); session.blocks[2].kept = true; let creative = std::env::args().any(|arg| arg == "--creative"); if creative { session.performance.mode = rivet_core::RoleMode::CallResponse; session.performance.voices[0].name = "Glass question".into(); session.performance.voices[1].name = "Warm answer".into(); for (i, voice) in session.performance.voices.iter_mut().enumerate() { voice.controller.enabled = true; voice.controller.cc = 20 + i as u8; voice.controller.shape(2); voice.attack_ms = 40; } } let session_motif = session.motifs[0].id; shared.replace(session).unwrap(); let ctx = egui::Context::default(); if weld { weld_ui::style(&ctx); } else if temper { temper_ui::style(&ctx); } else if lathe { lathe_ui::style(&ctx); } else if crucible { crucible_ui::style(&ctx); } else if anvil { anvil_ui::style(&ctx); } else if patina { patina_ui::style(&ctx); } else { editor::style(&ctx); } let mut state = editor::EditorState::default(); if creative { state.tab = editor::Tab::Sound; } if std::env::args().any(|arg| arg == "--motif") { state.focus = Some(editor::Focus::Motif(session_motif)); } let mut textures = std::collections::HashMap::new(); let mut output = None; for _ in 0..3 { let frame = ctx.run( egui::RawInput { screen_rect: Some(egui::Rect::from_min_size( egui::Pos2::ZERO, egui::vec2(width as f32, height as f32), )), ..Default::default() }, |ui| { egui::CentralPanel::default().show(ui, |ui| { if weld { weld_ui::show(ui, &mut weld_settings, &mut weld_state); } else if temper { temper_ui::show(ui, &mut temper_settings, &mut temper_state); } else if lathe { lathe_ui::show(ui, &mut lathe_settings, &mut lathe_state); } else if crucible { crucible_ui::show( ui, &mut crucible_settings, &mut crucible_state, &crucible_link, ); } else if anvil { anvil_ui::show(ui, &mut anvil_settings, &mut anvil_state); } else if patina { patina_ui::show(ui, &mut patina_settings, &mut patina_state); } else { editor::show(ui, &shared, &mut state); } }); }, ); for (id, delta) in &frame.textures_delta.set { let image = match &delta.image { egui::ImageData::Color(image) => image.as_ref().clone(), egui::ImageData::Font(image) => egui::ColorImage { size: image.size, pixels: image.srgba_pixels(None).collect(), }, }; if let Some([x, y]) = delta.pos { let full = textures.get_mut(id).unwrap(); let full: &mut egui::ColorImage = full; for row in 0..image.size[1] { for col in 0..image.size[0] { full.pixels[(y + row) * full.size[0] + x + col] = image.pixels[row * image.size[0] + col]; } } } else { textures.insert(*id, image.clone()); } } output = Some(frame); } let output = output.unwrap(); let meshes = ctx.tessellate(output.shapes, 1.); let mut image = image::RgbaImage::from_pixel(width, height, image::Rgba([23, 34, 51, 255])); for clipped in meshes { let egui::epaint::Primitive::Mesh(mesh) = clipped.primitive else { continue; }; let Some(texture) = textures.get(&mesh.texture_id) else { continue; }; for tri in mesh.indices.chunks_exact(3) { let [a, b, c] = [ mesh.vertices[tri[0] as usize], mesh.vertices[tri[1] as usize], mesh.vertices[tri[2] as usize], ]; let min_x = a .pos .x .min(b.pos.x) .min(c.pos.x) .max(clipped.clip_rect.left()) .max(0.) as u32; let min_y = a .pos .y .min(b.pos.y) .min(c.pos.y) .max(clipped.clip_rect.top()) .max(0.) as u32; let max_x = a .pos .x .max(b.pos.x) .max(c.pos.x) .min(clipped.clip_rect.right()) .min((width - 1) as f32) as u32; let max_y = a .pos .y .max(b.pos.y) .max(c.pos.y) .min(clipped.clip_rect.bottom()) .min((height - 1) as f32) as u32; let cross = |u: egui::Vec2, v: egui::Vec2| u.x * v.y - u.y * v.x; let area = cross(b.pos - a.pos, c.pos - a.pos); if area.abs() < 0.0001 { continue; } for y in min_y..=max_y { for x in min_x..=max_x { let p = egui::pos2(x as f32 + 0.5, y as f32 + 0.5); let wa = cross(b.pos - p, c.pos - p) / area; let wb = cross(c.pos - p, a.pos - p) / area; let wc = 1. - wa - wb; if wa < 0. || wb < 0. || wc < 0. { continue; } let uv = a.uv.to_vec2() * wa + b.uv.to_vec2() * wb + c.uv.to_vec2() * wc; let tx = (uv.x * texture.size[0] as f32) .floor() .clamp(0., texture.size[0] as f32 - 1.) as usize; let ty = (uv.y * texture.size[1] as f32) .floor() .clamp(0., texture.size[1] as f32 - 1.) as usize; let tex = texture.pixels[ty * texture.size[0] + tx].to_array(); let ac = a.color.to_array(); let bc = b.color.to_array(); let cc = c.color.to_array(); let mut color = [0.; 4]; for i in 0..4 { color[i] = (ac[i] as f32 * wa + bc[i] as f32 * wb + cc[i] as f32 * wc) / 255. * tex[i] as f32; } let pixel = image.get_pixel_mut(x, y); let alpha = color[3] / 255.; for i in 0..3 { pixel[i] = (color[i] + pixel[i] as f32 * (1. - alpha)).clamp(0., 255.) as u8; } } } } } let path = std::env::args() .nth(1) .unwrap_or_else(|| "/tmp/rivet-editor.png".into()); image.save(&path).unwrap(); println!("Rendered {path}"); }