Files
Cagire/src/state/audio.rs
Raphaël Forment af81c94207
Some checks failed
Deploy Website / deploy (push) Failing after 4m46s
WIP: even more crazy linux optimizations
2026-02-03 00:38:46 +01:00

337 lines
8.4 KiB
Rust

use doux::audio::AudioDeviceInfo;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::CyclicEnum;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum MainLayout {
#[default]
Top,
Bottom,
Left,
Right,
}
impl CyclicEnum for MainLayout {
const VARIANTS: &'static [Self] = &[Self::Top, Self::Bottom, Self::Left, Self::Right];
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum RefreshRate {
#[default]
Fps60,
Fps30,
Fps15,
}
impl RefreshRate {
pub fn from_fps(fps: u32) -> Self {
if fps >= 60 {
RefreshRate::Fps60
} else if fps >= 30 {
RefreshRate::Fps30
} else {
RefreshRate::Fps15
}
}
pub fn toggle(self) -> Self {
match self {
RefreshRate::Fps60 => RefreshRate::Fps30,
RefreshRate::Fps30 => RefreshRate::Fps15,
RefreshRate::Fps15 => RefreshRate::Fps60,
}
}
pub fn millis(self) -> u64 {
match self {
RefreshRate::Fps60 => 16,
RefreshRate::Fps30 => 33,
RefreshRate::Fps15 => 66,
}
}
pub fn label(self) -> &'static str {
match self {
RefreshRate::Fps60 => "60",
RefreshRate::Fps30 => "30",
RefreshRate::Fps15 => "15",
}
}
pub fn to_fps(self) -> u32 {
match self {
RefreshRate::Fps60 => 60,
RefreshRate::Fps30 => 30,
RefreshRate::Fps15 => 15,
}
}
}
#[derive(Clone)]
pub struct AudioConfig {
pub output_device: Option<String>,
pub input_device: Option<String>,
pub channels: u16,
pub buffer_size: u32,
pub max_voices: usize,
pub sample_rate: f32,
pub sample_paths: Vec<PathBuf>,
pub sample_count: usize,
pub refresh_rate: RefreshRate,
pub show_scope: bool,
pub show_spectrum: bool,
pub lookahead_ms: u32,
pub layout: MainLayout,
}
impl Default for AudioConfig {
fn default() -> Self {
Self {
output_device: None,
input_device: None,
channels: 2,
buffer_size: 512,
max_voices: 32,
sample_rate: 44100.0,
sample_paths: Vec::new(),
sample_count: 0,
refresh_rate: RefreshRate::default(),
show_scope: true,
show_spectrum: true,
lookahead_ms: 15,
layout: MainLayout::default(),
}
}
}
pub struct ListSelectState {
pub cursor: usize,
pub scroll_offset: usize,
}
impl ListSelectState {
pub fn move_up(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
if self.cursor < self.scroll_offset {
self.scroll_offset = self.cursor;
}
}
}
pub fn move_down(&mut self, item_count: usize) {
if self.cursor + 1 < item_count {
self.cursor += 1;
if self.cursor >= self.scroll_offset + 5 {
self.scroll_offset = self.cursor - 4;
}
}
}
pub fn page_up(&mut self) {
self.cursor = self.cursor.saturating_sub(5);
if self.cursor < self.scroll_offset {
self.scroll_offset = self.cursor;
}
}
pub fn page_down(&mut self, item_count: usize) {
self.cursor = (self.cursor + 5).min(item_count.saturating_sub(1));
if self.cursor >= self.scroll_offset + 5 {
self.scroll_offset = self.cursor.saturating_sub(4);
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum EngineSection {
#[default]
Devices,
Settings,
Samples,
}
impl CyclicEnum for EngineSection {
const VARIANTS: &'static [Self] = &[Self::Devices, Self::Settings, Self::Samples];
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum DeviceKind {
#[default]
Output,
Input,
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum SettingKind {
#[default]
Channels,
BufferSize,
Polyphony,
Nudge,
Lookahead,
}
impl CyclicEnum for SettingKind {
const VARIANTS: &'static [Self] = &[
Self::Channels,
Self::BufferSize,
Self::Polyphony,
Self::Nudge,
Self::Lookahead,
];
}
pub struct Metrics {
pub event_count: usize,
pub dropped_events: usize,
pub active_voices: usize,
pub peak_voices: usize,
pub cpu_load: f32,
pub schedule_depth: usize,
pub scope: [f32; 256],
pub peak_left: f32,
pub peak_right: f32,
pub spectrum: [f32; 32],
pub nudge_ms: f64,
}
impl Default for Metrics {
fn default() -> Self {
Self {
event_count: 0,
dropped_events: 0,
active_voices: 0,
peak_voices: 0,
cpu_load: 0.0,
schedule_depth: 0,
scope: [0.0; 256],
peak_left: 0.0,
peak_right: 0.0,
spectrum: [0.0; 32],
nudge_ms: 0.0,
}
}
}
pub struct AudioSettings {
pub config: AudioConfig,
pub section: EngineSection,
pub device_kind: DeviceKind,
pub setting_kind: SettingKind,
pub output_devices: Vec<AudioDeviceInfo>,
pub input_devices: Vec<AudioDeviceInfo>,
pub output_list: ListSelectState,
pub input_list: ListSelectState,
pub restart_pending: bool,
pub error: Option<String>,
}
impl Default for AudioSettings {
fn default() -> Self {
Self {
config: AudioConfig::default(),
section: EngineSection::default(),
device_kind: DeviceKind::default(),
setting_kind: SettingKind::default(),
output_devices: doux::audio::list_output_devices(),
input_devices: doux::audio::list_input_devices(),
output_list: ListSelectState {
cursor: 0,
scroll_offset: 0,
},
input_list: ListSelectState {
cursor: 0,
scroll_offset: 0,
},
restart_pending: false,
error: None,
}
}
}
impl AudioSettings {
pub fn refresh_devices(&mut self) {
self.output_devices = doux::audio::list_output_devices();
self.input_devices = doux::audio::list_input_devices();
}
pub fn next_section(&mut self) {
self.section = self.section.next();
}
pub fn prev_section(&mut self) {
self.section = self.section.prev();
}
pub fn current_output_device_index(&self) -> usize {
match &self.config.output_device {
Some(name) => self
.output_devices
.iter()
.position(|d| &d.name == name)
.unwrap_or(0),
None => self
.output_devices
.iter()
.position(|d| d.is_default)
.unwrap_or(0),
}
}
pub fn current_input_device_index(&self) -> usize {
match &self.config.input_device {
Some(name) => self
.input_devices
.iter()
.position(|d| &d.name == name)
.unwrap_or(0),
None => self
.input_devices
.iter()
.position(|d| d.is_default)
.unwrap_or(0),
}
}
pub fn adjust_channels(&mut self, delta: i16) {
let new_val = (self.config.channels as i16 + delta).clamp(1, 64) as u16;
self.config.channels = new_val;
}
pub fn adjust_buffer_size(&mut self, delta: i32) {
let new_val = (self.config.buffer_size as i32 + delta).clamp(64, 4096) as u32;
self.config.buffer_size = new_val;
}
pub fn adjust_max_voices(&mut self, delta: i32) {
let new_val = (self.config.max_voices as i32 + delta).clamp(1, 128) as usize;
self.config.max_voices = new_val;
}
pub fn adjust_lookahead(&mut self, delta: i32) {
let new_val = (self.config.lookahead_ms as i32 + delta).clamp(0, 50) as u32;
self.config.lookahead_ms = new_val;
}
pub fn toggle_refresh_rate(&mut self) {
self.config.refresh_rate = self.config.refresh_rate.toggle();
}
pub fn add_sample_path(&mut self, path: PathBuf) {
if !self.config.sample_paths.contains(&path) {
self.config.sample_paths.push(path);
}
}
pub fn remove_last_sample_path(&mut self) {
self.config.sample_paths.pop();
}
pub fn trigger_restart(&mut self) {
self.restart_pending = true;
}
}