1use core::cmp::Reverse;
6
7use platform::{thread_pool::ThreadPool, Instant, Platform, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE};
8
9use crate::{
10 allocators::LinearAllocator,
11 collections::FixedVec,
12 multithreading::parallelize,
13 resources::{
14 audio_clip::AudioClipHandle, ResourceDatabase, ResourceLoader, AUDIO_SAMPLES_PER_CHUNK,
15 },
16};
17
18#[derive(Debug)]
19struct PlayingClip {
20 channel: usize,
21 clip: AudioClipHandle,
22 start_position: u64,
23}
24
25impl PlayingClip {
26 fn get_end(&self, resources: &ResourceDatabase) -> u64 {
27 self.start_position + resources.get_audio_clip(self.clip).samples as u64
28 }
29}
30
31#[derive(Debug)]
34pub struct ChannelSettings {
35 pub volume: u8,
37}
38
39pub struct Mixer {
41 playing_clips: FixedVec<'static, PlayingClip>,
42 pub channels: FixedVec<'static, ChannelSettings>,
44 playback_buffer: FixedVec<'static, [i16; AUDIO_CHANNELS]>,
45 playback_position: u64,
48}
49
50impl Mixer {
51 pub fn new(
65 arena: &'static LinearAllocator,
66 channel_count: usize,
67 max_playing_clips: usize,
68 playback_buffer_length: usize,
69 ) -> Option<Mixer> {
70 let mut playback_buffer = FixedVec::new(arena, playback_buffer_length)?;
71 playback_buffer.fill_with_zeroes();
72
73 let playing_clips = FixedVec::new(arena, max_playing_clips)?;
74
75 let mut channels = FixedVec::new(arena, channel_count)?;
76 for _ in 0..channel_count {
77 channels.push(ChannelSettings { volume: 0xFF }).unwrap();
78 }
79
80 Some(Mixer {
81 playing_clips,
82 channels,
83 playback_buffer,
84 playback_position: 0,
85 })
86 }
87
88 pub fn play_clip(
100 &mut self,
101 channel: usize,
102 clip: AudioClipHandle,
103 important: bool,
104 resources: &ResourceDatabase,
105 ) -> bool {
106 if channel >= self.channels.len() {
107 return false;
108 }
109
110 let playing_clip = PlayingClip {
111 channel,
112 clip,
113 start_position: self.playback_position,
114 };
115
116 if !self.playing_clips.is_full() {
117 self.playing_clips.push(playing_clip).unwrap();
118 } else if important {
119 if self.playing_clips.is_empty() {
120 return false; }
122
123 let mut lowest_end_time = self.playing_clips[0].get_end(resources);
124 let mut candidate_index = 0;
125 for (i, clip) in self.playing_clips.iter().enumerate().skip(1) {
126 let end_time = clip.get_end(resources);
127 if end_time < lowest_end_time {
128 lowest_end_time = end_time;
129 candidate_index = i;
130 }
131 }
132
133 self.playing_clips[candidate_index] = playing_clip;
134 } else {
135 return false;
136 }
137
138 true
139 }
140
141 pub fn update_audio_sync(&mut self, frame_timestamp: Instant, platform: &dyn Platform) {
146 let (playback_position, playback_timestamp) = platform.audio_playback_position();
147 if let Some(time_since_playback_pos) = frame_timestamp.duration_since(playback_timestamp) {
148 let frame_offset_from_playback_pos =
149 time_since_playback_pos.as_micros() * AUDIO_SAMPLE_RATE as u128 / 1_000_000;
150 self.playback_position = playback_position + frame_offset_from_playback_pos as u64;
151 } else {
152 self.playback_position = playback_position;
153 }
154 }
155
156 pub fn render_audio(
161 &mut self,
162 thread_pool: &mut ThreadPool,
163 platform: &dyn Platform,
164 resources: &ResourceDatabase,
165 resource_loader: &mut ResourceLoader,
166 ) {
167 profiling::function_scope!();
168 self.playing_clips
170 .sort_unstable_by_key(|clip| Reverse(clip.get_end(resources)));
171 if let Some(finished_clips_start_index) = (self.playing_clips)
172 .iter()
173 .position(|clip| clip.get_end(resources) < self.playback_position)
174 {
175 self.playing_clips.truncate(finished_clips_start_index);
176 }
177
178 parallelize(
180 thread_pool,
181 &mut self.playback_buffer,
182 |playback_buffer, offset| {
183 profiling::scope!("mix audio");
184 playback_buffer.fill([0; AUDIO_CHANNELS]);
185 let playback_start = self.playback_position + offset as u64;
186 for clip in &*self.playing_clips {
187 let volume = self.channels[clip.channel].volume;
188 let asset = resources.get_audio_clip(clip.clip);
189
190 let already_played = playback_start.saturating_sub(clip.start_position) as u32;
191 let first_chunk =
192 asset.chunks.start + already_played / AUDIO_SAMPLES_PER_CHUNK as u32;
193 let last_chunk =
194 asset.chunks.start + asset.samples / AUDIO_SAMPLES_PER_CHUNK as u32;
195
196 let mut playback_offset =
197 clip.start_position.saturating_sub(playback_start) as usize;
198 for chunk_index in first_chunk..=last_chunk {
199 if playback_buffer.len() <= playback_offset {
200 break;
201 }
202
203 let chunk_start =
204 (chunk_index - asset.chunks.start) * AUDIO_SAMPLES_PER_CHUNK as u32;
205 let chunk_end =
206 (chunk_index - asset.chunks.start + 1) * AUDIO_SAMPLES_PER_CHUNK as u32;
207
208 if let Some(chunk) = &resources.chunks.get(chunk_index) {
209 let chunk_samples =
210 bytemuck::cast_slice::<u8, [i16; AUDIO_CHANNELS]>(&chunk.0);
211 let first_sample_idx =
212 (already_played.max(chunk_start) - chunk_start) as usize;
213 let last_sample_idx =
214 (asset.samples.min(chunk_end).saturating_sub(chunk_start)) as usize;
215 if first_sample_idx < last_sample_idx {
216 render_audio_chunk(
217 &chunk_samples[first_sample_idx..last_sample_idx],
218 &mut playback_buffer[playback_offset..],
219 volume,
220 );
221 playback_offset += last_sample_idx - first_sample_idx;
222 }
223 } else {
224 break;
225 }
226 }
227 }
228 },
229 );
230
231 platform.update_audio_buffer(self.playback_position, &self.playback_buffer);
233
234 for clip in &*self.playing_clips {
236 let asset = resources.get_audio_clip(clip.clip);
237 let current_pos = self.playback_position.saturating_sub(clip.start_position);
238 let current_chunk_index = (current_pos / AUDIO_SAMPLES_PER_CHUNK as u64) as u32;
239 let next_chunk_index = current_chunk_index + 1;
240
241 resource_loader.queue_chunk(asset.chunks.start + current_chunk_index, resources);
242 if asset.chunks.start + next_chunk_index < asset.chunks.end {
243 resource_loader.queue_chunk(asset.chunks.start + next_chunk_index, resources);
244 }
245 }
246 }
247}
248
249fn render_audio_chunk(
250 chunk_samples: &[[i16; AUDIO_CHANNELS]],
251 dst: &mut [[i16; AUDIO_CHANNELS]],
252 volume: u8,
253) {
254 profiling::function_scope!();
255 for (dst, sample) in dst.iter_mut().zip(chunk_samples) {
256 for channel in 0..AUDIO_CHANNELS {
257 let sample = sample[channel];
258 let attenuated = ((sample as i32 * volume as i32) / u8::MAX as i32) as i16;
259 dst[channel] += attenuated;
260 }
261 }
262}