veloren_voxygen/audio/
ambience.rs1use crate::{
3 audio::{AudioFrontend, channel::AmbienceChannelTag},
4 scene::Camera,
5 settings::AudioSettings,
6};
7use client::Client;
8use common::{
9 assets::{self, AssetExt, AssetHandle},
10 terrain::site::SiteKindMeta,
11 vol::ReadVol,
12};
13use common_state::State;
14use serde::Deserialize;
15use strum::IntoEnumIterator;
16use tracing::warn;
17use vek::*;
18
19#[derive(Debug, Default, Deserialize)]
20pub struct AmbienceCollection {
21 tracks: Vec<AmbienceItem>,
22}
23
24#[derive(Debug, Deserialize)]
25pub struct AmbienceItem {
26 path: String,
27 tag: AmbienceChannelTag,
29 start: usize,
30 end: usize,
31}
32
33pub struct AmbienceMgr {
34 pub ambience: AssetHandle<AmbienceCollection>,
35}
36
37impl AmbienceMgr {
38 pub fn maintain(
39 &mut self,
40 audio: &mut AudioFrontend,
41 audio_settings: &AudioSettings,
42 state: &State,
43 client: &Client,
44 camera: &Camera,
45 ) {
46 if !audio.ambience_enabled() {
47 return;
48 }
49
50 let ambience_sounds = self.ambience.read();
51
52 let cam_pos = camera.get_pos_with_focus();
53
54 if state
56 .terrain()
57 .get(cam_pos.map(|e| e.floor() as i32))
58 .map(|b| b.is_liquid())
59 .unwrap_or(false)
60 {
61 audio.set_ambience_master_filter(888);
62 } else {
63 audio.set_ambience_master_filter(20000);
64 }
65
66 for tag in AmbienceChannelTag::iter() {
69 if let Some(inner) = audio.inner.as_mut()
72 && inner.channels.get_ambience_channel(tag).is_none()
73 {
74 inner.new_ambience_channel(tag);
75 let track = ambience_sounds.tracks.iter().find(|track| track.tag == tag);
76 if let Some(track) = track {
77 audio.play_ambience_looping(tag, &track.path, track.start, track.end);
78 }
79 }
80 if let Some(inner) = audio.inner.as_mut()
81 && let Some(channel) = inner.channels.get_ambience_channel(tag)
82 {
83 let target_volume =
86 if !audio_settings.rain_ambience_enabled && tag == AmbienceChannelTag::Rain {
87 0.0
88 } else {
89 get_target_volume(tag, client, camera)
90 };
91
92 channel.fade_to(target_volume, 1.0);
94 }
95 }
96 }
97}
98
99impl AmbienceChannelTag {
100 pub fn tag_max_volume(tag: AmbienceChannelTag) -> f32 {
101 match tag {
102 AmbienceChannelTag::Wind => 1.0,
103 AmbienceChannelTag::Rain => 0.95,
104 AmbienceChannelTag::ThunderRumbling => 1.33,
105 AmbienceChannelTag::Leaves => 1.33,
106 AmbienceChannelTag::Cave => 1.0,
107 _ => 1.0,
108 }
109 }
110
111 pub fn get_tag_volume(tag: AmbienceChannelTag, client: &Client, camera: &Camera) -> f32 {
113 match tag {
114 AmbienceChannelTag::Wind => {
115 let focus_off = camera.get_focus_pos().map(f32::trunc);
116 let cam_pos = camera.dependents().cam_pos + focus_off;
117
118 let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
119 (chunk.meta().alt(), chunk.meta().tree_density())
120 } else {
121 (0.0, 0.0)
122 };
123
124 let alt_factor = (cam_pos.z / 1200.0).abs();
126
127 let tree_factor = ((1.0 - (tree_density * 0.5))
131 + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
132 .min(1.0);
133
134 let wind_speed_factor = (client.weather_at_player().wind.magnitude_squared()
139 / 15.0_f32.powi(2))
140 .min(1.33);
141
142 (alt_factor
143 * tree_factor
144 * (wind_speed_factor + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)))
145 + (alt_factor * 0.15) * tree_factor
146 },
147 AmbienceChannelTag::Rain => {
148 let focus_off = camera.get_focus_pos().map(f32::trunc);
149 let cam_pos = camera.dependents().cam_pos + focus_off;
150
151 let terrain_alt = if let Some(chunk) = client.current_chunk() {
152 chunk.meta().alt()
153 } else {
154 0.0
155 };
156 let camera_factor = 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
158
159 (client.weather_at_player().rain * 3.0) * camera_factor
160 },
161 AmbienceChannelTag::ThunderRumbling => {
162 let rain_intensity = client.weather_at_player().rain * 3.0;
163
164 if rain_intensity < 0.7 {
165 0.0
166 } else {
167 rain_intensity
168 }
169 },
170 AmbienceChannelTag::Leaves => {
171 let focus_off = camera.get_focus_pos().map(f32::trunc);
172 let cam_pos = camera.dependents().cam_pos + focus_off;
173
174 let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
175 (chunk.meta().alt(), chunk.meta().tree_density())
176 } else {
177 (0.0, 0.0)
178 };
179
180 let tree_factor = 1.0
184 - (((1.0 - tree_density)
185 + ((cam_pos.z - terrain_alt - 20.0).abs() / 150.0).powi(2))
186 .min(1.1));
187
188 let wind_speed_factor = (client.weather_at_player().wind.magnitude_squared()
190 / 20.0_f32.powi(2))
191 .min(1.0);
192
193 if tree_factor > 0.1 {
194 tree_factor * (1.0 + wind_speed_factor)
195 } else {
196 0.0
197 }
198 },
199 AmbienceChannelTag::Cave => {
200 let focus_off = camera.get_focus_pos().map(f32::trunc);
201 let cam_pos = camera.dependents().cam_pos + focus_off;
202
203 let terrain_alt = if let Some(chunk) = client.current_chunk() {
204 chunk.meta().alt()
205 } else {
206 0.0
207 };
208
209 let camera_factor = (-(cam_pos.z - terrain_alt) / 100.0).max(0.0);
211
212 if client.current_site() == SiteKindMeta::Cave {
213 camera_factor
214 } else {
215 0.0
216 }
217 },
218 _ => 1.0,
219 }
220 }
221}
222
223fn get_target_volume(tag: AmbienceChannelTag, client: &Client, camera: &Camera) -> f32 {
225 let focus_off = camera.get_focus_pos().map(f32::trunc);
226 let cam_pos = camera.dependents().cam_pos + focus_off;
227
228 let volume: f32 = AmbienceChannelTag::get_tag_volume(tag, client, camera);
229
230 let terrain_alt = if let Some(chunk) = client.current_chunk() {
231 chunk.meta().alt()
232 } else {
233 0.0
234 };
235
236 if tag != AmbienceChannelTag::Cave {
239 (volume * ((cam_pos.z - terrain_alt) / 50.0 + 1.0).clamped(0.0, 1.0))
240 .min(AmbienceChannelTag::tag_max_volume(tag))
241 } else {
242 volume.min(AmbienceChannelTag::tag_max_volume(tag))
243 }
244}
245
246pub fn load_ambience_items() -> AssetHandle<AmbienceCollection> {
247 AmbienceCollection::load_or_insert_with("voxygen.audio.ambience", |error| {
248 warn!(
249 "Error reading ambience config file, ambience will not be available: {:#?}",
250 error
251 );
252 AmbienceCollection::default()
253 })
254}
255
256impl assets::Asset for AmbienceCollection {
257 type Loader = assets::RonLoader;
258
259 const EXTENSION: &'static str = "ron";
260}