diff --git a/Cargo.lock b/Cargo.lock index fc87b2cf..aa3d8003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1818,12 +1818,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "extended" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" - [[package]] name = "fastrand" version = "2.3.0" @@ -5189,20 +5183,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" dependencies = [ "lazy_static", - "symphonia-codec-pcm", + "symphonia-bundle-mp3", "symphonia-core", - "symphonia-format-riff", "symphonia-metadata", ] [[package]] -name = "symphonia-codec-pcm" +name = "symphonia-bundle-mp3" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" dependencies = [ + "lazy_static", "log", "symphonia-core", + "symphonia-metadata", ] [[package]] @@ -5218,18 +5213,6 @@ dependencies = [ "log", ] -[[package]] -name = "symphonia-format-riff" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" -dependencies = [ - "extended", - "log", - "symphonia-core", - "symphonia-metadata", -] - [[package]] name = "symphonia-metadata" version = "0.5.5" @@ -6814,6 +6797,7 @@ dependencies = [ "idmap", "idmap-derive", "log", + "rodio", "serde", "smol", "wayvr-ipc", @@ -6850,7 +6834,6 @@ dependencies = [ "openxr", "ovr_overlay", "regex", - "rodio", "rosc", "rust-embed", "serde", diff --git a/wlx-common/Cargo.toml b/wlx-common/Cargo.toml index 5f66b0d6..61c78dbf 100644 --- a/wlx-common/Cargo.toml +++ b/wlx-common/Cargo.toml @@ -14,6 +14,10 @@ idmap-derive.workspace = true log.workspace = true serde = { workspace = true, features = ["rc"] } xdg.workspace = true - chrono = "0.4.42" smol = "2.0.2" +rodio = { version = "0.21.1", default-features = false, features = [ + "playback", + "mp3", + "hound", +] } diff --git a/wlx-common/src/audio.rs b/wlx-common/src/audio.rs new file mode 100644 index 00000000..50394c88 --- /dev/null +++ b/wlx-common/src/audio.rs @@ -0,0 +1,97 @@ +use std::{collections::HashMap, io::Cursor}; + +use rodio::Source; + +pub struct AudioSystem { + audio_stream: Option, + first_try: bool, +} + +pub struct AudioSample { + buffer: rodio::buffer::SamplesBuffer, +} + +pub struct SamplePlayer { + samples: HashMap, +} + +impl SamplePlayer { + pub fn new() -> Self { + Self { + samples: HashMap::new(), + } + } + + pub fn register_sample(&mut self, sample_name: &str, sample: AudioSample) { + self.samples.insert(String::from(sample_name), sample); + } + + pub fn play_sample(&mut self, system: &mut AudioSystem, sample_name: &str) { + let Some(sample) = self.samples.get(sample_name) else { + log::error!("failed to play sample by name {}", sample_name); + return; + }; + + system.play_sample(sample); + } +} + +impl Default for SamplePlayer { + fn default() -> Self { + Self::new() + } +} + +impl AudioSystem { + pub const fn new() -> Self { + Self { + audio_stream: None, + first_try: true, + } + } + + fn get_handle(&mut self) -> Option<&rodio::OutputStream> { + if self.audio_stream.is_none() && self.first_try { + self.first_try = false; + if let Ok(stream) = rodio::OutputStreamBuilder::open_default_stream() { + self.audio_stream = Some(stream); + } else { + log::error!("Failed to open audio stream. Audio will not work."); + return None; + } + } + self.audio_stream.as_ref() + } + + pub fn play_sample(&mut self, sample: &AudioSample) -> Option<()> { + let handle = self.get_handle()?; + handle.mixer().add(sample.buffer.clone()); + Some(()) + } +} + +impl Default for AudioSystem { + fn default() -> Self { + Self::new() + } +} + +impl AudioSample { + pub fn from_mp3(encoded_bin: &[u8]) -> anyhow::Result { + // SAFETY: this is safe + // rodio requires us to provide 'static data to decode it + // we are casting &T into &'static T just to prevent unnecessary memory copy into Vec. + // `encoded_bin` data will be always valid, because we are dropping `decoder` in this scope afterwards. + // Compliant and slower version would be: Cursor::new(encoded_bin.to_vec()) + let cursor = unsafe { Cursor::new(std::mem::transmute::<&[u8], &'static [u8]>(encoded_bin)) }; + + let decoder = rodio::Decoder::new_mp3(cursor)?; + Ok(Self { + buffer: rodio::buffer::SamplesBuffer::new( + decoder.channels(), + decoder.sample_rate(), + decoder.collect::>(), + ), + }) + } +} diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index ce87a795..077a5ce8 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -136,9 +136,6 @@ pub struct GeneralConfig { #[serde(default)] pub notification_topics: IdMap, - #[serde(default = "def_empty")] - pub notification_sound: Arc, - #[serde(default = "def_true")] pub keyboard_sound_enabled: bool, diff --git a/wlx-common/src/lib.rs b/wlx-common/src/lib.rs index 287c1c5c..a5697a43 100644 --- a/wlx-common/src/lib.rs +++ b/wlx-common/src/lib.rs @@ -1,4 +1,5 @@ pub mod astr_containers; +pub mod audio; pub mod cache_dir; pub mod common; pub mod config; diff --git a/wlx-overlay-s/Cargo.toml b/wlx-overlay-s/Cargo.toml index 310e0aa1..0b730559 100644 --- a/wlx-overlay-s/Cargo.toml +++ b/wlx-overlay-s/Cargo.toml @@ -30,7 +30,7 @@ anyhow.workspace = true clap.workspace = true glam = { workspace = true, features = ["mint", "serde"] } idmap = { workspace = true, features = ["serde"] } -idmap-derive. workspace = true +idmap-derive.workspace = true log.workspace = true rust-embed.workspace = true regex.workspace = true @@ -68,11 +68,6 @@ ovr_overlay = { features = [ "ovr_input", "ovr_system", ], git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "8d62c73d5f17e4210d6d0cd52e7f3953eb9b481a", optional = true } -rodio = { version = "0.21.1", default-features = false, features = [ - "playback", - "wav", - "hound", -] } rosc = { version = "0.11.4", optional = true } serde_json5 = "0.2.1" serde_yaml = "0.9.34" diff --git a/wlx-overlay-s/src/overlays/keyboard/mod.rs b/wlx-overlay-s/src/overlays/keyboard/mod.rs index 253d5c8c..e7342ec7 100644 --- a/wlx-overlay-s/src/overlays/keyboard/mod.rs +++ b/wlx-overlay-s/src/overlays/keyboard/mod.rs @@ -325,10 +325,9 @@ impl KeyboardState { } } -const KEY_AUDIO_WAV: &[u8] = include_bytes!("../../res/421581.wav"); - fn play_key_click(app: &mut AppState) { - app.audio_provider.play(KEY_AUDIO_WAV); + app.audio_sample_player + .play_sample(&mut app.audio_system, "key_click"); } struct KeyState { diff --git a/wlx-overlay-s/src/overlays/toast.rs b/wlx-overlay-s/src/overlays/toast.rs index 7ad73a4b..6e4bb1ae 100644 --- a/wlx-overlay-s/src/overlays/toast.rs +++ b/wlx-overlay-s/src/overlays/toast.rs @@ -67,7 +67,8 @@ impl Toast { let destroy_at = instant.add(std::time::Duration::from_secs_f32(self.timeout)); if self.sound && app.session.config.notifications_sound_enabled { - app.audio_provider.play(app.toast_sound); + app.audio_sample_player + .play_sample(&mut app.audio_system, "toast"); } // drop any toast that was created before us. diff --git a/wlx-overlay-s/src/res/380885.wav b/wlx-overlay-s/src/res/380885.wav deleted file mode 100644 index be1b8046..00000000 Binary files a/wlx-overlay-s/src/res/380885.wav and /dev/null differ diff --git a/wlx-overlay-s/src/res/421581.wav b/wlx-overlay-s/src/res/421581.wav deleted file mode 100644 index 11b767e4..00000000 Binary files a/wlx-overlay-s/src/res/421581.wav and /dev/null differ diff --git a/wlx-overlay-s/src/res/557297.wav b/wlx-overlay-s/src/res/557297.wav deleted file mode 100644 index 6bc3ef84..00000000 Binary files a/wlx-overlay-s/src/res/557297.wav and /dev/null differ diff --git a/wlx-overlay-s/src/res/660533.wav b/wlx-overlay-s/src/res/660533.wav deleted file mode 100644 index 3910bdf0..00000000 Binary files a/wlx-overlay-s/src/res/660533.wav and /dev/null differ diff --git a/wlx-overlay-s/src/res/key_click.mp3 b/wlx-overlay-s/src/res/key_click.mp3 new file mode 100644 index 00000000..5e0f1e08 Binary files /dev/null and b/wlx-overlay-s/src/res/key_click.mp3 differ diff --git a/wlx-overlay-s/src/res/toast.mp3 b/wlx-overlay-s/src/res/toast.mp3 new file mode 100644 index 00000000..a3ab62b1 Binary files /dev/null and b/wlx-overlay-s/src/res/toast.mp3 differ diff --git a/wlx-overlay-s/src/state.rs b/wlx-overlay-s/src/state.rs index 0f9df04b..16efb0fd 100644 --- a/wlx-overlay-s/src/state.rs +++ b/wlx-overlay-s/src/state.rs @@ -7,6 +7,7 @@ use wgui::{ renderer_vk::context::SharedContext as WSharedContext, }; use wlx_common::{ + audio, config::GeneralConfig, overlays::{ToastDisplayMethod, ToastTopic}, }; @@ -26,7 +27,7 @@ use crate::{ graphics::WGfxExtras, gui, ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal}, - subsystem::{audio::AudioOutput, dbus::DbusConnector, input::HidWrapper}, + subsystem::{dbus::DbusConnector, input::HidWrapper}, }; pub struct AppState { @@ -36,7 +37,9 @@ pub struct AppState { pub gfx: Arc, pub gfx_extras: WGfxExtras, pub hid_provider: HidWrapper, - pub audio_provider: AudioOutput, + + pub audio_system: audio::AudioSystem, + pub audio_sample_player: audio::SamplePlayer, pub wgui_shared: WSharedContext, @@ -44,7 +47,6 @@ pub struct AppState { pub screens: SmallVec<[ScreenMeta; 8]>, pub anchor: Affine3A, pub anchor_grabbed: bool, - pub toast_sound: &'static [u8], pub wgui_globals: WguiGlobals, @@ -93,14 +95,20 @@ impl AppState { #[cfg(feature = "osc")] let osc_sender = crate::subsystem::osc::OscSender::new(session.config.osc_out_port).ok(); - let toast_sound_wav = Self::try_load_bytes( - &session.config.notification_sound, - include_bytes!("res/557297.wav"), - ); - let wgui_shared = WSharedContext::new(gfx.clone())?; let theme = session.config.theme_path.clone(); + let mut audio_sample_player = audio::SamplePlayer::new(); + audio_sample_player.register_sample( + "key_click", + audio::AudioSample::from_mp3(include_bytes!("res/key_click.mp3"))?, + ); + + audio_sample_player.register_sample( + "toast", + audio::AudioSample::from_mp3(include_bytes!("res/toast.mp3"))?, + ); + let mut defaults = wgui::globals::Defaults::default(); { @@ -131,13 +139,13 @@ impl AppState { gfx, gfx_extras, hid_provider, - audio_provider: AudioOutput::new(), + audio_system: audio::AudioSystem::new(), + audio_sample_player, wgui_shared, input_state: InputState::new(), screens: smallvec![], anchor: Affine3A::IDENTITY, anchor_grabbed: false, - toast_sound: toast_sound_wav, wgui_globals: WguiGlobals::new( Box::new(gui::asset::GuiAsset {}), defaults, @@ -156,29 +164,6 @@ impl AppState { wvr_server: wayvr_server, }) } - - pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] { - if path.is_empty() { - return fallback_data; - } - - let real_path = config_io::get_config_root().join(path); - - if std::fs::File::open(real_path.clone()).is_err() { - log::warn!("Could not open file at: {path}"); - return fallback_data; - } - - match std::fs::read(real_path) { - // Box is used here to work around `f`'s limited lifetime - Ok(f) => Box::leak(Box::new(f)).as_slice(), - Err(e) => { - log::warn!("Failed to read file at: {path}"); - log::warn!("{e:?}"); - fallback_data - } - } - } } pub struct AppSession { diff --git a/wlx-overlay-s/src/subsystem/audio.rs b/wlx-overlay-s/src/subsystem/audio.rs deleted file mode 100644 index 5fe2f614..00000000 --- a/wlx-overlay-s/src/subsystem/audio.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::io::Cursor; - -use rodio::{Decoder, OutputStreamBuilder, stream::OutputStream}; - -pub struct AudioOutput { - audio_stream: Option, - first_try: bool, -} - -impl AudioOutput { - pub const fn new() -> Self { - Self { - audio_stream: None, - first_try: true, - } - } - - fn get_handle(&mut self) -> Option<&OutputStream> { - if self.audio_stream.is_none() && self.first_try { - self.first_try = false; - if let Ok(stream) = OutputStreamBuilder::open_default_stream() { - self.audio_stream = Some(stream); - } else { - log::error!("Failed to open audio stream. Audio will not work."); - return None; - } - } - self.audio_stream.as_ref() - } - - pub fn play(&mut self, wav_bytes: &'static [u8]) { - let Some(handle) = self.get_handle() else { - return; - }; - let cursor = Cursor::new(wav_bytes); - let source = match Decoder::new_wav(cursor) { - Ok(source) => source, - Err(e) => { - log::error!("Failed to play sound: {e:?}"); - return; - } - }; - let () = handle.mixer().add(source); - } -} diff --git a/wlx-overlay-s/src/subsystem/mod.rs b/wlx-overlay-s/src/subsystem/mod.rs index bfeec33c..abf66273 100644 --- a/wlx-overlay-s/src/subsystem/mod.rs +++ b/wlx-overlay-s/src/subsystem/mod.rs @@ -1,4 +1,3 @@ -pub mod audio; pub mod dbus; pub mod hid; pub mod input;