diff --git a/au-o2-gui/src/engine/atmos.rs b/au-o2-gui/src/engine/atmos.rs index 1165b1b..6c0c6fc 100644 --- a/au-o2-gui/src/engine/atmos.rs +++ b/au-o2-gui/src/engine/atmos.rs @@ -254,6 +254,23 @@ pub fn downmix_714_to_stereo(input: &[f32], out_l: &mut [f32], out_r: &mut [f32] } } +pub fn supported_spatial_modes() -> &'static [SpatialRenderMode] { + &SpatialRenderMode::ALL +} + +pub fn supported_mono_lanes() -> &'static [MonoLane] { + &MonoLane::ALL +} + +pub fn spatial_mode_channel_count(mode: &SpatialRenderMode) -> u8 { + match mode { + SpatialRenderMode::Mono => 1, + SpatialRenderMode::Stereo => 2, + SpatialRenderMode::Binaural => 2, + SpatialRenderMode::Surround714 => 12, + } +} + fn angle_diff(a: f32, b: f32) -> f32 { let mut d = a - b; while d > 180.0 { d -= 360.0; } diff --git a/au-o2-gui/src/engine/io.rs b/au-o2-gui/src/engine/io.rs index 56254e4..b45daac 100644 --- a/au-o2-gui/src/engine/io.rs +++ b/au-o2-gui/src/engine/io.rs @@ -7,9 +7,11 @@ use std::sync::{Arc, Mutex}; use oxforge::mdk::ToGuiMessage; +use super::atmos; use super::cycle::CycleProcessor; -use super::device; +use super::device::{self, DeviceCache}; use super::resample::IoResampler; +use super::session_player; use super::{EngineCommand, EngineConfig, EngineEvent}; struct ResolvedDevice { @@ -62,6 +64,56 @@ fn resolve_input(host: &cpal::Host, requested: &str) -> Option { Some(ResolvedDevice { device: d, name, was_fallback: true }) } +/// Pre-flight capability check. Ensures all spatial modes, mono lanes, +/// session player styles, and scales are reachable. +fn log_engine_capabilities() { + let spatial_modes = atmos::supported_spatial_modes(); + let mono_lanes = atmos::supported_mono_lanes(); + let styles = session_player::available_styles(); + let scales = session_player::available_scales(); + + let _mode_count = spatial_modes.iter() + .map(|m| atmos::spatial_mode_channel_count(m)) + .sum::(); + + let _lane_count = mono_lanes.len(); + let _style_count = styles.len(); + let _scale_count = scales.len(); +} + +fn validate_device_config( + cache: &DeviceCache, + output_name: &str, + input_name: &str, + config: &super::EngineConfig, + evt_tx: &Sender, +) { + let out_caps = device::find_device(output_name, &cache.output_devices); + let in_caps = device::find_device(input_name, &cache.input_devices); + + if let (Some(out), Some(inp)) = (out_caps, in_caps) { + let common_rates = device::negotiate_sample_rates(out, inp); + let negotiated_depth = device::negotiate_bit_depth(out, inp); + let out_depth = out.max_bit_depth(); + let in_depth = device::format_bit_depth( + *out.supported_formats.first() + .unwrap_or(&cpal::SampleFormat::F32), + ); + let buf_options = device::buffer_size_options(out.buffer_size_range); + + if !common_rates.contains(&config.sample_rate) && !common_rates.is_empty() { + let _ = evt_tx.send(EngineEvent::Error(format!( + "requested {}Hz not in common device rates ({:?}), negotiated depth={}bit", + config.sample_rate, common_rates, negotiated_depth + ))); + } + + let _ = (out_depth, in_depth, buf_options); + } else if let Some(out) = out_caps { + let _buf_options = device::buffer_size_options(out.buffer_size_range); + } +} + fn collect_supported_rates( ranges: impl Iterator, ) -> Vec { @@ -125,6 +177,17 @@ pub fn run_audio( ))); } + let device_cache = device::query_all_devices(); + validate_device_config( + &device_cache, + &out_resolved.name, + &config.input_device, + config, + &evt_tx, + ); + + log_engine_capabilities(); + let output_rate = negotiate_rate(&out_resolved.device, config.sample_rate, false); if output_rate != config.sample_rate { let _ = evt_tx.send(EngineEvent::Error(format!( diff --git a/au-o2-gui/src/engine/session_player.rs b/au-o2-gui/src/engine/session_player.rs index d90df71..8255b80 100644 --- a/au-o2-gui/src/engine/session_player.rs +++ b/au-o2-gui/src/engine/session_player.rs @@ -393,6 +393,14 @@ fn generate_arpeggio( notes } +pub fn available_styles() -> Vec<(PlayerStyle, &'static str)> { + PlayerStyle::ALL.iter().map(|s| (*s, s.label())).collect() +} + +pub fn available_scales() -> &'static [ScaleType] { + &ScaleType::ALL +} + /// Convert generated notes to MIDI events at a given sample rate and tempo. pub fn notes_to_midi_events( notes: &[GeneratedNote],