mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial biome specific music and ambient sound channel
This commit is contained in:
parent
26957d4fd4
commit
46d3f6f6d2
22
assets/voxygen/audio/ambient.ron
Normal file
22
assets/voxygen/audio/ambient.ron
Normal file
@ -0,0 +1,22 @@
|
||||
// TODO: Add an ambient-soundtrack that runs independently from the musical soundtrack
|
||||
|
||||
(
|
||||
tracks: [
|
||||
(
|
||||
title: "Forest Day", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_day",
|
||||
length: 629.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Forest),
|
||||
artist: "https://www.youtube.com/watch?v=FwVTkB-BIvM",
|
||||
),
|
||||
(
|
||||
title: "Forest Morning", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_morning",
|
||||
length: 600.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Forest),
|
||||
artist: "https://www.youtube.com/watch?v=eq4nfIdK6C4",
|
||||
),
|
||||
]
|
||||
)
|
@ -3,18 +3,12 @@
|
||||
|
||||
(
|
||||
tracks: [
|
||||
(
|
||||
title: "Forest Day", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_day",
|
||||
length: 629.0,
|
||||
timing: Some(Day),
|
||||
artist: "https://www.youtube.com/watch?v=FwVTkB-BIvM",
|
||||
),
|
||||
(
|
||||
title: "A Solemn Quest",
|
||||
path: "voxygen.audio.soundtrack.a_solemn_quest",
|
||||
length: 206.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Grassland),
|
||||
artist: "Eden",
|
||||
),
|
||||
(
|
||||
@ -22,6 +16,7 @@
|
||||
path: "voxygen.audio.soundtrack.into_the_dark_forest",
|
||||
length: 184.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
@ -29,6 +24,7 @@
|
||||
path: "voxygen.audio.soundtrack.field_grazing",
|
||||
length: 154.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Grassland),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
@ -36,20 +32,23 @@
|
||||
path: "voxygen.audio.soundtrack.wandering_voices",
|
||||
length: 137.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
/*(
|
||||
(
|
||||
title: "Snowtop Volume", //Snow Region
|
||||
path: "voxygen.audio.soundtrack.snowtop_volume",
|
||||
length: 89.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),*/
|
||||
),
|
||||
(
|
||||
title: "Mineral Deposits",
|
||||
path: "voxygen.audio.soundtrack.mineral_deposits",
|
||||
length: 148.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
@ -57,6 +56,7 @@
|
||||
path: "voxygen.audio.soundtrack.moonbeams",
|
||||
length: 158.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
@ -64,6 +64,7 @@
|
||||
path: "voxygen.audio.soundtrack.serene_meadows",
|
||||
length: 173.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
/*(
|
||||
@ -71,6 +72,7 @@
|
||||
path: "voxygen.audio.soundtrack.rest_assured",
|
||||
length: 185.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),*/
|
||||
(
|
||||
@ -78,6 +80,7 @@
|
||||
path: "voxygen.audio.soundtrack.just_the_beginning",
|
||||
length: 188.0,
|
||||
timing: Some(Day),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
@ -85,6 +88,7 @@
|
||||
path: "voxygen.audio.soundtrack.campfire_stories",
|
||||
length: 100.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
@ -92,6 +96,7 @@
|
||||
path: "voxygen.audio.soundtrack.limits",
|
||||
length: 203.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),
|
||||
/*( // Dungeon
|
||||
@ -99,6 +104,7 @@
|
||||
path: "voxygen.audio.soundtrack.down_the_rabbit_hole",
|
||||
length: 244.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),*/
|
||||
(
|
||||
@ -106,14 +112,8 @@
|
||||
path: "voxygen.audio.soundtrack.between_the_fairies",
|
||||
length: 175.0,
|
||||
timing: Some(Night),
|
||||
biome: Some(Snowlands),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Forest Morning", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_morning",
|
||||
length: 600.0,
|
||||
timing: Some(Day),
|
||||
artist: "https://www.youtube.com/watch?v=eq4nfIdK6C4",
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum BiomeKind {
|
||||
Void,
|
||||
Grassland,
|
||||
|
183
voxygen/src/audio/ambient.rs
Normal file
183
voxygen/src/audio/ambient.rs
Normal file
@ -0,0 +1,183 @@
|
||||
//! Handles ambient sound playback and transitions
|
||||
//!
|
||||
//! Game ambient sound is controlled though a configuration file found in the
|
||||
//! source at `/assets/voxygen/audio/ambient.ron`. Each track enabled in game
|
||||
//! has a configuration corresponding to the
|
||||
//! [`SoundtrackItem`](struct.SoundtrackItem.html) format, as well as the
|
||||
//! corresponding `.ogg` file in the `/assets/voxygen/audio/soundtrack/`
|
||||
//! directory.
|
||||
//!
|
||||
//! If there are errors while reading or deserialising the configuration file, a
|
||||
//! warning is logged and music will be disabled.
|
||||
//!
|
||||
//! ## Adding new ambient sound
|
||||
//!
|
||||
//! To add a new item, append the details to the audio configuration file, and
|
||||
//! add the audio file (in `.ogg` format) to the assets directory.
|
||||
//!
|
||||
//! The `length` should be provided in seconds. This allows us to know when to
|
||||
//! transition to another track, without having to spend time determining track
|
||||
//! length programmatically.
|
||||
//!
|
||||
//! An example of a new night time track:
|
||||
//! ```text
|
||||
//! (
|
||||
//! title: "Sleepy Song",
|
||||
//! path: "voxygen.audio.soundtrack.sleepy",
|
||||
//! length: 400.0,
|
||||
//! timing: Some(Night),
|
||||
//! biome: Some(Forest),
|
||||
//! artist: "Elvis",
|
||||
//! ),
|
||||
//! ```
|
||||
//!
|
||||
//! Before sending an MR for your new track item:
|
||||
//! - Be conscious of the file size for your new track. Assets contribute to
|
||||
//! download sizes
|
||||
//! - Ensure that the track is mastered to a volume proportionate to other music
|
||||
//! tracks
|
||||
//! - If you are not the author of the track, ensure that the song's licensing
|
||||
//! permits usage of the track for non-commercial use
|
||||
use crate::audio::AudioFrontend;
|
||||
use client::Client;
|
||||
use common::{assets, state::State, terrain::BiomeKind};
|
||||
use rand::{seq::IteratorRandom, thread_rng};
|
||||
use serde::Deserialize;
|
||||
use std::time::Instant;
|
||||
use tracing::warn;
|
||||
|
||||
const DAY_START_SECONDS: u32 = 28800; // 8:00
|
||||
const DAY_END_SECONDS: u32 = 70200; // 19:30
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct AmbientSoundtrackCollection {
|
||||
tracks: Vec<AmbientSoundtrackItem>,
|
||||
}
|
||||
|
||||
/// Configuration for a single music track in the soundtrack
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AmbientSoundtrackItem {
|
||||
title: String,
|
||||
path: String,
|
||||
/// Length of the track in seconds
|
||||
length: f64,
|
||||
/// Whether this track should play during day or night
|
||||
timing: Option<DayPeriod>,
|
||||
biome: Option<BiomeKind>,
|
||||
}
|
||||
|
||||
/// Allows control over when a track should play based on in-game time of day
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
enum DayPeriod {
|
||||
/// 8:00 AM to 7:30 PM
|
||||
Day,
|
||||
/// 7:31 PM to 6:59 AM
|
||||
Night,
|
||||
}
|
||||
|
||||
/// Provides methods to control music playback
|
||||
pub struct AmbientMgr {
|
||||
ambient_soundtrack: AmbientSoundtrackCollection,
|
||||
began_playing: Instant,
|
||||
next_track_change: f64,
|
||||
/// The title of the last track played. Used to prevent a track
|
||||
/// being played twice in a row
|
||||
last_track: String,
|
||||
}
|
||||
|
||||
impl AmbientMgr {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ambient_soundtrack: Self::load_ambient_soundtrack_items(),
|
||||
began_playing: Instant::now(),
|
||||
next_track_change: 0.0,
|
||||
last_track: String::from("None"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the previous track has completed. If so, sends a
|
||||
/// request to play the next (random) track
|
||||
pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
if audio.music_enabled()
|
||||
&& !self.ambient_soundtrack.tracks.is_empty()
|
||||
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change
|
||||
{
|
||||
self.play_random_track(audio, state, client);
|
||||
}
|
||||
}
|
||||
|
||||
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
|
||||
|
||||
let game_time = (state.get_time_of_day() as u64 % 86400) as u32;
|
||||
let current_period_of_day = Self::get_current_day_period(game_time);
|
||||
let current_biome = Self::get_current_biome(client);
|
||||
println!("Ambient Current biome: {:?}", current_biome);
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let maybe_track = self
|
||||
.ambient_soundtrack
|
||||
.tracks
|
||||
.iter()
|
||||
.filter(|track| {
|
||||
!track.title.eq(&self.last_track)
|
||||
&& match &track.timing {
|
||||
Some(period_of_day) => period_of_day == ¤t_period_of_day,
|
||||
None => true,
|
||||
}
|
||||
})
|
||||
.filter(|track| match &track.biome {
|
||||
Some(biome) => biome == ¤t_biome,
|
||||
None => true,
|
||||
})
|
||||
.choose(&mut rng);
|
||||
|
||||
if let Some(track) = maybe_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_exploration_ambient(&track.path);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_day_period(game_time: u32) -> DayPeriod {
|
||||
if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS {
|
||||
DayPeriod::Day
|
||||
} else {
|
||||
DayPeriod::Night
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_biome(client: &Client) -> BiomeKind {
|
||||
match client.current_chunk() {
|
||||
Some(chunk) => chunk.meta().biome(),
|
||||
_ => BiomeKind::Void,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_ambient_soundtrack_items() -> AmbientSoundtrackCollection {
|
||||
match assets::load_file("voxygen.audio.ambient", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error parsing music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientSoundtrackCollection::default()
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientSoundtrackCollection::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -134,6 +134,111 @@ impl MusicChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each `AmbientChannel` has a `AmbientChannelTag` which help us determine when
|
||||
/// we should transition between two types of in-game music. For example, we
|
||||
/// transition between `TitleMusic` and `Exploration` when a player enters the
|
||||
/// world by crossfading over a slow duration. In the future, transitions in the
|
||||
/// world such as `Exploration` -> `BossBattle` would transition more rapidly.
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum AmbientChannelTag {
|
||||
TitleMusic,
|
||||
Exploration,
|
||||
}
|
||||
|
||||
/// An AmbientChannel uses a non-positional audio sink designed to play ambient
|
||||
/// sound which is always heard at the player's position.
|
||||
///
|
||||
/// See also: [`Rodio::Sink`](https://docs.rs/rodio/0.11.0/rodio/struct.Sink.html)
|
||||
pub struct AmbientChannel {
|
||||
tag: AmbientChannelTag,
|
||||
sink: Sink,
|
||||
state: ChannelState,
|
||||
fader: Fader,
|
||||
}
|
||||
impl AmbientChannel {
|
||||
pub fn new(device: &Device) -> Self {
|
||||
Self {
|
||||
sink: Sink::new(device),
|
||||
tag: AmbientChannelTag::TitleMusic,
|
||||
state: ChannelState::Stopped,
|
||||
fader: Fader::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Play an ambient track item on this channel. If the channel has an existing
|
||||
// track playing, the new sounds will be appended and played once they
|
||||
// complete. Otherwise it will begin playing immediately.
|
||||
pub fn play<S>(&mut self, source: S, tag: AmbientChannelTag)
|
||||
where
|
||||
S: Source + Send + 'static,
|
||||
S::Item: Sample,
|
||||
S::Item: Send,
|
||||
<S as std::iter::Iterator>::Item: std::fmt::Debug,
|
||||
{
|
||||
self.tag = tag;
|
||||
if !self.sink.len() > 0 {
|
||||
self.sink.append(source);
|
||||
}
|
||||
|
||||
self.state = if !self.fader.is_finished() {
|
||||
ChannelState::Fading
|
||||
} else {
|
||||
ChannelState::Playing
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the volume of the current channel. If the channel is currently
|
||||
/// fading, the volume of the fader is updated to this value.
|
||||
pub fn set_volume(&mut self, volume: f32) {
|
||||
if !self.fader.is_finished() {
|
||||
self.fader.update_target_volume(volume);
|
||||
} else {
|
||||
self.sink.set_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a fader for the channel. If a fader exists already, it is replaced.
|
||||
/// If the channel has not begun playing, and the fader is set to fade in,
|
||||
/// we set the volume of the channel to the initial volume of the fader so
|
||||
/// that the volumes match when playing begins.
|
||||
pub fn set_fader(&mut self, fader: Fader) {
|
||||
self.fader = fader;
|
||||
self.state = ChannelState::Fading;
|
||||
|
||||
if self.state == ChannelState::Stopped && fader.direction() == FadeDirection::In {
|
||||
self.sink.set_volume(fader.get_volume());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if either the channels sink reports itself as empty (no
|
||||
/// more sounds in the queue) or we have forcibly set the channels state to
|
||||
/// the 'Stopped' state
|
||||
pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped }
|
||||
|
||||
pub fn get_tag(&self) -> AmbientChannelTag { self.tag }
|
||||
|
||||
/// Maintain the fader attached to this channel. If the channel is not
|
||||
/// fading, no action is taken.
|
||||
pub fn maintain(&mut self, dt: f32) {
|
||||
if self.state == ChannelState::Fading {
|
||||
self.fader.update(dt);
|
||||
self.sink.set_volume(self.fader.get_volume());
|
||||
|
||||
if self.fader.is_finished() {
|
||||
match self.fader.direction() {
|
||||
FadeDirection::Out => {
|
||||
self.state = ChannelState::Stopped;
|
||||
self.sink.stop();
|
||||
},
|
||||
FadeDirection::In => {
|
||||
self.state = ChannelState::Playing;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An SfxChannel uses a positional audio sink, and is designed for short-lived
|
||||
/// audio which can be spatially controlled, but does not need control over
|
||||
/// playback or fading/transitions
|
||||
|
@ -1,12 +1,13 @@
|
||||
//! Handles audio device detection and playback of sound effects and music
|
||||
|
||||
pub mod ambient;
|
||||
pub mod channel;
|
||||
pub mod fader;
|
||||
pub mod music;
|
||||
pub mod sfx;
|
||||
pub mod soundcache;
|
||||
|
||||
use channel::{MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use fader::Fader;
|
||||
use soundcache::SoundCache;
|
||||
use std::time::Duration;
|
||||
@ -38,9 +39,11 @@ pub struct AudioFrontend {
|
||||
|
||||
music_channels: Vec<MusicChannel>,
|
||||
sfx_channels: Vec<SfxChannel>,
|
||||
ambient_channels: Vec<AmbientChannel>,
|
||||
|
||||
sfx_volume: f32,
|
||||
music_volume: f32,
|
||||
ambient_volume: f32,
|
||||
|
||||
listener: Listener,
|
||||
}
|
||||
@ -61,9 +64,11 @@ impl AudioFrontend {
|
||||
audio_device,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
ambient_channels: Vec::new(),
|
||||
sfx_channels,
|
||||
sfx_volume: 1.0,
|
||||
music_volume: 1.0,
|
||||
ambient_volume: 1.0,
|
||||
|
||||
listener: Listener::default(),
|
||||
}
|
||||
@ -77,9 +82,11 @@ impl AudioFrontend {
|
||||
audio_device: None,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
ambient_channels: Vec::new(),
|
||||
sfx_channels: Vec::new(),
|
||||
sfx_volume: 1.0,
|
||||
music_volume: 1.0,
|
||||
ambient_volume: 1.0,
|
||||
listener: Listener::default(),
|
||||
}
|
||||
}
|
||||
@ -87,10 +94,15 @@ impl AudioFrontend {
|
||||
/// Drop any unused music channels, and update their faders
|
||||
pub fn maintain(&mut self, dt: Duration) {
|
||||
self.music_channels.retain(|c| !c.is_done());
|
||||
self.ambient_channels.retain(|c| !c.is_done());
|
||||
|
||||
for channel in self.music_channels.iter_mut() {
|
||||
channel.maintain(dt);
|
||||
}
|
||||
|
||||
for channel in self.ambient_channels.iter_mut() {
|
||||
channel.maintain(dt);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> {
|
||||
@ -141,6 +153,35 @@ impl AudioFrontend {
|
||||
self.music_channels.last_mut()
|
||||
}
|
||||
|
||||
fn get_ambient_channel(
|
||||
&mut self,
|
||||
next_channel_tag: AmbientChannelTag,
|
||||
) -> Option<&mut AmbientChannel> {
|
||||
if let Some(audio_device) = &self.audio_device {
|
||||
if self.ambient_channels.is_empty() {
|
||||
let mut next_ambient_channel = AmbientChannel::new(&audio_device);
|
||||
next_ambient_channel.set_volume(self.ambient_volume);
|
||||
|
||||
self.ambient_channels.push(next_ambient_channel);
|
||||
} else {
|
||||
let existing_channel = self.ambient_channels.last_mut()?;
|
||||
|
||||
if existing_channel.get_tag() != next_channel_tag {
|
||||
// Fade the existing channel out. It will be removed when the fade completes.
|
||||
existing_channel.set_fader(Fader::fade_out(2.0, self.ambient_volume));
|
||||
|
||||
let mut next_ambient_channel = AmbientChannel::new(&audio_device);
|
||||
|
||||
next_ambient_channel.set_fader(Fader::fade_in(12.0, self.ambient_volume));
|
||||
|
||||
self.ambient_channels.push(next_ambient_channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ambient_channels.last_mut()
|
||||
}
|
||||
|
||||
/// Play (once) an sfx file by file path at the give position and volume
|
||||
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||
if self.audio_device.is_some() {
|
||||
@ -167,6 +208,15 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
fn play_ambient(&mut self, sound: &str, channel_tag: AmbientChannelTag) {
|
||||
if let Some(channel) = self.get_ambient_channel(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.play(sound, channel_tag);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
|
||||
self.listener.pos = pos;
|
||||
self.listener.ori = ori.normalized();
|
||||
@ -199,14 +249,24 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_exploration_ambient(&mut self, item: &str) {
|
||||
if self.ambient_enabled() {
|
||||
self.play_ambient(item, AmbientChannelTag::Exploration)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume }
|
||||
|
||||
pub fn get_music_volume(&self) -> f32 { self.music_volume }
|
||||
|
||||
pub fn get_ambient_volume(&self) -> f32 { self.ambient_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 ambient_enabled(&self) -> bool { self.ambient_volume > 0.0 }
|
||||
|
||||
pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
|
||||
self.sfx_volume = sfx_volume;
|
||||
|
||||
@ -223,6 +283,14 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ambient_volume(&mut self, ambient_volume: f32) {
|
||||
self.ambient_volume = ambient_volume;
|
||||
|
||||
for channel in self.ambient_channels.iter_mut() {
|
||||
channel.set_volume(ambient_volume);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how badly this will break things when it is called
|
||||
pub fn set_device(&mut self, name: String) {
|
||||
self.device = name.clone();
|
||||
|
@ -26,6 +26,7 @@
|
||||
//! path: "voxygen.audio.soundtrack.sleepy",
|
||||
//! length: 400.0,
|
||||
//! timing: Some(Night),
|
||||
//! biome: Some(Forest),
|
||||
//! artist: "Elvis",
|
||||
//! ),
|
||||
//! ```
|
||||
@ -38,7 +39,8 @@
|
||||
//! - If you are not the author of the track, ensure that the song's licensing
|
||||
//! permits usage of the track for non-commercial use
|
||||
use crate::audio::AudioFrontend;
|
||||
use common::{assets, state::State};
|
||||
use client::Client;
|
||||
use common::{assets, state::State, terrain::BiomeKind};
|
||||
use rand::{seq::IteratorRandom, thread_rng};
|
||||
use serde::Deserialize;
|
||||
use std::time::Instant;
|
||||
@ -61,6 +63,7 @@ pub struct SoundtrackItem {
|
||||
length: f64,
|
||||
/// Whether this track should play during day or night
|
||||
timing: Option<DayPeriod>,
|
||||
biome: Option<BiomeKind>,
|
||||
}
|
||||
|
||||
/// Allows control over when a track should play based on in-game time of day
|
||||
@ -95,20 +98,22 @@ impl MusicMgr {
|
||||
|
||||
/// Checks whether the previous track has completed. If so, sends a
|
||||
/// request to play the next (random) track
|
||||
pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State) {
|
||||
pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
if audio.music_enabled()
|
||||
&& !self.soundtrack.tracks.is_empty()
|
||||
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change
|
||||
{
|
||||
self.play_random_track(audio, state);
|
||||
self.play_random_track(audio, state, client);
|
||||
}
|
||||
}
|
||||
|
||||
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State) {
|
||||
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
|
||||
|
||||
let game_time = (state.get_time_of_day() as u64 % 86400) as u32;
|
||||
let current_period_of_day = Self::get_current_day_period(game_time);
|
||||
let current_biome = Self::get_current_biome(client);
|
||||
println!("======> Music Current biome: {:?}", current_biome);
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let maybe_track = self
|
||||
@ -122,6 +127,10 @@ impl MusicMgr {
|
||||
None => true,
|
||||
}
|
||||
})
|
||||
.filter(|track| match &track.biome {
|
||||
Some(biome) => biome == ¤t_biome,
|
||||
None => true,
|
||||
})
|
||||
.choose(&mut rng);
|
||||
|
||||
if let Some(track) = maybe_track {
|
||||
@ -141,6 +150,13 @@ impl MusicMgr {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_biome(client: &Client) -> BiomeKind {
|
||||
match client.current_chunk() {
|
||||
Some(chunk) => chunk.meta().biome(),
|
||||
_ => BiomeKind::Void,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_soundtrack_items() -> SoundtrackCollection {
|
||||
match assets::load_file("voxygen.audio.soundtrack", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
|
@ -14,7 +14,7 @@ pub use self::{
|
||||
terrain::Terrain,
|
||||
};
|
||||
use crate::{
|
||||
audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
||||
audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
||||
render::{
|
||||
create_clouds_mesh, create_pp_mesh, create_skybox_mesh, CloudsLocals, CloudsPipeline,
|
||||
Consts, GlobalModel, Globals, Light, LodData, Model, PostProcessLocals,
|
||||
@ -103,6 +103,7 @@ pub struct Scene {
|
||||
figure_mgr: FigureMgr,
|
||||
sfx_mgr: SfxMgr,
|
||||
music_mgr: MusicMgr,
|
||||
ambient_mgr: AmbientMgr,
|
||||
}
|
||||
|
||||
pub struct SceneData<'a> {
|
||||
@ -308,6 +309,7 @@ impl Scene {
|
||||
figure_mgr: FigureMgr::new(renderer),
|
||||
sfx_mgr: SfxMgr::new(),
|
||||
music_mgr: MusicMgr::new(),
|
||||
ambient_mgr: AmbientMgr::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,6 +443,7 @@ impl Scene {
|
||||
renderer: &mut Renderer,
|
||||
audio: &mut AudioFrontend,
|
||||
scene_data: &SceneData,
|
||||
client: &Client,
|
||||
) {
|
||||
span!(_guard, "maintain", "Scene::maintain");
|
||||
// Get player position.
|
||||
@ -994,7 +997,8 @@ impl Scene {
|
||||
scene_data.player_entity,
|
||||
&self.camera,
|
||||
);
|
||||
self.music_mgr.maintain(audio, scene_data.state);
|
||||
self.music_mgr.maintain(audio, scene_data.state, client);
|
||||
self.ambient_mgr.maintain(audio, scene_data.state, client);
|
||||
}
|
||||
|
||||
/// Render the scene using the provided `Renderer`.
|
||||
|
@ -1077,6 +1077,7 @@ impl PlayState for SessionState {
|
||||
global_state.window.renderer_mut(),
|
||||
&mut global_state.audio,
|
||||
&scene_data,
|
||||
&client,
|
||||
);
|
||||
|
||||
// Process outcomes from client
|
||||
|
Loading…
Reference in New Issue
Block a user