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 music system
### Changed
### 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 rodio::{Decoder, Device, Sample, Source, SpatialSink};
use std::{fs::File, io::BufReader};
use rodio::{Device, Sample, Source, SpatialSink};
use vek::*;
#[derive(PartialEq, Clone, Copy)]
@ -20,12 +19,19 @@ enum ChannelState {
Stopped,
}
#[derive(PartialEq, Clone, Copy)]
pub enum ChannelTag {
TitleMusic,
Soundtrack,
}
pub struct Channel {
id: usize,
sink: SpatialSink,
audio_type: AudioType,
state: ChannelState,
fader: Fader,
tag: Option<ChannelTag>,
pub pos: Vec3<f32>,
}
@ -35,41 +41,15 @@ impl Channel {
pub fn new(device: &Device) -> Self {
Self {
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,
state: ChannelState::Stopped,
fader: Fader::fade_in(0.0),
tag: None,
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)
where
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 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) {
self.state = ChannelState::Stopping;
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 {
length: time,
running_time: 0.0,
volume_from: 1.0,
volume_from,
volume_to: 0.0,
is_running: true,
}

View File

@ -1,9 +1,10 @@
pub mod channel;
pub mod fader;
pub mod music;
pub mod sfx;
pub mod soundcache;
use channel::{AudioType, Channel};
use channel::{AudioType, Channel, ChannelTag};
use fader::Fader;
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()) {
let id = self.next_channel_id;
self.next_channel_id += 1;
@ -95,6 +100,7 @@ impl AudioFrontend {
};
channel.set_id(id);
channel.set_tag(channel_tag);
channel.set_audio_type(audio_type);
channel.set_volume(volume);
@ -114,7 +120,7 @@ impl AudioFrontend {
let left_ear = self.listener_ear_left.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_left_ear_position(left_ear);
channel.set_right_ear_position(right_ear);
@ -127,12 +133,13 @@ impl AudioFrontend {
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 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 sound = Decoder::new(file).expect("Failed to decode sound");
channel.set_emitter_position([0.0; 3]);
channel.play(sound);
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) {
let index = self.channels.iter().position(|c| c.get_id() == channel_id);
if let Some(index) = index {
self.channels[index].stop(fader);
}
@ -178,6 +207,10 @@ impl AudioFrontend {
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) {
self.sfx_volume = sfx_volume;
@ -193,7 +226,11 @@ impl AudioFrontend {
for channel in self.channels.iter_mut() {
if channel.get_audio_type() == AudioType::Music {
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) {
if !audio.sfx_enabled() {
return;
}
self.event_mapper.maintain(client, &self.triggers);
let ecs = client.state().ecs();

View File

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

View File

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

View File

@ -126,6 +126,9 @@ impl PlayState for SessionState {
let mut clock = Clock::start();
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
if global_state.settings.send_logon_commands {
for cmd in &global_state.settings.logon_commands {