Remove ambient channel system in favor of sfx system

This commit is contained in:
jiminycrick 2020-10-31 18:00:08 -07:00
parent 39d4ee8a96
commit 0a9f1ee11c
18 changed files with 8 additions and 567 deletions

View File

@ -1,86 +0,0 @@
// TODO: Add an ambient-soundtrack that runs independently from the musical soundtrack
(
tracks: [
(
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",
),
(
title: "Crickets and Tawny Owl", // Ambience Track
path: "voxygen.audio.ambient.grassland_night",
length: 141.0,
timing: Some(Night),
biome: Some(Forest),
artist: "https://freesound.org/people/raoul_slayer/sounds/203598/",
),
//(
// 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: "Desert Night", // Ambience Track
path: "voxygen.audio.ambient.desert_night",
length: 328.0,
timing: Some(Night),
biome: Some(Desert),
artist: "https://freesound.org/people/kangaroovindaloo/sounds/483550/",
),
(
title: "Village Jungle Morning", // Ambience Track
path: "voxygen.audio.ambient.jungle_day",
length: 105.0,
timing: Some(Day),
biome: Some(Swamp),
artist: "https://freesound.org/people/aurelien.leveque/sounds/417635/",
),
(
title: "Jungle in Maharashtra at Night", // Ambience Track
path: "voxygen.audio.ambient.jungle_night",
length: 63.0,
timing: Some(Night),
biome: Some(Swamp),
artist: "https://freesound.org/people/Agim/sounds/417872/",
),
(
title: "Mountain Glacier Distant", // Ambience Track
path: "voxygen.audio.ambient.snowlands",
length: 22.0,
timing: Some(Day),
biome: Some(Snowlands),
artist: "https://freesound.org/people/Eelke/sounds/462623/",
),
(
title: "Mountain Glacier Distant", // Ambience Track
path: "voxygen.audio.ambient.snowlands",
length: 22.0,
timing: Some(Night),
biome: Some(Snowlands),
artist: "https://freesound.org/people/Eelke/sounds/462623/",
),
(
title: "Summer Meadow", // Ambience Track
path: "voxygen.audio.ambient.grassland_day",
length: 92.0,
timing: Some(Day),
biome: Some(Grassland),
artist: "https://freesound.org/people/baryy/sounds/409143/",
),
(
title: "Crickets and Tawny Owl", // Ambience Track
path: "voxygen.audio.ambient.grassland_night",
length: 141.0,
timing: Some(Night),
biome: Some(Grassland),
artist: "https://freesound.org/people/raoul_slayer/sounds/203598/",
),
]
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/audio/ambient/snowlands.ogg (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,214 +0,0 @@
//! 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 ambient sound 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,
}
/// Determines whether the sound is stopped, playing, or fading
#[derive(Debug, Deserialize, PartialEq)]
enum PlayState {
Playing,
Stopped,
FadingOut,
FadingIn,
}
/// Provides methods to control ambient sound playback
pub struct AmbientMgr {
ambient_soundtrack: AmbientSoundtrackCollection,
began_playing: Instant,
began_fading: 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,
last_biome: BiomeKind,
playing: PlayState,
}
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(),
began_fading: Instant::now(),
next_track_change: 0.0,
last_track: String::from("None"),
last_biome: BiomeKind::Void,
playing: PlayState::Stopped,
}
}
/// 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) {
// Gets the current player biome
let current_biome: BiomeKind = match client.current_chunk() {
Some(chunk) => chunk.meta().biome(),
_ => self.last_biome,
};
if audio.music_enabled() // TODO: Change this to ambient_enabled
&& !self.ambient_soundtrack.tracks.is_empty()
&& (self.began_playing.elapsed().as_secs_f64() > self.next_track_change
|| self.playing == PlayState::Stopped)
&& self.playing != PlayState::FadingOut
{
self.play_random_track(audio, state, client);
self.playing = PlayState::Playing;
} else if current_biome != self.last_biome && self.playing == PlayState::Playing {
audio.fade_out_exploration_ambient();
self.began_fading = Instant::now();
self.playing = PlayState::FadingOut;
} else if self.began_fading.elapsed().as_secs_f64() > 5.0
&& self.playing == PlayState::FadingOut
{
audio.stop_exploration_ambient();
self.playing = PlayState::Stopped;
}
self.last_biome = current_biome;
}
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
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);
let mut rng = thread_rng();
let maybe_track = self
.ambient_soundtrack
.tracks
.iter()
.filter(|track| match &track.timing {
Some(period_of_day) => period_of_day == &current_period_of_day,
None => true,
})
.filter(|track| match &track.biome {
Some(biome) => biome == &current_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;
audio.fade_in_exploration_ambient();
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 ambient sound config file, ambient sound will not be \
available: {}",
format!("{:#?}", error)
);
AmbientSoundtrackCollection::default()
},
},
Err(error) => {
warn!(
"Error reading ambient sound config file, ambient sound will not be \
available: {}",
format!("{:#?}", error)
);
AmbientSoundtrackCollection::default()
},
}
}
}

