Move music under audio

Also add some blank time after each track so that we get some silence
between tracks.
This commit is contained in:
S Handley 2020-02-03 11:55:32 +00:00 committed by Songtronix
parent 5b46587c2d
commit b7ce91fead
16 changed files with 242 additions and 44 deletions

View File

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added music system
### Changed ### Changed
### Removed ### Removed

View File

@ -0,0 +1,46 @@
(
tracks: [
(
title: "Field Grazing",
path: "voxygen.audio.soundtrack.field_grazing",
length: 154.0,
timing: Some(Day),
artist: "Aeronic",
),
(
title: "Wandering Voices",
path: "voxygen.audio.soundtrack.wandering_voices",
length: 137.0,
timing: Some(Night),
artist: "Aeronic",
),
(
title: "Snowtop Volume",
path: "voxygen.audio.soundtrack.snowtop_volume",
length: 89.0,
timing: Some(Day),
artist: "Aeronic",
),
(
title: "Mineral Deposits",
path: "voxygen.audio.soundtrack.mineral_deposits",
length: 148.0,
timing: Some(Day),
artist: "Aeronic",
),
(
title: "Moonbeams",
path: "voxygen.audio.soundtrack.moonbeams",
length: 158.0,
timing: Some(Night),
artist: "Aeronic",
),
(
title: "Serene Meadows",
path: "voxygen.audio.soundtrack.serene_meadows",
length: 173.0,
timing: Some(Night),
artist: "Aeronic",
)
]
)

