use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering}; use std::sync::Arc; use crossbeam_channel::Receiver; use doux::EngineMetrics; use crate::app::App; use crate::engine::{ build_stream, spawn_sequencer, AnalysisHandle, AudioStreamConfig, LinkState, MidiCommand, PatternChange, ScopeBuffer, SequencerConfig, SequencerHandle, SpectrumBuffer, }; use crate::midi; use crate::model; use crate::settings::Settings; use crate::state::audio::RefreshRate; use crate::state::StagedChange; use crate::theme; pub struct InitArgs { pub samples: Vec, pub output: Option, pub input: Option, pub channels: Option, pub buffer: Option, } pub struct Init { pub app: App, pub link: Arc, pub sequencer: SequencerHandle, pub playing: Arc, pub nudge_us: Arc, pub metrics: Arc, pub scope_buffer: Arc, pub spectrum_buffer: Arc, pub audio_sample_pos: Arc, pub sample_rate_shared: Arc, pub stream: Option, pub analysis_handle: Option, pub midi_rx: Receiver, #[cfg(feature = "desktop")] pub settings: Settings, #[cfg(feature = "desktop")] pub mouse_x: Arc, #[cfg(feature = "desktop")] pub mouse_y: Arc, #[cfg(feature = "desktop")] pub mouse_down: Arc, } pub fn init(args: InitArgs) -> Init { let settings = Settings::load(); let link = Arc::new(LinkState::new(settings.link.tempo, settings.link.quantum)); if settings.link.enabled { link.enable(); } let playing = Arc::new(AtomicBool::new(true)); let nudge_us = Arc::new(AtomicI64::new(0)); let mut app = App::new(); app.playback.queued_changes.push(StagedChange { change: PatternChange::Start { bank: 0, pattern: 0, }, quantization: model::LaunchQuantization::Immediate, sync_mode: model::SyncMode::Reset, }); app.audio.config.output_device = args.output.or(settings.audio.output_device.clone()); app.audio.config.input_device = args.input.or(settings.audio.input_device.clone()); app.audio.config.channels = args.channels.unwrap_or(settings.audio.channels); app.audio.config.buffer_size = args.buffer.unwrap_or(settings.audio.buffer_size); app.audio.config.max_voices = settings.audio.max_voices; app.audio.config.sample_paths = args.samples; app.audio.config.refresh_rate = RefreshRate::from_fps(settings.display.fps); app.ui.runtime_highlight = settings.display.runtime_highlight; app.audio.config.show_scope = settings.display.show_scope; app.audio.config.show_spectrum = settings.display.show_spectrum; app.ui.show_completion = settings.display.show_completion; app.ui.color_scheme = settings.display.color_scheme; app.ui.hue_rotation = settings.display.hue_rotation; app.audio.config.layout = settings.display.layout; let base_theme = settings.display.color_scheme.to_theme(); let rotated = cagire_ratatui::theme::transform::rotate_theme(base_theme, settings.display.hue_rotation); theme::set(rotated); // MIDI connections let outputs = midi::list_midi_outputs(); let inputs = midi::list_midi_inputs(); for (slot, name) in settings.midi.output_devices.iter().enumerate() { if !name.is_empty() { if let Some(idx) = outputs.iter().position(|d| &d.name == name) { let _ = app.midi.connect_output(slot, idx); } } } for (slot, name) in settings.midi.input_devices.iter().enumerate() { if !name.is_empty() { if let Some(idx) = inputs.iter().position(|d| &d.name == name) { let _ = app.midi.connect_input(slot, idx); } } } let metrics = Arc::new(EngineMetrics::default()); let scope_buffer = Arc::new(ScopeBuffer::new()); let spectrum_buffer = Arc::new(SpectrumBuffer::new()); let audio_sample_pos = Arc::new(AtomicU64::new(0)); let sample_rate_shared = Arc::new(AtomicU32::new(44100)); let mut initial_samples = Vec::new(); for path in &app.audio.config.sample_paths { let index = doux::sampling::scan_samples_dir(path); app.audio.config.sample_count += index.len(); initial_samples.extend(index); } let preload_entries: Vec<(String, std::path::PathBuf)> = initial_samples .iter() .map(|e| (e.name.clone(), e.path.clone())) .collect(); #[cfg(feature = "desktop")] let mouse_x = Arc::new(AtomicU32::new(0.5_f32.to_bits())); #[cfg(feature = "desktop")] let mouse_y = Arc::new(AtomicU32::new(0.5_f32.to_bits())); #[cfg(feature = "desktop")] let mouse_down = Arc::new(AtomicU32::new(0.0_f32.to_bits())); let seq_config = SequencerConfig { audio_sample_pos: Arc::clone(&audio_sample_pos), sample_rate: Arc::clone(&sample_rate_shared), cc_access: Some(Arc::new(app.midi.cc_memory.clone()) as Arc), variables: Arc::clone(&app.variables), dict: Arc::clone(&app.dict), #[cfg(feature = "desktop")] mouse_x: Arc::clone(&mouse_x), #[cfg(feature = "desktop")] mouse_y: Arc::clone(&mouse_y), #[cfg(feature = "desktop")] mouse_down: Arc::clone(&mouse_down), }; let (sequencer, initial_audio_rx, midi_rx) = spawn_sequencer( Arc::clone(&link), Arc::clone(&playing), settings.link.quantum, Arc::clone(&app.live_keys), Arc::clone(&nudge_us), seq_config, ); let stream_config = AudioStreamConfig { output_device: app.audio.config.output_device.clone(), channels: app.audio.config.channels, buffer_size: app.audio.config.buffer_size, max_voices: app.audio.config.max_voices, }; let (stream, analysis_handle) = match build_stream( &stream_config, initial_audio_rx, Arc::clone(&scope_buffer), Arc::clone(&spectrum_buffer), Arc::clone(&metrics), initial_samples, Arc::clone(&audio_sample_pos), ) { Ok((s, info, analysis, registry)) => { app.audio.config.sample_rate = info.sample_rate; app.audio.config.host_name = info.host_name; app.audio.config.channels = info.channels; sample_rate_shared.store(info.sample_rate as u32, Ordering::Relaxed); app.audio.sample_registry = Some(Arc::clone(®istry)); if !preload_entries.is_empty() { let sr = info.sample_rate; std::thread::Builder::new() .name("sample-preload".into()) .spawn(move || { preload_sample_heads(preload_entries, sr, ®istry); }) .expect("failed to spawn preload thread"); } (Some(s), Some(analysis)) } Err(e) => { app.ui.set_status(format!("Audio failed: {e}")); app.audio.error = Some(e); (None, None) } }; app.mark_all_patterns_dirty(); Init { app, link, sequencer, playing, nudge_us, metrics, scope_buffer, spectrum_buffer, audio_sample_pos, sample_rate_shared, stream, analysis_handle, midi_rx, #[cfg(feature = "desktop")] settings, #[cfg(feature = "desktop")] mouse_x, #[cfg(feature = "desktop")] mouse_y, #[cfg(feature = "desktop")] mouse_down, } } pub fn preload_sample_heads( entries: Vec<(String, std::path::PathBuf)>, target_sr: f32, registry: &doux::SampleRegistry, ) { let mut batch = Vec::with_capacity(entries.len()); for (name, path) in &entries { match doux::sampling::decode_sample_head(path, target_sr) { Ok(data) => batch.push((name.clone(), Arc::new(data))), Err(e) => eprintln!("preload {name}: {e}"), } } if !batch.is_empty() { registry.insert_batch(batch); } }