//! 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", "knurl", "planish", "braze", "quench", "ballast", "forge", ] .contains(&plugin) { eprintln!( "Use --plugin rivet, patina, anvil, crucible, lathe, temper, weld, knurl, planish, \ braze, quench, ballast or forge" ); 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 knurl = plugin == "knurl"; let planish = plugin == "planish"; let braze = plugin == "braze"; let quench = plugin == "quench"; let ballast = plugin == "ballast"; let forge = plugin == "forge"; let (width, height) = if forge { (forge_ui::WIDTH, forge_ui::HEIGHT) } else if ballast { (ballast_ui::WIDTH, ballast_ui::HEIGHT) } else if quench { (quench_ui::WIDTH, quench_ui::HEIGHT) } else if braze { (braze_ui::WIDTH, braze_ui::HEIGHT) } else if planish { (planish_ui::WIDTH, planish_ui::HEIGHT) } else if knurl { (knurl_ui::WIDTH, knurl_ui::HEIGHT) } else 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 { anvil_settings.ghost = 60.; anvil_settings.kick_lock = true; anvil_state.audition = true; let monitor = anvil_core::Monitor { audition: true, ..Default::default() }; 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); let total = 300000; for i in 0..total { // A distorted F1 bass whose own sub drifts in phase and level, and a kick every // 0.2 s in the sidechain while the audition runs. let x = (saw.sample() * 1.6 + 0.3 * (i as f32 * 0.0009).sin()).tanh() * 0.4; let t = (i % 9600) as f32 / 48000.; let kick = 0.8 * (std::f32::consts::TAU * 50. * t).sin() * (-t / 0.06).exp(); engine.process_with_kick([x, x], kick, monitor); if i < total - 36000 { continue; } 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 knurl_settings = knurl_core::Settings::default(); let mut knurl_state = knurl_ui::State::default(); if knurl { // An F1 saw bass with the default Fry pattern, jitter and grit. let mut engine = knurl_core::Engine::new(48000., 2).unwrap(); engine.set_settings(knurl_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { let x = (saw.sample() * 2.2).tanh() * 0.5; engine.process_sample([x, x]); if i % 1600 == 0 { knurl_state.accept_frame(engine.take_analyzer()); } } knurl_state.accept_frame(engine.take_analyzer()); } let mut forge_settings = forge_core::Settings::default(); forge_settings.lathe.gains[1] = -9.; forge_settings.lathe.gains[3] = 4.; forge_settings.temper.drive = 9.; forge_settings.order = [ forge_core::Stage::Knurl, forge_core::Stage::Lathe, forge_core::Stage::Temper, ]; let mut forge_state = forge_ui::State::default(); forge_state.page = match args .iter() .position(|a| a == "--page") .map(|i| args[i + 1].as_str()) { Some("temper") => forge_core::Stage::Temper, Some("knurl") => forge_core::Stage::Knurl, _ => forge_core::Stage::Lathe, }; if forge { // An F1 saw bass through Knurl's Fry pattern first: Lathe after it still follows F1. let mut engine = forge_core::Engine::new(48000., 2).unwrap(); engine.set_settings(forge_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..36000 { let x = (saw.sample() * 2.2).tanh() * 0.5; engine.process_sample([x, x]); if i % 1600 == 0 { forge_state.accept_frame(engine.take_analyzer()); } } forge_state.accept_frame(engine.take_analyzer()); } let mut planish_settings = planish_core::Settings { depth: 80., gains: [0., 2., 3., -2.], ..Default::default() }; let mut planish_state = planish_ui::State::default(); if planish { // A distorted F1 saw bass: loud body, thin top, so both directions work. let mut engine = planish_core::Engine::new(48000., 2).unwrap(); engine.set_settings(planish_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 { planish_state.accept_frame(engine.take_analyzer()); } } planish_state.accept_frame(engine.take_analyzer()); } let mut braze_settings = braze_core::Settings { width: 30., ..Default::default() }; let mut braze_state = braze_ui::State::default(); if braze { // A reese-like F1 saw at 140 BPM, 2.3 beats into the bar. let mut engine = braze_core::Engine::new(48000., 2).unwrap(); engine.set_settings(braze_settings).unwrap(); engine.set_transport(Some(140.), Some(0.)); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..47300 { let x = (saw.sample() * 1.6).tanh() * 0.4; engine.process_sample([x, x]); if i % 1600 == 0 { braze_state.accept_frame(engine.take_analyzer()); } } braze_state.accept_frame(engine.take_analyzer()); } let mut quench_settings = quench_core::Settings { drive: 9., ..Default::default() }; let mut quench_state = quench_ui::State::default(); if quench { // A 49 Hz sub under a distorted saw top two octaves up, driven 9 dB into the clipper. let mut engine = quench_core::Engine::new(48000., 2).unwrap(); engine.set_settings(quench_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 196.); for i in 0..48000 { let t = i as f32 / 48000.; let sub = 0.18 * (std::f32::consts::TAU * 49. * t).sin(); let top = (saw.sample() * 3.).tanh() * 0.3; engine.process_sample([sub + top, sub + top], quench_core::Monitor::Quench); if i % 1600 == 0 { quench_state.accept_frame(engine.take_analyzer()); } } quench_state.accept_frame(engine.take_analyzer()); } let mut ballast_settings = ballast_core::Settings { segment: ballast_core::Segment::Grid, target: -16., ..Default::default() }; let mut ballast_state = ballast_ui::State::default(); if ballast { // A switch-up every quarter at 140 BPM, cycling four patches of different loudness. let mut engine = ballast_core::Engine::new(48000., 2).unwrap(); engine.set_settings(ballast_settings).unwrap(); let mut saw = common_dsp::Saw::new(48000., 43.65); let quarter = 48000. * 60. / 140.; for i in 0..48000 * 9 { if i % 512 == 0 { engine.set_transport(Some(140.), Some(i as f64 / 48000. * 140. / 60.)); } let part = (i as f32 / quarter) as usize; let s = saw.sample(); let x = match part % 4 { 0 => (s * 4.).tanh() * 0.6, 1 => s * 0.15, 2 => (s * 2.).tanh() * 0.35, _ => (s * 8.).tanh() * 0.9, }; engine.process_sample([x, x]); if i % 1600 == 0 { ballast_state.accept_frame(engine.take_analyzer()); } } ballast_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 and runs // through an allpass at 400 Hz, as an EQ in its chain would leave it. The real // analyzer measures them and its fits go back to the engine, as on the worker. let mut engine = weld_core::Engine::new(48000., 2).unwrap(); engine.set_settings(weld_settings).unwrap(); let link = engine.link(); let mut analyzer = weld_core::Analyzer::new(48000., weld_core::Fit::NONE); let mut other = weld_core::Chain::new( weld_core::Fit { flip: true, count: 1, sections: [weld_core::Section { fc: 400., q: 0.7 }; weld_core::MAX_SECTIONS], }, 48000., ); let mut saw = common_dsp::Saw::new(48000., 43.65); for i in 0..48000 * 4 { let x = (saw.sample() * 1.6).tanh() * 0.4; let b = 0.8 * other.process(0, x); engine.process_sample([x, x], [b, b]); if i % 1600 == 0 { weld_state.accept_frame(engine.take_analyzer()); } while let Some(settings) = link.pop_settings() { analyzer.set_settings(settings); } while let Some(block) = link.pop_block() { analyzer.push(&block.a, &block.b); if let Some(update) = analyzer.poll() { if let Some(fit) = update.fit { link.push_fit(fit); } weld_state.accept_report(update.report); } } } 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 forge { forge_ui::style(&ctx); } else if ballast { ballast_ui::style(&ctx); } else if quench { quench_ui::style(&ctx); } else if braze { braze_ui::style(&ctx); } else if planish { planish_ui::style(&ctx); } else if knurl { knurl_ui::style(&ctx); } else 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 forge { forge_ui::show(ui, &mut forge_settings, &mut forge_state); } else if ballast { ballast_ui::show(ui, &mut ballast_settings, &mut ballast_state); } else if quench { quench_ui::show(ui, &mut quench_settings, &mut quench_state); } else if braze { braze_ui::show(ui, &mut braze_settings, &mut braze_state); } else if planish { planish_ui::show(ui, &mut planish_settings, &mut planish_state); } else if knurl { knurl_ui::show(ui, &mut knurl_settings, &mut knurl_state); } else 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}"); }