Ableton but written in Rust
at main 138 lines 3.0 kB view raw
1use std::{f32::consts::PI, sync::Arc}; 2 3use rodio::{OutputStream, Sink}; 4use wgpu::util::TextureBlitter; 5use winit::{ 6 application::ApplicationHandler, 7 error::EventLoopError, 8 event::WindowEvent, 9 event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, 10 window::Window, 11}; 12use xilem::{ 13 Color, WidgetView, WindowOptions, Xilem, 14 core::{Edit, ViewArgument, lens}, 15 masonry::kurbo::{BezPath, Point, QuadSpline, Shape as _, Stroke}, 16 style::{Padding, Style}, 17 view::{ 18 CrossAxisAlignment, MainAxisAlignment, flex_col, flex_row, label, 19 text_button, 20 }, 21}; 22 23use crate::clip::{Clip, ClipView}; 24 25mod clip; 26 27struct AppState { 28 bpm: f64, 29 sample_rate: u32, 30 clips: Vec<Arc<Clip>>, 31 sink: Sink, 32 stream: OutputStream, 33} 34 35impl AppState { 36 pub fn new() -> Self { 37 let stream = rodio::OutputStreamBuilder::open_default_stream().unwrap(); 38 let sample_rate = stream.config().sample_rate(); 39 40 let sink = rodio::Sink::connect_new(stream.mixer()); 41 sink.pause(); 42 43 Self { 44 bpm: 120.0, 45 clips: vec![ 46 Arc::new(Clip { 47 track: 0, 48 start: 0.0, 49 duration: 2.0, 50 color: Color::from_rgb8(200, 100, 100), 51 data: (0..sample_rate as usize * 2) 52 .map(|i| { 53 (2.0 * PI * 440.0 * (i as f32 / sample_rate as f32)) 54 .sin() * 0.5 55 }) 56 .collect(), 57 }), 58 Arc::new(Clip { 59 track: 1, 60 start: 1.0, 61 duration: 2.0, 62 color: Color::from_rgb8(100, 200, 100), 63 data: (0..sample_rate as usize * 2) 64 .map(|i| { 65 (2.0 * PI * 660.0 * (i as f32 / sample_rate as f32)) 66 .sin() * 0.5 67 }) 68 .collect(), 69 }), 70 ], 71 sample_rate, 72 sink, 73 stream, 74 } 75 } 76} 77 78fn clip_view( 79 state: &mut AppState, 80 index: usize, 81) -> impl WidgetView<Edit<AppState>> + use<> { 82 ClipView::new(state.clips[index].clone(), state.bpm, state.sample_rate) 83} 84 85fn header(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> { 86 flex_row(( 87 label(format!("BPM: {}", state.bpm)), 88 text_button( 89 if state.sink.is_paused() { 90 "Play" 91 } else { 92 "Pause" 93 }, 94 |state: &mut AppState| { 95 if state.sink.is_paused() { 96 state.sink.clear(); 97 for clip in &state.clips { 98 let source = rodio::buffer::SamplesBuffer::new( 99 1, 100 state.sample_rate, 101 clip.data.clone(), 102 ); 103 state.sink.append(source); 104 } 105 state.sink.play(); 106 } else { 107 state.sink.pause(); 108 } 109 }, 110 ), 111 )) 112 .main_axis_alignment(MainAxisAlignment::Start) 113 .cross_axis_alignment(CrossAxisAlignment::Center) 114} 115 116fn app_logic(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> { 117 flex_col(( 118 header(state), 119 (0..state.clips.len()) 120 .map(|i| clip_view(state, i)) 121 .collect::<Vec<_>>(), 122 )) 123 .main_axis_alignment(MainAxisAlignment::Start) 124 .cross_axis_alignment(CrossAxisAlignment::Start) 125 .padding(Padding::all(10.0)) 126} 127 128fn main() -> Result<(), EventLoopError> { 129 tracing_subscriber::fmt::init(); 130 131 let app = Xilem::new_simple( 132 AppState::new(), 133 app_logic, 134 WindowOptions::new("Ableton Clone"), 135 ); 136 app.run_in(EventLoop::with_user_event())?; 137 Ok(()) 138}