diff --git a/assets/voxygen/audio/soundtrack/field_grazing.ogg b/assets/voxygen/audio/soundtrack/field_grazing.ogg new file mode 100644 index 0000000000..b3b1115318 --- /dev/null +++ b/assets/voxygen/audio/soundtrack/field_grazing.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31abe12210d7eaea3fbdd9e52102f63711b8227c31985dac9cf2da319e24fed0 +size 8645063 diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index e6c85494ee..8af09d54dd 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -1,3 +1,4 @@ +use crate::settings::AudioSettings; use common::assets; use rand::prelude::*; use rodio::{Decoder, Device, Source, SpatialSink}; @@ -5,6 +6,7 @@ use std::{ collections::HashMap, fs::File, io::BufReader, + iter::{Filter, Iterator}, path::PathBuf, sync::mpsc::{channel, Receiver, Sender, TryRecvError}, thread, @@ -15,30 +17,31 @@ use vek::*; pub struct AudioFrontend { device: Device, + // Performance optimisation, iterating through available audio devices takes time + devices: Vec, // streams: HashMap, //always use SpatialSink even if no possition is used for now stream: SpatialSink, } impl AudioFrontend { - pub fn new() -> Self { - let mut device = rodio::default_output_device().unwrap(); - - for d in rodio::devices() { - if d.name().contains("jack") { - continue; - } - - device = d; - break; - } + pub fn new(settings: &AudioSettings) -> Self { + let mut device = match &settings.audio_device { + Some(dev) => rodio::output_devices() + .find(|x| &x.name() == dev) + .or_else(rodio::default_output_device) + .expect("No Audio devices found"), + None => rodio::default_output_device().expect("No audio devices found"), + }; let mut sink = rodio::SpatialSink::new(&device, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]); + sink.set_volume(settings.music_volume); AudioFrontend { device, // streams: HashMap::::new(), stream: sink, + devices: AudioFrontend::list_devices_raw(), } } @@ -46,27 +49,20 @@ impl AudioFrontend { let bufreader = assets::load_from_path(path).unwrap(); let src = Decoder::new(bufreader).unwrap(); - let mut sink = rodio::SpatialSink::new( - &self.device, - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [-1.0, 0.0, 0.0], - ); - - sink.append(src); - - // self.streams.insert(path.to_string(), sink); - self.stream = sink; + // TODO: stop previous audio from playing. Sink has this ability, but + // SpatialSink does not for some reason. This means that we will + // probably want to use sinks for music, and SpatialSink for sfx. + self.stream.append(src); } pub fn maintain(&mut self) { let music = [ "voxygen/audio/soundtrack/Ethereal_Bonds.ogg", - "voxygen/audio/soundtrack/Field_Grazing.mp3", + "voxygen/audio/soundtrack/field_grazing.ogg", "voxygen/audio/soundtrack/fiesta_del_pueblo.ogg", "voxygen/audio/soundtrack/library_theme_with_harpsichord.ogg", "voxygen/audio/soundtrack/Mineral_Deposits.ogg", - "voxygen/audio/soundtrack/Ruination.ogg", + //"voxygen/audio/soundtrack/Ruination.ogg", "voxygen/audio/soundtrack/sacred_temple.ogg", "voxygen/audio/soundtrack/Snowtop.ogg", "voxygen/audio/soundtrack/veloren_title_tune-3.ogg", @@ -80,4 +76,49 @@ impl AudioFrontend { pub fn set_volume(&mut self, volume: f32) { self.stream.set_volume(volume.min(1.0).max(0.0)) } + + /// Returns a vec of the audio devices available. + /// Does not return rodio Device struct in case our audio backend changes. + pub fn list_device_names(&self) -> Vec { + self.devices.iter().map(|x| x.name()).collect() + } + + /// Returns vec of devices + fn list_devices_raw() -> Vec { + rodio::output_devices().collect() + } + + /// Caches vec of devices for later reference + fn maintain_devices(&mut self) { + self.devices = AudioFrontend::list_devices_raw() + } + + /// 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() + } + + /// Returns the name of the current audio device. + /// Does not return rodio Device struct in case our audio backend changes. + pub fn get_device_name(&self) -> String { + self.device.name() + } + + /// Sets the current audio device from a string. + /// Does not use the rodio Device struct in case that detail changes. + /// If the string is an invalid audio device, then no change is made. + pub fn set_device(&mut self, name: String) { + if let Some(dev) = rodio::output_devices().find(|x| x.name() == name) { + self.device = dev; + self.stream = rodio::SpatialSink::new( + &self.device, + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + ); + } + } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 388d5a5525..70a9c216f8 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -92,6 +92,7 @@ pub enum Event { SendMessage(String), AdjustViewDistance(u32), AdjustVolume(f32), + ChangeAudioDevice(String), Logout, Quit, } @@ -207,15 +208,11 @@ pub struct Hud { inventory_space: u32, show: Show, to_focus: Option>, - settings: Settings, force_ungrab: bool, - // TODO: move to settings - current_vd: u32, - current_volume: f32, } impl Hud { - pub fn new(window: &mut Window, settings: Settings) -> Self { + pub fn new(window: &mut Window) -> Self { let mut ui = Ui::new(window).unwrap(); // TODO: Adjust/remove this, right now it is used to demonstrate window scaling functionality. ui.scaling_mode(ScaleMode::RelativeToWindow([1920.0, 1080.0].into())); @@ -246,14 +243,11 @@ impl Hud { want_grab: true, }, to_focus: None, - settings, force_ungrab: false, - current_vd: 5, - current_volume: 0.5, } } - fn update_layout(&mut self, tps: f64) -> Vec { + fn update_layout(&mut self, tps: f64, global_state: &GlobalState) -> Vec { let mut events = Vec::new(); let ref mut ui_widgets = self.ui.set_widgets(); let version = env!("CARGO_PKG_VERSION"); @@ -302,7 +296,7 @@ impl Hud { .top_left_with_margins_on(ui_widgets.window, 3.0, 3.0) .w_h(300.0, 190.0) .set(self.ids.help_bg, ui_widgets); - Text::new(get_help_text(&self.settings.controls).as_str()) + Text::new(get_help_text(&global_state.settings.controls).as_str()) .color(TEXT_COLOR) .top_left_with_margins_on(self.ids.help_bg, 20.0, 20.0) .font_id(self.fonts.opensans) @@ -379,14 +373,8 @@ impl Hud { // Settings if let Windows::Settings = self.show.open_windows { - for event in SettingsWindow::new( - &self.show, - &self.imgs, - &self.fonts, - self.current_vd, - self.current_volume, - ) - .set(self.ids.settings_window, ui_widgets) + for event in SettingsWindow::new(&self.show, &self.imgs, &self.fonts, &global_state) + .set(self.ids.settings_window, ui_widgets) { match event { settings_window::Event::ToggleHelp => self.show.toggle_help(), @@ -396,13 +384,14 @@ impl Hud { settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, settings_window::Event::Close => self.show.open_windows = Windows::None, settings_window::Event::AdjustViewDistance(view_distance) => { - self.current_vd = view_distance; events.push(Event::AdjustViewDistance(view_distance)); } settings_window::Event::AdjustVolume(volume) => { - self.current_volume = volume; events.push(Event::AdjustVolume(volume)); } + settings_window::Event::ChangeAudioDevice(name) => { + events.push(Event::ChangeAudioDevice(name)); + } } } } @@ -483,10 +472,6 @@ impl Hud { pub fn handle_event(&mut self, event: WinEvent, global_state: &mut GlobalState) -> bool { let cursor_grabbed = global_state.window.is_cursor_grabbed(); let handled = match event { - WinEvent::SettingsChanged => { - self.settings = global_state.settings.clone(); - false - } WinEvent::Ui(event) => { if (self.typing() && event.is_keyboard() && self.show.ui) || !(cursor_grabbed && event.is_keyboard_or_mouse()) @@ -581,12 +566,12 @@ impl Hud { handled } - pub fn maintain(&mut self, renderer: &mut Renderer, tps: f64) -> Vec { + pub fn maintain(&mut self, global_state: &mut GlobalState, tps: f64) -> Vec { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(tps); - self.ui.maintain(renderer); + let events = self.update_layout(tps, &global_state); + self.ui.maintain(&mut global_state.window.renderer_mut()); events } diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 0f290cea71..323aef618e 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -7,10 +7,11 @@ use crate::{ ImageSlider, ScaleMode, ToggleButton, Ui, }, window::Window, + AudioFrontend, GlobalState, }; use conrod_core::{ color, - widget::{self, Button, Image, Rectangle, Scrollbar, Text}, + widget::{self, Button, DropDownList, Image, List, Rectangle, Scrollbar, Text}, widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; widget_ids! { @@ -45,6 +46,8 @@ widget_ids! { vd_slider_text, audio_volume_slider, audio_volume_text, + audio_device_list, + audio_device_text, } } @@ -63,8 +66,7 @@ pub struct SettingsWindow<'a> { imgs: &'a Imgs, fonts: &'a Fonts, - current_vd: u32, - current_volume: f32, + global_state: &'a GlobalState, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -75,15 +77,13 @@ impl<'a> SettingsWindow<'a> { show: &'a Show, imgs: &'a Imgs, fonts: &'a Fonts, - current_vd: u32, - current_volume: f32, + global_state: &'a GlobalState, ) -> Self { Self { show, imgs, fonts, - current_vd, - current_volume, + global_state, common: widget::CommonBuilder::default(), } } @@ -102,6 +102,7 @@ pub enum Event { Close, AdjustViewDistance(u32), AdjustVolume(f32), + ChangeAudioDevice(String), } impl<'a> Widget for SettingsWindow<'a> { @@ -490,7 +491,7 @@ impl<'a> Widget for SettingsWindow<'a> { .set(state.ids.vd_slider_text, ui); if let Some(new_val) = ImageSlider::discrete( - self.current_vd, + self.global_state.settings.graphics.view_distance, 1, 25, self.imgs.slider_indicator, @@ -535,6 +536,7 @@ impl<'a> Widget for SettingsWindow<'a> { } // Contents if let SettingsTab::Sound = state.settings_tab { + // Volume Slider ---------------------------------------------------- Text::new("Volume") .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) .font_size(14) @@ -543,7 +545,7 @@ impl<'a> Widget for SettingsWindow<'a> { .set(state.ids.audio_volume_text, ui); if let Some(new_val) = ImageSlider::continuous( - self.current_volume, + self.global_state.settings.audio.music_volume, 0.0, 1.0, self.imgs.slider_indicator, @@ -558,6 +560,29 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::AdjustVolume(new_val)); } + + // Audio Device Selector -------------------------------------------- + let device = self.global_state.audio.get_device_name(); + let device_list = self.global_state.audio.list_device_names(); + 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)); + + 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)); + } } events diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 643674e135..f48ea71175 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -79,7 +79,7 @@ pub trait PlayState { fn main() { // Set up the global state. - let settings = Settings::load(); + let mut settings = Settings::load(); let window = Window::new(&settings).expect("Failed to create window!"); // Initialize logging. @@ -159,15 +159,15 @@ fn main() { default_hook(panic_info); })); - let mut global_state = GlobalState { - settings, - window, - audio: AudioFrontend::new(), - }; + if settings.audio.audio_device == None { + settings.audio.audio_device = Some(AudioFrontend::get_default_device()); + } - // TODO: Remove this when the volume setting can be saved - // Lower the volume to 50% - global_state.audio.set_volume(0.5); + let mut global_state = GlobalState { + audio: AudioFrontend::new(&settings.audio), + window, + settings, + }; // Set up the initial play state. let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index de75d9bb4d..1fa492c173 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -32,7 +32,7 @@ impl SessionState { scene, client, key_state: KeyState::new(), - hud: Hud::new(window, settings), + hud: Hud::new(window), input_events: Vec::new(), } } @@ -173,14 +173,11 @@ impl PlayState for SessionState { // Maintain the scene. self.scene.maintain( global_state.window.renderer_mut(), - &mut self.client.borrow_mut(), + &self.client.borrow_mut(), ); // Maintain the UI. - for event in self - .hud - .maintain(global_state.window.renderer_mut(), clock.get_tps()) - { + for event in self.hud.maintain(global_state, clock.get_tps()) { match event { HudEvent::SendMessage(msg) => { // TODO: Handle result @@ -191,10 +188,22 @@ impl PlayState for SessionState { return PlayStateResult::Shutdown; } HudEvent::AdjustViewDistance(view_distance) => { - self.client.borrow_mut().set_view_distance(view_distance) + self.client.borrow_mut().set_view_distance(view_distance); + + global_state.settings.graphics.view_distance = view_distance; + global_state.settings.save_to_file(); } HudEvent::AdjustVolume(volume) => { global_state.audio.set_volume(volume); + + global_state.settings.audio.music_volume = volume; + global_state.settings.save_to_file(); + } + HudEvent::ChangeAudioDevice(name) => { + global_state.audio.set_device(name.clone()); + + global_state.settings.audio.audio_device = Some(name); + global_state.settings.save_to_file(); } } } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 22950cb6d2..8986f08cac 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -12,6 +12,8 @@ pub struct Settings { pub controls: ControlSettings, pub networking: NetworkingSettings, pub log: Log, + pub graphics: GraphicsSettings, + pub audio: AudioSettings, } /// `ControlSettings` contains keybindings. @@ -52,6 +54,22 @@ pub struct Log { pub file: PathBuf, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GraphicsSettings { + pub view_distance: u32, +} + +/// AudioSettings controls the volume of different audio subsystems and which +/// device is used. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AudioSettings { + pub music_volume: f32, + pub sfx_volume: f32, + + /// Audio Device that Voxygen will use to play audio. + pub audio_device: Option, +} + impl Default for Settings { fn default() -> Self { Settings { @@ -86,6 +104,12 @@ impl Default for Settings { log: Log { file: "voxygen.log".into(), }, + graphics: GraphicsSettings { view_distance: 5 }, + audio: AudioSettings { + music_volume: 0.5, + sfx_volume: 0.5, + audio_device: None, + }, } } }