Merge branch 'DaforLynx/event-music' into 'master'

Event music can use more than one event at a time. Added test for soundtracks.

See merge request veloren/veloren!3686
This commit is contained in:
Samuel Keiffer 2022-11-07 22:39:25 +00:00
commit 97f3fa56d3
4 changed files with 125 additions and 57 deletions

View File

@ -0,0 +1,13 @@
// Times: Some(Day or Night) or None [both]
// Weathers: Some(Clear, Cloudy, Rain, or Storm) or None [any weather]
// Biomes: Grassland, Forest, Desert, Snowland, Lake, Mountain, Ocean, Jungle, Savannah, Taiga
// planned biomes: Swamp
// Number after biome indicates weighting; higher numbers are less frequent
// Sites: Settlement(Default, Cliff, or Desert), Cave, Dungeon(Old or Gnarling), or Void [none]
// Music states: Activity(Explore or Combat)
// Combat music is looped. Needs three files: start, loop, and end. Start contains leadup to the loop.
// It's recommended to also have appropriate metadata for those who listen via the game files :)
(
tracks: []
)

View File

@ -142,7 +142,7 @@
"voxygen.audio.sfx.footsteps.stepdirt_4", "voxygen.audio.sfx.footsteps.stepdirt_4",
"voxygen.audio.sfx.footsteps.stepdirt_5", "voxygen.audio.sfx.footsteps.stepdirt_5",
], ],
threshold: 1.6, threshold: 1.8,
), ),
QuadRun(Earth): ( QuadRun(Earth): (
files: [ files: [
@ -152,7 +152,7 @@
"voxygen.audio.sfx.footsteps.stepdirt_4", "voxygen.audio.sfx.footsteps.stepdirt_4",
"voxygen.audio.sfx.footsteps.stepdirt_5", "voxygen.audio.sfx.footsteps.stepdirt_5",
], ],
threshold: 0.8, threshold: 0.9,
), ),
Run(Grass): ( Run(Grass): (
files: [ files: [

View File

@ -1,8 +1,9 @@
use chrono::{DateTime, Datelike, Local, TimeZone, Utc}; use chrono::{DateTime, Datelike, Local, TimeZone, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::EnumIter;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter)]
#[repr(u16)] #[repr(u16)]
pub enum CalendarEvent { pub enum CalendarEvent {
Christmas = 0, Christmas = 0,

View File

@ -56,7 +56,7 @@ use hashbrown::HashMap;
use rand::{prelude::SliceRandom, thread_rng, Rng}; use rand::{prelude::SliceRandom, thread_rng, Rng};
use serde::Deserialize; use serde::Deserialize;
use std::time::Instant; use std::time::Instant;
use tracing::{debug, trace, warn}; use tracing::{debug, trace};
/// Collection of all the tracks /// Collection of all the tracks
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -151,7 +151,7 @@ enum PlayState {
/// Provides methods to control music playback /// Provides methods to control music playback
pub struct MusicMgr { pub struct MusicMgr {
/// Collection of all the tracks /// Collection of all the tracks
soundtrack: AssetHandle<SoundtrackCollection<SoundtrackItem>>, soundtrack: SoundtrackCollection<SoundtrackItem>,
/// Instant at which the current track began playing /// Instant at which the current track began playing
began_playing: Instant, began_playing: Instant,
/// Time until the next track should be played /// Time until the next track should be played
@ -310,7 +310,7 @@ impl MusicMgr {
} }
if audio.music_enabled() if audio.music_enabled()
&& !self.soundtrack.read().tracks.is_empty() && !self.soundtrack.tracks.is_empty()
&& (self.began_playing.elapsed().as_secs_f32() > self.next_track_change || interrupt) && (self.began_playing.elapsed().as_secs_f32() > self.next_track_change || interrupt)
{ {
if interrupt { if interrupt {
@ -378,10 +378,10 @@ impl MusicMgr {
// too many constraints. Returning Err(()) signals that we couldn't find // too many constraints. Returning Err(()) signals that we couldn't find
// an appropriate track for the current state, and hence the state // an appropriate track for the current state, and hence the state
// machine for the activity shouldn't be updated. // machine for the activity shouldn't be updated.
let soundtrack = self.soundtrack.read();
// First, filter out tracks not matching the timing, site, biome, and current // First, filter out tracks not matching the timing, site, biome, and current
// activity // activity
let mut maybe_tracks = soundtrack let mut maybe_tracks = self
.soundtrack
.tracks .tracks
.iter() .iter()
.filter(|track| { .filter(|track| {
@ -487,24 +487,60 @@ impl MusicMgr {
} }
} }
fn load_soundtrack_items( /// Loads default soundtrack if no events are active. Otherwise, attempts to
calendar: &Calendar, /// compile and load all active event soundtracks, falling back to default
) -> AssetHandle<SoundtrackCollection<SoundtrackItem>> { /// if they are empty.
// Cannot fail: A default value is always provided fn load_soundtrack_items(calendar: &Calendar) -> SoundtrackCollection<SoundtrackItem> {
let mut soundtrack = SoundtrackCollection::load_expect("voxygen.audio.soundtrack"); let mut soundtrack = SoundtrackCollection::default();
// Loads default soundtrack if no events are active
if calendar.events().len() == 0 { if calendar.events().len() == 0 {
soundtrack for track in SoundtrackCollection::load_expect("voxygen.audio.soundtrack")
.read()
.tracks
.clone()
{
soundtrack.tracks.push(track)
}
} else { } else {
// Compiles event-specific soundtracks if any are active
for event in calendar.events() { for event in calendar.events() {
match event { match event {
CalendarEvent::Halloween => { CalendarEvent::Halloween => {
soundtrack = SoundtrackCollection::load_expect( for track in SoundtrackCollection::load_expect(
"voxygen.audio.calendar.halloween.soundtrack", "voxygen.audio.calendar.halloween.soundtrack",
); )
.read()
.tracks
.clone()
{
soundtrack.tracks.push(track)
}
},
CalendarEvent::Christmas => {
for track in SoundtrackCollection::load_expect(
"voxygen.audio.calendar.christmas.soundtrack",
)
.read()
.tracks
.clone()
{
soundtrack.tracks.push(track)
}
}, },
_ => soundtrack = SoundtrackCollection::load_expect("voxygen.audio.soundtrack"),
} }
} }
}
// Fallback if events are active but give an empty tracklist
if soundtrack.tracks.is_empty() {
for track in SoundtrackCollection::load_expect("voxygen.audio.soundtrack")
.read()
.tracks
.clone()
{
soundtrack.tracks.push(track)
}
soundtrack
} else {
soundtrack soundtrack
} }
} }
@ -517,13 +553,11 @@ impl assets::Asset for SoundtrackCollection<RawSoundtrackItem> {
impl assets::Compound for SoundtrackCollection<SoundtrackItem> { impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
fn load(_: assets::AnyCache, id: &str) -> Result<Self, assets::BoxedError> { fn load(_: assets::AnyCache, id: &str) -> Result<Self, assets::BoxedError> {
let inner = || -> Result<_, assets::Error> { let manifest: AssetHandle<SoundtrackCollection<RawSoundtrackItem>> = AssetExt::load(id)?;
let manifest: AssetHandle<SoundtrackCollection<RawSoundtrackItem>> = let mut soundtrack = SoundtrackCollection::default();
AssetExt::load(id)?;
let mut soundtracks = SoundtrackCollection::default();
for item in manifest.read().tracks.iter().cloned() { for item in manifest.read().tracks.iter().cloned() {
match item { match item {
RawSoundtrackItem::Individual(track) => soundtracks.tracks.push(track), RawSoundtrackItem::Individual(track) => soundtrack.tracks.push(track),
RawSoundtrackItem::Segmented { RawSoundtrackItem::Segmented {
title, title,
timing, timing,
@ -534,7 +568,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
artist, artist,
} => { } => {
for (path, length, music_state, activity_override) in segments.into_iter() { for (path, length, music_state, activity_override) in segments.into_iter() {
soundtracks.tracks.push(SoundtrackItem { soundtrack.tracks.push(SoundtrackItem {
title: title.clone(), title: title.clone(),
path, path,
length, length,
@ -550,14 +584,34 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
}, },
} }
} }
Ok(soundtracks) Ok(soundtrack)
}; }
match inner() { }
Ok(soundtracks) => Ok(soundtracks),
Err(e) => { #[cfg(test)]
warn!("Error loading soundtracks: {:?}", e); mod tests {
Ok(SoundtrackCollection::default()) use super::*;
use strum::IntoEnumIterator;
#[test]
fn test_load_soundtracks() {
let _: AssetHandle<SoundtrackCollection<SoundtrackItem>> =
SoundtrackCollection::load_expect("voxygen.audio.soundtrack");
for event in CalendarEvent::iter() {
match event {
CalendarEvent::Halloween => {
let _: AssetHandle<SoundtrackCollection<SoundtrackItem>> =
SoundtrackCollection::load_expect(
"voxygen.audio.calendar.halloween.soundtrack",
);
},
CalendarEvent::Christmas => {
let _: AssetHandle<SoundtrackCollection<SoundtrackItem>> =
SoundtrackCollection::load_expect(
"voxygen.audio.calendar.christmas.soundtrack",
);
}, },
} }
} }
} }
}