View File

@ -140,117 +140,6 @@ 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
};
}
/// Stop whatever is playing on a given channel
pub fn stop(&mut self, tag: AmbientChannelTag) {
self.tag = tag;
self.sink.stop();
}
/// 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

View File

@ -1,13 +1,12 @@
//! 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::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
use channel::{MusicChannel, MusicChannelTag, SfxChannel};
use fader::Fader;
use soundcache::SoundCache;
use std::time::Duration;
@ -39,10 +38,8 @@ 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,
}
@ -62,11 +59,9 @@ 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(),
}
}
@ -79,11 +74,9 @@ 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(),
}
}
@ -91,15 +84,10 @@ 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> {
@ -150,38 +138,6 @@ 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.music_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.music_volume));
// let mut next_ambient_channel =
// AmbientChannel::new(&audio_device);
// next_ambient_channel.set_fader(Fader::fade_in(12.0,
// self.music_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() {
@ -228,36 +184,6 @@ impl AudioFrontend {
}
}
//fn stop_ambient(&mut self, channel_tag: AmbientChannelTag) {
// if let Some(channel) = self.get_ambient_channel(channel_tag) {
// channel.stop(channel_tag);
// }
//}
//fn fade_out_ambient(&mut self, channel_tag: AmbientChannelTag) {
// let music_volume = self.music_volume;
// if let Some(channel) = self.get_ambient_channel(channel_tag) {
// channel.set_fader(Fader::fade_out(2.0, music_volume));
// }
//}
//fn fade_in_ambient(&mut self, channel_tag: AmbientChannelTag) {
// let music_volume = self.music_volume;
// if let Some(channel) = self.get_ambient_channel(channel_tag) {
// channel.set_fader(Fader::fade_in(2.0, music_volume));
// }
//}
//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();
@ -308,42 +234,14 @@ impl AudioFrontend {
}
}
//pub fn play_exploration_ambient(&mut self, item: &str) {
// if self.music_enabled() {
// self.play_ambient(item, AmbientChannelTag::Exploration)
// }
//}
//pub fn fade_out_exploration_ambient(&mut self) {
// if self.music_enabled() {
// self.fade_out_ambient(AmbientChannelTag::Exploration)
// }
//}
//pub fn fade_in_exploration_ambient(&mut self) {
// if self.music_enabled() {
// self.fade_in_ambient(AmbientChannelTag::Exploration)
// }
//}
//pub fn stop_exploration_ambient(&mut self) {
// if self.music_enabled() {
// self.stop_ambient(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.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 ambient_enabled(&self) -> bool { self.music_volume > 0.0 }
pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
self.sfx_volume = sfx_volume;
@ -360,14 +258,6 @@ impl AudioFrontend {
}
}
//pub fn set_ambient_volume(&mut self, ambient_volume: f32) {
// self.music_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();

View File

@ -11,10 +11,7 @@ use common::{
vol::RectRasterableVol,
};
use hashbrown::HashMap;
use rand::{
prelude::{IteratorRandom, SliceRandom},
thread_rng, Rng,
};
use rand::{thread_rng, Rng};
use specs::WorldExt;
use std::time::Instant;
use vek::*;
@ -34,15 +31,6 @@ impl Default for PreviousBlockState {
}
}
impl PreviousBlockState {
fn new(event: SfxEvent) -> Self {
PreviousBlockState {
event,
time: Instant::now(),
}
}
}
pub struct BlockEventMapper {
history: HashMap<Vec3<i32>, PreviousBlockState>,
}

View File

@ -32,7 +32,6 @@ impl Default for PreviousEntityState {
}
pub struct CampfireEventMapper {
timer: Instant,
event_history: HashMap<EcsEntity, PreviousEntityState>,
}
@ -91,7 +90,6 @@ impl EventMapper for CampfireEventMapper {
impl CampfireEventMapper {
pub fn new() -> Self {
Self {
timer: Instant::now(),
event_history: HashMap::new(),
}
}

View File

@ -47,7 +47,7 @@ impl EventMapper for MovementEventMapper {
player_entity: specs::Entity,
camera: &Camera,
triggers: &SfxTriggers,
terrain: &Terrain<TerrainChunk>,
_terrain: &Terrain<TerrainChunk>,
) {
let ecs = state.ecs();

View File

@ -32,7 +32,7 @@ impl EventMapper for ProgressionEventMapper {
player_entity: specs::Entity,
_camera: &Camera,
triggers: &SfxTriggers,
terrain: &Terrain<TerrainChunk>,
_terrain: &Terrain<TerrainChunk>,
) {
let ecs = state.ecs();