mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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.
This commit is contained in:
parent
0fe5b66dce
commit
9dc1f8f549
@ -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<String>,
|
||||
pub device: String,
|
||||
pub device_list: Vec<String>,
|
||||
audio_device: Option<Device>,
|
||||
|
||||
channels: Vec<Channel>,
|
||||
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::<usize>::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::<usize>() % 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<String> {
|
||||
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::<Vec<String>>();
|
||||
|
||||
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<String> {
|
||||
list_devices_raw().iter().map(|x| x.name()).collect()
|
||||
}
|
||||
|
||||
/// Returns vec of devices
|
||||
fn list_devices_raw() -> Vec<Device> {
|
||||
rodio::output_devices().collect()
|
||||
}
|
||||
|
||||
fn get_device_raw(device: String) -> Option<Device> {
|
||||
rodio::output_devices().find(|d| d.name() == device)
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
|
||||
states
|
||||
|
@ -34,6 +34,8 @@ impl PlayState for MainMenuState {
|
||||
// Used for client creation.
|
||||
let mut client_init: Option<ClientInit> = 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() {
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user