BIN
assets/voxygen/audio/soundtrack/field_grazing.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/mineral_deposits.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/moonbeams.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/serene_meadows.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/snowtop_volume.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/wandering_voices.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,6 +1,5 @@
use crate::audio::fader::Fader; use crate::audio::fader::Fader;
use rodio::{Decoder, Device, Sample, Source, SpatialSink}; use rodio::{Device, Sample, Source, SpatialSink};
use std::{fs::File, io::BufReader};
use vek::*; use vek::*;
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
@ -20,12 +19,19 @@ enum ChannelState {
Stopped, Stopped,
} }
#[derive(PartialEq, Clone, Copy)]
pub enum ChannelTag {
TitleMusic,
Soundtrack,
}
pub struct Channel { pub struct Channel {
id: usize, id: usize,
sink: SpatialSink, sink: SpatialSink,
audio_type: AudioType, audio_type: AudioType,
state: ChannelState, state: ChannelState,
fader: Fader, fader: Fader,
tag: Option<ChannelTag>,
pub pos: Vec3<f32>, pub pos: Vec3<f32>,
} }
@ -35,41 +41,15 @@ impl Channel {
pub fn new(device: &Device) -> Self { pub fn new(device: &Device) -> Self {
Self { Self {
id: 0, id: 0,
sink: SpatialSink::new(device, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]), sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]),
audio_type: AudioType::None, audio_type: AudioType::None,
state: ChannelState::Stopped, state: ChannelState::Stopped,
fader: Fader::fade_in(0.0), fader: Fader::fade_in(0.0),
tag: None,
pos: Vec3::zero(), pos: Vec3::zero(),
} }
} }
pub fn music(id: usize, device: &Device, bufr: BufReader<File>) -> Self {
let sink = SpatialSink::new(device, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]);
let sound = Decoder::new(bufr).unwrap();
sink.append(sound);
Self {
id,
sink,
audio_type: AudioType::Music,
state: ChannelState::Playing,
fader: Fader::fade_in(0.0),
pos: Vec3::zero(),
}
}
pub fn sfx(id: usize, sink: SpatialSink, pos: Vec3<f32>) -> Self {
Self {
id,
sink,
audio_type: AudioType::Sfx,
state: ChannelState::Playing,
fader: Fader::fade_in(0.0),
pos,
}
}
pub fn play<S>(&mut self, source: S) pub fn play<S>(&mut self, source: S)
where where
S: Source + Send + 'static, S: Source + Send + 'static,
@ -83,6 +63,10 @@ impl Channel {
pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped } pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped }
pub fn set_tag(&mut self, tag: Option<ChannelTag>) { self.tag = tag; }
pub fn get_tag(&self) -> Option<ChannelTag> { self.tag }
pub fn stop(&mut self, fader: Fader) { pub fn stop(&mut self, fader: Fader) {
self.state = ChannelState::Stopping; self.state = ChannelState::Stopping;
self.fader = fader; self.fader = fader;

View File

@ -30,11 +30,11 @@ impl Fader {
} }
} }
pub fn fade_out(time: f32) -> Self { pub fn fade_out(time: f32, volume_from: f32) -> Self {
Self { Self {
length: time, length: time,
running_time: 0.0, running_time: 0.0,
volume_from: 1.0, volume_from,
volume_to: 0.0, volume_to: 0.0,
is_running: true, is_running: true,
} }

View File

@ -1,9 +1,10 @@
pub mod channel; pub mod channel;
pub mod fader; pub mod fader;
pub mod music;
pub mod sfx; pub mod sfx;
pub mod soundcache; pub mod soundcache;
use channel::{AudioType, Channel}; use channel::{AudioType, Channel, ChannelTag};
use fader::Fader; use fader::Fader;
use soundcache::SoundCache; use soundcache::SoundCache;
@ -84,7 +85,11 @@ impl AudioFrontend {
} }
} }
pub fn get_channel(&mut self, audio_type: AudioType) -> Option<&mut Channel> { pub fn get_channel(
&mut self,
audio_type: AudioType,
channel_tag: Option<ChannelTag>,
) -> Option<&mut Channel> {
if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) { if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) {
let id = self.next_channel_id; let id = self.next_channel_id;
self.next_channel_id += 1; self.next_channel_id += 1;
@ -95,6 +100,7 @@ impl AudioFrontend {
}; };
channel.set_id(id); channel.set_id(id);
channel.set_tag(channel_tag);
channel.set_audio_type(audio_type); channel.set_audio_type(audio_type);
channel.set_volume(volume); channel.set_volume(volume);
@ -114,7 +120,7 @@ impl AudioFrontend {
let left_ear = self.listener_ear_left.into_array(); let left_ear = self.listener_ear_left.into_array();
let right_ear = self.listener_ear_right.into_array(); let right_ear = self.listener_ear_right.into_array();
if let Some(channel) = self.get_channel(AudioType::Sfx) { if let Some(channel) = self.get_channel(AudioType::Sfx, None) {
channel.set_emitter_position(calc_pos); channel.set_emitter_position(calc_pos);
channel.set_left_ear_position(left_ear); channel.set_left_ear_position(left_ear);
channel.set_right_ear_position(right_ear); channel.set_right_ear_position(right_ear);
@ -127,12 +133,13 @@ impl AudioFrontend {
None None
} }
pub fn play_music(&mut self, sound: &str) -> Option<usize> { pub fn play_music(&mut self, sound: &str, channel_tag: Option<ChannelTag>) -> Option<usize> {
if self.audio_device.is_some() { if self.audio_device.is_some() {
if let Some(channel) = self.get_channel(AudioType::Music) { if let Some(channel) = self.get_channel(AudioType::Music, channel_tag) {
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
let sound = Decoder::new(file).expect("Failed to decode sound"); let sound = Decoder::new(file).expect("Failed to decode sound");
channel.set_emitter_position([0.0; 3]);
channel.play(sound); channel.play(sound);
return Some(channel.get_id()); return Some(channel.get_id());
@ -167,8 +174,30 @@ impl AudioFrontend {
} }
} }
pub fn play_title_music(&mut self) -> Option<usize> {
if self.music_enabled() {
self.play_music(
"voxygen.audio.soundtrack.veloren_title_tune",
Some(ChannelTag::TitleMusic),
)
} else {
None
}
}
pub fn stop_title_music(&mut self) {
let index = self.channels.iter().position(|c| {
!c.is_done() && c.get_tag().is_some() && c.get_tag().unwrap() == ChannelTag::TitleMusic
});
if let Some(index) = index {
self.channels[index].stop(Fader::fade_out(1.5, self.music_volume));
}
}
pub fn stop_channel(&mut self, channel_id: usize, fader: Fader) { pub fn stop_channel(&mut self, channel_id: usize, fader: Fader) {
let index = self.channels.iter().position(|c| c.get_id() == channel_id); let index = self.channels.iter().position(|c| c.get_id() == channel_id);
if let Some(index) = index { if let Some(index) = index {
self.channels[index].stop(fader); self.channels[index].stop(fader);
} }
@ -178,6 +207,10 @@ impl AudioFrontend {
pub fn get_music_volume(&self) -> f32 { self.music_volume } pub fn get_music_volume(&self) -> f32 { self.music_volume }
pub fn sfx_enabled(&self) -> bool { self.sfx_volume > 0.0 }
pub fn music_enabled(&self) -> bool { self.music_volume > 0.0 }
pub fn set_sfx_volume(&mut self, sfx_volume: f32) { pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
self.sfx_volume = sfx_volume; self.sfx_volume = sfx_volume;
@ -193,7 +226,11 @@ impl AudioFrontend {
for channel in self.channels.iter_mut() { for channel in self.channels.iter_mut() {
if channel.get_audio_type() == AudioType::Music { if channel.get_audio_type() == AudioType::Music {
channel.set_volume(music_volume); if music_volume > 0.0 {
channel.set_volume(music_volume);
} else {
channel.stop(Fader::fade_out(0.0, 0.0));
}
} }
} }
} }

View File

@ -0,0 +1,99 @@
use crate::audio::{channel::ChannelTag, AudioFrontend};
use client::Client;
use common::assets;
use rand::{seq::IteratorRandom, thread_rng};
use serde::Deserialize;
use std::time::Instant;
const DAY_START_SECONDS: u32 = 28800; // 8:00
const DAY_END_SECONDS: u32 = 70200; // 19:30
#[derive(Debug, Deserialize)]
struct SoundtrackCollection {
tracks: Vec<SoundtrackItem>,
}
#[derive(Debug, Deserialize)]
pub struct SoundtrackItem {
title: String,
path: String,
length: f64,
timing: Option<DayPeriod>,
}
#[derive(Debug, Deserialize, PartialEq)]
enum DayPeriod {
Day, // 8:00 AM to 7:30 PM
Night, // 7:31 PM to 6:59 AM
}
pub struct MusicMgr {
soundtrack: SoundtrackCollection,
began_playing: Instant,
next_track_change: f64,
current_music: Option<usize>,
last_track: String,
}
impl MusicMgr {
pub fn new() -> Self {
Self {
soundtrack: Self::load_soundtrack_items(),
began_playing: Instant::now(),
next_track_change: 0.0,
current_music: None,
last_track: String::from("None"),
}
}
pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) {
if audio.music_enabled()
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change
{
self.current_music = self.play_random_track(audio, client);
}
}
fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) -> Option<usize> {
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32;
let current_period_of_day = self.get_current_day_period(game_time);
let mut rng = thread_rng();
let track = self
.soundtrack
.tracks
.iter()
.filter(|track| {
!track.title.eq(&self.last_track)
&& match &track.timing {
Some(period_of_day) => period_of_day == &current_period_of_day,
None => true,
}
})
.choose(&mut rng)
.expect("Failed to select a random track");
self.last_track = String::from(&track.title);
self.began_playing = Instant::now();
self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS;
audio.play_music(&track.path, Some(ChannelTag::Soundtrack))
}
fn get_current_day_period(&self, game_time: u32) -> DayPeriod {
if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS {
DayPeriod::Day
} else {
DayPeriod::Night
}
}
fn load_soundtrack_items() -> SoundtrackCollection {
let file = assets::load_file("voxygen.audio.soundtrack", &["ron"])
.expect("Failed to load the soundtrack config file");
ron::de::from_reader(file).expect("Error parsing soundtrack manifest")
}
}

View File

@ -51,6 +51,10 @@ impl SfxMgr {
} }
pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) { pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) {
if !audio.sfx_enabled() {
return;
}
self.event_mapper.maintain(client, &self.triggers); self.event_mapper.maintain(client, &self.triggers);
let ecs = client.state().ecs(); let ecs = client.state().ecs();

View File

@ -44,10 +44,11 @@ impl PlayState for MainMenuState {
let mut client_init: Option<ClientInit> = None; let mut client_init: Option<ClientInit> = None;
// Kick off title music // Kick off title music
if self.title_music_channel.is_none() && global_state.settings.audio.audio_on { if self.title_music_channel.is_none()
self.title_music_channel = global_state && global_state.settings.audio.audio_on
.audio && global_state.audio.music_enabled()
.play_music("voxygen.audio.soundtrack.veloren_title_tune"); {
self.title_music_channel = global_state.audio.play_title_music();
} }
// Reset singleplayer server if it was running already // Reset singleplayer server if it was running already

View File

@ -5,11 +5,12 @@ pub mod terrain;
use self::{ use self::{
camera::{Camera, CameraMode}, camera::{Camera, CameraMode},
figure::FigureMgr, figure::FigureMgr,
music::MusicMgr,
terrain::Terrain, terrain::Terrain,
}; };
use crate::{ use crate::{
anim::character::SkeletonAttr, anim::character::SkeletonAttr,
audio::{sfx::SfxMgr, AudioFrontend}, audio::{music, sfx::SfxMgr, AudioFrontend},
render::{ render::{
create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals,
PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline,
@ -58,6 +59,7 @@ pub struct Scene {
figure_mgr: FigureMgr, figure_mgr: FigureMgr,
sfx_mgr: SfxMgr, sfx_mgr: SfxMgr,
music_mgr: MusicMgr,
} }
impl Scene { impl Scene {
@ -91,6 +93,7 @@ impl Scene {
figure_mgr: FigureMgr::new(), figure_mgr: FigureMgr::new(),
sfx_mgr: SfxMgr::new(), sfx_mgr: SfxMgr::new(),
music_mgr: MusicMgr::new(),
} }
} }
@ -312,8 +315,9 @@ impl Scene {
// Remove unused figures. // Remove unused figures.
self.figure_mgr.clean(client.get_tick()); self.figure_mgr.clean(client.get_tick());
// Maintain sfx // Maintain audio
self.sfx_mgr.maintain(audio, client); self.sfx_mgr.maintain(audio, client);
self.music_mgr.maintain(audio, client);
} }
/// Render the scene using the provided `Renderer`. /// Render the scene using the provided `Renderer`.

View File

@ -126,6 +126,9 @@ impl PlayState for SessionState {
let mut clock = Clock::start(); let mut clock = Clock::start();
self.client.borrow_mut().clear_terrain(); self.client.borrow_mut().clear_terrain();
// Kill the title music if it is still playing
global_state.audio.stop_title_music();
// Send startup commands to the server // Send startup commands to the server
if global_state.settings.send_logon_commands { if global_state.settings.send_logon_commands {
for cmd in &global_state.settings.logon_commands { for cmd in &global_state.settings.logon_commands {