mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
5b46587c2d
commit
b7ce91fead
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added music system
|
||||
|
||||
### Changed
|
||||
|
||||
### Removed
|
||||
|
46
assets/voxygen/audio/soundtrack.ron
Normal file
46
assets/voxygen/audio/soundtrack.ron
Normal 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
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
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
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
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
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
BIN
assets/voxygen/audio/soundtrack/wandering_voices.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
99
voxygen/src/audio/music.rs
Normal file
99
voxygen/src/audio/music.rs
Normal 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 == ¤t_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")
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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`.
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user