From 9dc1f8f549c66c032d1a8af3da97acb9e4c00cdb Mon Sep 17 00:00:00 2001 From: Louis Pearson Date: Sat, 31 Aug 2019 00:37:09 -0600 Subject: [PATCH] Revamp AudioFrontend audio::base had a lot of unnescessary abstractions and constructs. The current code simplifies the API in a way that makes sense and that will allow sound effects and fading to be added in an incremental way. --- voxygen/src/audio/mod.rs | 184 +++++++++++++++++++++++++---- voxygen/src/hud/settings_window.rs | 50 +++----- voxygen/src/main.rs | 56 ++++----- voxygen/src/menu/main/mod.rs | 2 + voxygen/src/session.rs | 4 +- 5 files changed, 200 insertions(+), 96 deletions(-) diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 7de8428049..b4c6c6c6ed 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -1,41 +1,173 @@ -pub mod base; -use base::{Genre, Jukebox}; +use common::assets; +use rodio::{Decoder, Device, Sink, SpatialSink}; + +const LEFT_EAR : [f32; 3] = [1.0, 0.0, 0.0]; +const RIGHT_EAR : [f32; 3] = [-1.0, 0.0, 0.0]; + +#[derive(PartialEq)] +enum AudioType { + Sfx, + Music, +} + +struct Channel { + id: usize, + sink: SpatialSink, + audio_type: AudioType, +} pub struct AudioFrontend { - pub(crate) model: Jukebox, - pub(crate) default_device: String, - pub(crate) device_list: Vec, + pub device: String, + pub device_list: Vec, + audio_device: Option, + + channels: Vec, + next_channel_id: usize, + + sfx_volume: f32, + music_volume: f32, } impl AudioFrontend { - pub(crate) fn new() -> Self { + /// Construct with given device + pub fn new(device: String) -> Self { Self { - model: Jukebox::new(Genre::Bgm), - default_device: base::get_default_device(), - device_list: base::list_devices(), + device: device.clone(), + device_list: list_devices(), + audio_device: get_device_raw(device), + channels: Vec::new(), + next_channel_id: 0, + sfx_volume: 1.0, + music_volume: 1.0, } } - /// Play audio. - pub(crate) fn play(&mut self) { - let path = base::select_random_music(&Genre::Bgm); - - match self.model.player.is_paused() { - true => match self.model.get_genre() { - Genre::Bgm => self.model.player.resume(), - Genre::Sfx => unimplemented!(), // TODO: add support for sound effects. - Genre::None => (), - }, - false => self.model.player.load(&path), + /// Construct in `no-audio` mode for debugging + pub fn no_audio() -> Self { + Self { + device: "none".to_string(), + device_list: list_devices(), + audio_device: None, + channels: Vec::new(), + next_channel_id: 0, + sfx_volume: 1.0, + music_volume: 1.0, } } - /// Construct in `no-audio` mode for debugging. - pub(crate) fn no_audio() -> Self { - Self { - model: Jukebox::new(Genre::None), - default_device: "None".to_owned(), - device_list: Vec::new(), + /// Maintain audio + pub fn maintain(&mut self) { + let mut stopped_channels = Vec::::new(); + for (i, channel) in self.channels.iter().enumerate() { + if channel.sink.empty() { + stopped_channels.push(i); + } } + for i in stopped_channels.iter().rev() { + self.channels.remove(*i); + } + } + + /// Play specfied sound file. + ///```ignore + ///audio.play_sound("voxygen.audio.sfx.step"); + ///``` + pub fn play_sound(&mut self, sound: String) -> usize { + let id = self.next_channel_id; + self.next_channel_id += 1; + + if let Some(device) = &self.audio_device { + let sink = SpatialSink::new(device, [0.0, 0.0, 0.0], LEFT_EAR, RIGHT_EAR); + + let file = assets::load_file(&sound, &["wav", "ogg"]).unwrap(); + let sound = rodio::Decoder::new(file).unwrap(); + + sink.append(sound); + + self.channels.push(Channel { + id, + sink, + audio_type: AudioType::Music, + }); + } + + id + } + + pub fn get_sfx_volume(&self) -> f32 { + self.sfx_volume + } + + pub fn get_music_volume(&self) -> f32 { + self.music_volume + } + + pub fn set_sfx_volume(&mut self, volume: f32) { + self.sfx_volume = volume; + + for channel in self.channels.iter() { + if channel.audio_type == AudioType::Sfx { + channel.sink.set_volume(volume); + } + } + } + + pub fn set_music_volume(&mut self, volume: f32) { + self.music_volume = volume; + + for channel in self.channels.iter() { + if channel.audio_type == AudioType::Music { + channel.sink.set_volume(volume); + } + } + } + + pub fn set_device(&mut self, name: String) { + self.device = name.clone(); + self.audio_device = get_device_raw(name); } } + +pub fn select_random_music() -> String { + let soundtracks = load_soundtracks(); + let index = rand::random::() % soundtracks.len(); + soundtracks[index].clone() +} + +/// Returns the default audio device. +/// Does not return rodio Device struct in case our audio backend changes. +pub fn get_default_device() -> String { + rodio::default_output_device() + .expect("No audio output devices detected.") + .name() +} + +/// Load the audio file directory selected by genre. +pub fn load_soundtracks() -> Vec { + let assets = assets::read_dir("voxygen.audio.soundtrack").unwrap(); + let soundtracks = assets + .filter_map(|entry| { + entry.ok().map(|f| { + let path = f.path(); + path.to_string_lossy().into_owned() + }) + }) + .collect::>(); + + soundtracks +} + +/// Returns a vec of the audio devices available. +/// Does not return rodio Device struct in case our audio backend changes. +pub fn list_devices() -> Vec { + list_devices_raw().iter().map(|x| x.name()).collect() +} + +/// Returns vec of devices +fn list_devices_raw() -> Vec { + rodio::output_devices().collect() +} + +fn get_device_raw(device: String) -> Option { + rodio::output_devices().find(|d| d.name() == device) +} diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index fbb64e05b2..682eb3dfd9 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -2,7 +2,6 @@ use super::{ img_ids::Imgs, BarNumbers, CrosshairType, Fonts, ShortcutNumbers, Show, XpBar, TEXT_COLOR, }; use crate::{ - audio::base::Genre, ui::{ImageSlider, ScaleMode, ToggleButton}, GlobalState, }; @@ -1251,39 +1250,26 @@ impl<'a> Widget for SettingsWindow<'a> { } // Audio Device Selector -------------------------------------------- - match self.global_state.audio.model.get_genre() { - Genre::Bgm => { - let device = &self.global_state.audio.default_device; - let device_list = &self.global_state.audio.device_list; - Text::new("Volume") - .down_from(state.ids.audio_volume_slider, 10.0) - .font_size(14) - .font_id(self.fonts.opensans) - .color(TEXT_COLOR) - .set(state.ids.audio_device_text, ui); + let device = &self.global_state.audio.device; + let device_list = &self.global_state.audio.device_list; + Text::new("Volume") + .down_from(state.ids.audio_volume_slider, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.audio_device_text, ui); - // Get which device is currently selected - let selected = device_list.iter().position(|x| x.contains(device)); + // Get which device is currently selected + let selected = device_list.iter().position(|x| x.contains(device)); - if let Some(clicked) = DropDownList::new(&device_list, selected) - .w_h(400.0, 22.0) - .down_from(state.ids.audio_device_text, 10.0) - .label_font_id(self.fonts.opensans) - .set(state.ids.audio_device_list, ui) - { - let new_val = device_list[clicked].clone(); - events.push(Event::ChangeAudioDevice(new_val)); - } - } - Genre::Sfx => unimplemented!(), - Genre::None => { - Text::new("Volume") - .down_from(state.ids.audio_volume_slider, 10.0) - .font_size(14) - .font_id(self.fonts.opensans) - .color(TEXT_COLOR) - .set(state.ids.audio_device_text, ui); - } + if let Some(clicked) = DropDownList::new(&device_list, selected) + .w_h(400.0, 22.0) + .down_from(state.ids.audio_device_text, 10.0) + .label_font_id(self.fonts.opensans) + .set(state.ids.audio_device_list, ui) + { + let new_val = device_list[clicked].clone(); + events.push(Event::ChangeAudioDevice(new_val)); } } diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 4afeda5723..949f1d91d2 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -32,7 +32,7 @@ pub mod window; pub use crate::error::Error; use crate::{ - audio::base::Genre, audio::AudioFrontend, menu::main::MainMenuState, settings::Settings, + audio::AudioFrontend, menu::main::MainMenuState, settings::Settings, window::Window, }; use heaptrack::track_mem; @@ -59,8 +59,7 @@ impl GlobalState { } pub fn maintain(&mut self) { - // TODO: Maintain both `Bgm` and `Sfx` audio threads. - self.audio.play(); + self.audio.maintain(); } } @@ -104,10 +103,28 @@ lazy_static! { fn main() { // Load the settings let settings = Settings::load(); +<<<<<<< HEAD // Save settings to add new fields or create the file if it is not already there if let Err(err) = settings.save_to_file() { panic!("Failed to save settings: {:?}", err); } +======= + let audio_device = match &settings.audio.audio_device { + Some(d) => d.to_string(), + None => audio::get_default_device(), + }; + let audio = if settings.audio.audio_on { + AudioFrontend::new(audio_device) + } else { + AudioFrontend::no_audio() + }; + + let mut global_state = GlobalState { + audio, + window: Window::new(&settings).expect("Failed to create window!"), + settings, + }; +>>>>>>> Revamp AudioFrontend // Initialize logging. let term_log_level = std::env::var_os("VOXYGEN_LOG") @@ -186,39 +203,6 @@ fn main() { default_hook(panic_info); })); - // Set up the global state. - let audio = if settings.audio.audio_on { - AudioFrontend::new() - } else { - AudioFrontend::no_audio() - }; - - let mut global_state = GlobalState { - audio, - window: Window::new(&settings).expect("Failed to create window!"), - settings, - }; - - // Initialize discord. (lazy_static initalise lazily...) - #[cfg(feature = "discord")] - { - match DISCORD_INSTANCE.lock() { - Ok(_disc) => { - //great - } - Err(e) => log::error!("Couldn't init discord: {}", e), - } - } - - match global_state.audio.model.get_genre() { - Genre::Bgm => { - global_state.settings.audio.audio_device = - Some(crate::audio::base::get_default_device()) - } - Genre::Sfx => unimplemented!(), - Genre::None => global_state.settings.audio.audio_device = None, - } - // Set up the initial play state. let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; states diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index eb2ce26018..facfae1020 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -34,6 +34,8 @@ impl PlayState for MainMenuState { // Used for client creation. let mut client_init: Option = None; + global_state.audio.play_sound("voxygen.audio.soundtrack.veloren_title_tune-3".to_string()); + loop { // Handle window events. for event in global_state.window.fetch_events() { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7555b8efc7..e60550cc7f 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -357,13 +357,13 @@ impl PlayState for SessionState { } HudEvent::AdjustVolume(volume) => { - global_state.audio.model.player.set_volume(volume); + global_state.audio.set_music_volume(volume); global_state.settings.audio.music_volume = volume; global_state.settings.save_to_file_warn(); } HudEvent::ChangeAudioDevice(name) => { - global_state.audio.model.player.set_device(&name.clone()); + global_state.audio.set_device(name.clone()); global_state.settings.audio.audio_device = Some(name); global_state.settings.save_to_file_warn();