1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | #include <SDL.h> |
4 | |
5 | #include <base/math.h> |
6 | #include <base/system.h> |
7 | |
8 | #include <engine/graphics.h> |
9 | #include <engine/shared/config.h> |
10 | #include <engine/storage.h> |
11 | |
12 | #include "sound.h" |
13 | |
14 | #if defined(CONF_VIDEORECORDER) |
15 | #include <engine/shared/video.h> |
16 | #endif |
17 | extern "C" { |
18 | #include <opusfile.h> |
19 | #include <wavpack.h> |
20 | } |
21 | |
22 | #include <cmath> |
23 | |
24 | static constexpr int SAMPLE_INDEX_USED = -2; |
25 | static constexpr int SAMPLE_INDEX_FULL = -1; |
26 | |
27 | void CSound::Mix(short *pFinalOut, unsigned Frames) |
28 | { |
29 | Frames = minimum(a: Frames, b: m_MaxFrames); |
30 | mem_zero(block: m_pMixBuffer, size: Frames * 2 * sizeof(int)); |
31 | |
32 | // acquire lock while we are mixing |
33 | m_SoundLock.lock(); |
34 | |
35 | const int MasterVol = m_SoundVolume.load(m: std::memory_order_relaxed); |
36 | |
37 | for(auto &Voice : m_aVoices) |
38 | { |
39 | if(!Voice.m_pSample) |
40 | continue; |
41 | |
42 | // mix voice |
43 | int *pOut = m_pMixBuffer; |
44 | |
45 | const int Step = Voice.m_pSample->m_Channels; // setup input sources |
46 | short *pInL = &Voice.m_pSample->m_pData[Voice.m_Tick * Step]; |
47 | short *pInR = &Voice.m_pSample->m_pData[Voice.m_Tick * Step + 1]; |
48 | |
49 | unsigned End = Voice.m_pSample->m_NumFrames - Voice.m_Tick; |
50 | |
51 | int VolumeR = round_truncate(f: Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f)); |
52 | int VolumeL = VolumeR; |
53 | |
54 | // make sure that we don't go outside the sound data |
55 | if(Frames < End) |
56 | End = Frames; |
57 | |
58 | // check if we have a mono sound |
59 | if(Voice.m_pSample->m_Channels == 1) |
60 | pInR = pInL; |
61 | |
62 | // volume calculation |
63 | if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan) |
64 | { |
65 | // TODO: we should respect the channel panning value |
66 | const int dx = Voice.m_X - m_CenterX.load(m: std::memory_order_relaxed); |
67 | const int dy = Voice.m_Y - m_CenterY.load(m: std::memory_order_relaxed); |
68 | float FalloffX = 0.0f; |
69 | float FalloffY = 0.0f; |
70 | |
71 | int RangeX = 0; // for panning |
72 | bool InVoiceField = false; |
73 | |
74 | switch(Voice.m_Shape) |
75 | { |
76 | case ISound::SHAPE_CIRCLE: |
77 | { |
78 | const float Radius = Voice.m_Circle.m_Radius; |
79 | RangeX = Radius; |
80 | |
81 | // dx and dy can be larger than 46341 and thus the calculation would go beyond the limits of a integer, |
82 | // therefore we cast them into float |
83 | const int Dist = (int)length(a: vec2(dx, dy)); |
84 | if(Dist < Radius) |
85 | { |
86 | InVoiceField = true; |
87 | |
88 | // falloff |
89 | int FalloffDistance = Radius * Voice.m_Falloff; |
90 | if(Dist > FalloffDistance) |
91 | FalloffX = FalloffY = (Radius - Dist) / (Radius - FalloffDistance); |
92 | else |
93 | FalloffX = FalloffY = 1.0f; |
94 | } |
95 | else |
96 | InVoiceField = false; |
97 | |
98 | break; |
99 | } |
100 | |
101 | case ISound::SHAPE_RECTANGLE: |
102 | { |
103 | RangeX = Voice.m_Rectangle.m_Width / 2.0f; |
104 | |
105 | const int abs_dx = absolute(a: dx); |
106 | const int abs_dy = absolute(a: dy); |
107 | |
108 | const int w = Voice.m_Rectangle.m_Width / 2.0f; |
109 | const int h = Voice.m_Rectangle.m_Height / 2.0f; |
110 | |
111 | if(abs_dx < w && abs_dy < h) |
112 | { |
113 | InVoiceField = true; |
114 | |
115 | // falloff |
116 | int fx = Voice.m_Falloff * w; |
117 | int fy = Voice.m_Falloff * h; |
118 | |
119 | FalloffX = abs_dx > fx ? (float)(w - abs_dx) / (w - fx) : 1.0f; |
120 | FalloffY = abs_dy > fy ? (float)(h - abs_dy) / (h - fy) : 1.0f; |
121 | } |
122 | else |
123 | InVoiceField = false; |
124 | |
125 | break; |
126 | } |
127 | }; |
128 | |
129 | if(InVoiceField) |
130 | { |
131 | // panning |
132 | if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING)) |
133 | { |
134 | if(dx > 0) |
135 | VolumeL = ((RangeX - absolute(a: dx)) * VolumeL) / RangeX; |
136 | else |
137 | VolumeR = ((RangeX - absolute(a: dx)) * VolumeR) / RangeX; |
138 | } |
139 | |
140 | { |
141 | VolumeL *= FalloffX * FalloffY; |
142 | VolumeR *= FalloffX * FalloffY; |
143 | } |
144 | } |
145 | else |
146 | { |
147 | VolumeL = 0; |
148 | VolumeR = 0; |
149 | } |
150 | } |
151 | |
152 | // process all frames |
153 | for(unsigned s = 0; s < End; s++) |
154 | { |
155 | *pOut++ += (*pInL) * VolumeL; |
156 | *pOut++ += (*pInR) * VolumeR; |
157 | pInL += Step; |
158 | pInR += Step; |
159 | Voice.m_Tick++; |
160 | } |
161 | |
162 | // free voice if not used any more |
163 | if(Voice.m_Tick == Voice.m_pSample->m_NumFrames) |
164 | { |
165 | if(Voice.m_Flags & ISound::FLAG_LOOP) |
166 | Voice.m_Tick = 0; |
167 | else |
168 | { |
169 | Voice.m_pSample = nullptr; |
170 | Voice.m_Age++; |
171 | } |
172 | } |
173 | } |
174 | |
175 | m_SoundLock.unlock(); |
176 | |
177 | // clamp accumulated values |
178 | for(unsigned i = 0; i < Frames * 2; i++) |
179 | pFinalOut[i] = clamp<int>(val: ((m_pMixBuffer[i] * MasterVol) / 101) >> 8, lo: std::numeric_limits<short>::min(), hi: std::numeric_limits<short>::max()); |
180 | |
181 | #if defined(CONF_ARCH_ENDIAN_BIG) |
182 | swap_endian(pFinalOut, sizeof(short), Frames * 2); |
183 | #endif |
184 | } |
185 | |
186 | static void SdlCallback(void *pUser, Uint8 *pStream, int Len) |
187 | { |
188 | CSound *pSound = static_cast<CSound *>(pUser); |
189 | |
190 | #if defined(CONF_VIDEORECORDER) |
191 | if(!(IVideo::Current() && g_Config.m_ClVideoSndEnable)) |
192 | { |
193 | pSound->Mix(pFinalOut: (short *)pStream, Frames: Len / sizeof(short) / 2); |
194 | } |
195 | else |
196 | { |
197 | mem_zero(block: pStream, size: Len); |
198 | } |
199 | #else |
200 | pSound->Mix((short *)pStream, Len / sizeof(short) / 2); |
201 | #endif |
202 | } |
203 | |
204 | int CSound::Init() |
205 | { |
206 | m_SoundEnabled = false; |
207 | m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); |
208 | m_pStorage = Kernel()->RequestInterface<IStorage>(); |
209 | |
210 | // Initialize sample indices. We always need them to load sounds in |
211 | // the editor even if sound is disabled or failed to be enabled. |
212 | m_FirstFreeSampleIndex = 0; |
213 | for(size_t i = 0; i < std::size(m_aSamples) - 1; ++i) |
214 | { |
215 | m_aSamples[i].m_Index = i; |
216 | m_aSamples[i].m_NextFreeSampleIndex = i + 1; |
217 | m_aSamples[i].m_pData = nullptr; |
218 | } |
219 | m_aSamples[std::size(m_aSamples) - 1].m_Index = std::size(m_aSamples) - 1; |
220 | m_aSamples[std::size(m_aSamples) - 1].m_NextFreeSampleIndex = SAMPLE_INDEX_FULL; |
221 | |
222 | if(!g_Config.m_SndEnable) |
223 | return 0; |
224 | |
225 | if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) |
226 | { |
227 | dbg_msg(sys: "sound" , fmt: "unable to init SDL audio: %s" , SDL_GetError()); |
228 | return -1; |
229 | } |
230 | |
231 | m_MixingRate = g_Config.m_SndRate; |
232 | |
233 | SDL_AudioSpec Format, FormatOut; |
234 | Format.freq = m_MixingRate; |
235 | Format.format = AUDIO_S16; |
236 | Format.channels = 2; |
237 | Format.samples = g_Config.m_SndBufferSize; |
238 | Format.callback = SdlCallback; |
239 | Format.userdata = this; |
240 | |
241 | // Open the audio device and start playing sound! |
242 | m_Device = SDL_OpenAudioDevice(device: nullptr, iscapture: 0, desired: &Format, obtained: &FormatOut, allowed_changes: 0); |
243 | if(m_Device == 0) |
244 | { |
245 | dbg_msg(sys: "sound" , fmt: "unable to open audio: %s" , SDL_GetError()); |
246 | return -1; |
247 | } |
248 | else |
249 | dbg_msg(sys: "sound" , fmt: "sound init successful using audio driver '%s'" , SDL_GetCurrentAudioDriver()); |
250 | |
251 | m_MaxFrames = FormatOut.samples * 2; |
252 | #if defined(CONF_VIDEORECORDER) |
253 | m_MaxFrames = maximum<uint32_t>(a: m_MaxFrames, b: 1024 * 2); // make the buffer bigger just in case |
254 | #endif |
255 | m_pMixBuffer = (int *)calloc(nmemb: m_MaxFrames * 2, size: sizeof(int)); |
256 | |
257 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 0); |
258 | |
259 | m_SoundEnabled = true; |
260 | Update(); |
261 | return 0; |
262 | } |
263 | |
264 | int CSound::Update() |
265 | { |
266 | UpdateVolume(); |
267 | return 0; |
268 | } |
269 | |
270 | void CSound::UpdateVolume() |
271 | { |
272 | int WantedVolume = g_Config.m_SndVolume; |
273 | if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute) |
274 | WantedVolume = 0; |
275 | m_SoundVolume.store(i: WantedVolume, m: std::memory_order_relaxed); |
276 | } |
277 | |
278 | void CSound::Shutdown() |
279 | { |
280 | for(unsigned SampleId = 0; SampleId < NUM_SAMPLES; SampleId++) |
281 | { |
282 | UnloadSample(SampleId); |
283 | } |
284 | |
285 | SDL_CloseAudioDevice(dev: m_Device); |
286 | SDL_QuitSubSystem(SDL_INIT_AUDIO); |
287 | free(ptr: m_pMixBuffer); |
288 | m_pMixBuffer = nullptr; |
289 | } |
290 | |
291 | CSample *CSound::AllocSample() |
292 | { |
293 | if(m_FirstFreeSampleIndex == SAMPLE_INDEX_FULL) |
294 | return nullptr; |
295 | |
296 | CSample *pSample = &m_aSamples[m_FirstFreeSampleIndex]; |
297 | if(pSample->m_pData != nullptr || pSample->m_NextFreeSampleIndex == SAMPLE_INDEX_USED) |
298 | { |
299 | char aError[128]; |
300 | str_format(buffer: aError, buffer_size: sizeof(aError), format: "Sample was not unloaded (index=%d, next=%d, duration=%f, data=%p)" , |
301 | pSample->m_Index, pSample->m_NextFreeSampleIndex, pSample->TotalTime(), pSample->m_pData); |
302 | dbg_assert(false, aError); |
303 | } |
304 | m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex; |
305 | pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED; |
306 | return pSample; |
307 | } |
308 | |
309 | void CSound::RateConvert(CSample &Sample) const |
310 | { |
311 | dbg_assert(Sample.m_pData != nullptr, "Sample is not loaded" ); |
312 | // make sure that we need to convert this sound |
313 | if(Sample.m_Rate == m_MixingRate) |
314 | return; |
315 | |
316 | // allocate new data |
317 | const int NumFrames = (int)((Sample.m_NumFrames / (float)Sample.m_Rate) * m_MixingRate); |
318 | short *pNewData = (short *)calloc(nmemb: (size_t)NumFrames * Sample.m_Channels, size: sizeof(short)); |
319 | |
320 | for(int i = 0; i < NumFrames; i++) |
321 | { |
322 | // resample TODO: this should be done better, like linear at least |
323 | float a = i / (float)NumFrames; |
324 | int f = (int)(a * Sample.m_NumFrames); |
325 | if(f >= Sample.m_NumFrames) |
326 | f = Sample.m_NumFrames - 1; |
327 | |
328 | // set new data |
329 | if(Sample.m_Channels == 1) |
330 | pNewData[i] = Sample.m_pData[f]; |
331 | else if(Sample.m_Channels == 2) |
332 | { |
333 | pNewData[i * 2] = Sample.m_pData[f * 2]; |
334 | pNewData[i * 2 + 1] = Sample.m_pData[f * 2 + 1]; |
335 | } |
336 | } |
337 | |
338 | // free old data and apply new |
339 | free(ptr: Sample.m_pData); |
340 | Sample.m_pData = pNewData; |
341 | Sample.m_NumFrames = NumFrames; |
342 | Sample.m_Rate = m_MixingRate; |
343 | } |
344 | |
345 | bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const |
346 | { |
347 | int OpusError = 0; |
348 | OggOpusFile *pOpusFile = op_open_memory(data: (const unsigned char *)pData, size: DataSize, error: &OpusError); |
349 | if(pOpusFile) |
350 | { |
351 | const int NumChannels = op_channel_count(of: pOpusFile, li: -1); |
352 | if(NumChannels > 2) |
353 | { |
354 | op_free(of: pOpusFile); |
355 | dbg_msg(sys: "sound/opus" , fmt: "file is not mono or stereo." ); |
356 | return false; |
357 | } |
358 | |
359 | const int NumSamples = op_pcm_total(of: pOpusFile, li: -1); // per channel! |
360 | if(NumSamples < 0) |
361 | { |
362 | op_free(of: pOpusFile); |
363 | dbg_msg(sys: "sound/opus" , fmt: "failed to get number of samples, error %d" , NumSamples); |
364 | return false; |
365 | } |
366 | |
367 | short *pSampleData = (short *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(short)); |
368 | |
369 | int Pos = 0; |
370 | while(Pos < NumSamples) |
371 | { |
372 | const int Read = op_read(of: pOpusFile, pcm: pSampleData + Pos * NumChannels, buf_size: (NumSamples - Pos) * NumChannels, li: nullptr); |
373 | if(Read < 0) |
374 | { |
375 | free(ptr: pSampleData); |
376 | op_free(of: pOpusFile); |
377 | dbg_msg(sys: "sound/opus" , fmt: "op_read error %d at %d" , Read, Pos); |
378 | return false; |
379 | } |
380 | else if(Read == 0) // EOF |
381 | break; |
382 | Pos += Read; |
383 | } |
384 | |
385 | op_free(of: pOpusFile); |
386 | |
387 | Sample.m_pData = pSampleData; |
388 | Sample.m_NumFrames = Pos; |
389 | Sample.m_Rate = 48000; |
390 | Sample.m_Channels = NumChannels; |
391 | Sample.m_LoopStart = -1; |
392 | Sample.m_LoopEnd = -1; |
393 | Sample.m_PausedAt = 0; |
394 | } |
395 | else |
396 | { |
397 | dbg_msg(sys: "sound/opus" , fmt: "failed to decode sample, error %d" , OpusError); |
398 | return false; |
399 | } |
400 | |
401 | return true; |
402 | } |
403 | |
404 | // TODO: Update WavPack to get rid of these global variables |
405 | static const void *s_pWVBuffer = nullptr; |
406 | static int s_WVBufferPosition = 0; |
407 | static int s_WVBufferSize = 0; |
408 | |
409 | static int ReadDataOld(void *pBuffer, int Size) |
410 | { |
411 | int ChunkSize = minimum(a: Size, b: s_WVBufferSize - s_WVBufferPosition); |
412 | mem_copy(dest: pBuffer, source: (const char *)s_pWVBuffer + s_WVBufferPosition, size: ChunkSize); |
413 | s_WVBufferPosition += ChunkSize; |
414 | return ChunkSize; |
415 | } |
416 | |
417 | #if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX) |
418 | static int ReadData(void *pId, void *pBuffer, int Size) |
419 | { |
420 | (void)pId; |
421 | return ReadDataOld(pBuffer, Size); |
422 | } |
423 | |
424 | static int ReturnFalse(void *pId) |
425 | { |
426 | (void)pId; |
427 | return 0; |
428 | } |
429 | |
430 | static unsigned int GetPos(void *pId) |
431 | { |
432 | (void)pId; |
433 | return s_WVBufferPosition; |
434 | } |
435 | |
436 | static unsigned int GetLength(void *pId) |
437 | { |
438 | (void)pId; |
439 | return s_WVBufferSize; |
440 | } |
441 | |
442 | static int PushBackByte(void *pId, int Char) |
443 | { |
444 | s_WVBufferPosition -= 1; |
445 | return 0; |
446 | } |
447 | #endif |
448 | |
449 | bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const |
450 | { |
451 | char aError[100]; |
452 | |
453 | dbg_assert(s_pWVBuffer == nullptr, "DecodeWV already in use" ); |
454 | s_pWVBuffer = pData; |
455 | s_WVBufferSize = DataSize; |
456 | s_WVBufferPosition = 0; |
457 | |
458 | #if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX) |
459 | WavpackStreamReader Callback = {.read_bytes: 0}; |
460 | Callback.can_seek = ReturnFalse; |
461 | Callback.get_length = GetLength; |
462 | Callback.get_pos = GetPos; |
463 | Callback.push_back_byte = PushBackByte; |
464 | Callback.read_bytes = ReadData; |
465 | WavpackContext *pContext = WavpackOpenFileInputEx(reader: &Callback, wv_id: (void *)1, wvc_id: 0, error: aError, flags: 0, norm_offset: 0); |
466 | #else |
467 | WavpackContext *pContext = WavpackOpenFileInput(ReadDataOld, aError); |
468 | #endif |
469 | if(pContext) |
470 | { |
471 | const int NumSamples = WavpackGetNumSamples(wpc: pContext); |
472 | const int BitsPerSample = WavpackGetBitsPerSample(wpc: pContext); |
473 | const unsigned int SampleRate = WavpackGetSampleRate(wpc: pContext); |
474 | const int NumChannels = WavpackGetNumChannels(wpc: pContext); |
475 | |
476 | if(NumChannels > 2) |
477 | { |
478 | dbg_msg(sys: "sound/wv" , fmt: "file is not mono or stereo." ); |
479 | s_pWVBuffer = nullptr; |
480 | return false; |
481 | } |
482 | |
483 | if(BitsPerSample != 16) |
484 | { |
485 | dbg_msg(sys: "sound/wv" , fmt: "bps is %d, not 16" , BitsPerSample); |
486 | s_pWVBuffer = nullptr; |
487 | return false; |
488 | } |
489 | |
490 | int *pBuffer = (int *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(int)); |
491 | if(!WavpackUnpackSamples(wpc: pContext, buffer: pBuffer, samples: NumSamples)) |
492 | { |
493 | free(ptr: pBuffer); |
494 | dbg_msg(sys: "sound/wv" , fmt: "WavpackUnpackSamples failed. NumSamples=%d, NumChannels=%d" , NumSamples, NumChannels); |
495 | s_pWVBuffer = nullptr; |
496 | return false; |
497 | } |
498 | |
499 | Sample.m_pData = (short *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(short)); |
500 | |
501 | int *pSrc = pBuffer; |
502 | short *pDst = Sample.m_pData; |
503 | for(int i = 0; i < NumSamples * NumChannels; i++) |
504 | *pDst++ = (short)*pSrc++; |
505 | |
506 | free(ptr: pBuffer); |
507 | #ifdef CONF_WAVPACK_CLOSE_FILE |
508 | WavpackCloseFile(wpc: pContext); |
509 | #endif |
510 | |
511 | Sample.m_NumFrames = NumSamples; |
512 | Sample.m_Rate = SampleRate; |
513 | Sample.m_Channels = NumChannels; |
514 | Sample.m_LoopStart = -1; |
515 | Sample.m_LoopEnd = -1; |
516 | Sample.m_PausedAt = 0; |
517 | |
518 | s_pWVBuffer = nullptr; |
519 | } |
520 | else |
521 | { |
522 | dbg_msg(sys: "sound/wv" , fmt: "failed to decode sample (%s)" , aError); |
523 | s_pWVBuffer = nullptr; |
524 | return false; |
525 | } |
526 | |
527 | return true; |
528 | } |
529 | |
530 | int CSound::LoadOpus(const char *pFilename, int StorageType) |
531 | { |
532 | // no need to load sound when we are running with no sound |
533 | if(!m_SoundEnabled) |
534 | return -1; |
535 | |
536 | if(!m_pStorage) |
537 | return -1; |
538 | |
539 | CSample *pSample = AllocSample(); |
540 | if(!pSample) |
541 | { |
542 | dbg_msg(sys: "sound/opus" , fmt: "failed to allocate sample ID. filename='%s'" , pFilename); |
543 | return -1; |
544 | } |
545 | |
546 | void *pData; |
547 | unsigned DataSize; |
548 | if(!m_pStorage->ReadFile(pFilename, Type: StorageType, ppResult: &pData, pResultLen: &DataSize)) |
549 | { |
550 | UnloadSample(SampleId: pSample->m_Index); |
551 | dbg_msg(sys: "sound/opus" , fmt: "failed to open file. filename='%s'" , pFilename); |
552 | return -1; |
553 | } |
554 | |
555 | const bool DecodeSuccess = DecodeOpus(Sample&: *pSample, pData, DataSize); |
556 | free(ptr: pData); |
557 | if(!DecodeSuccess) |
558 | { |
559 | UnloadSample(SampleId: pSample->m_Index); |
560 | return -1; |
561 | } |
562 | |
563 | if(g_Config.m_Debug) |
564 | dbg_msg(sys: "sound/opus" , fmt: "loaded %s" , pFilename); |
565 | |
566 | RateConvert(Sample&: *pSample); |
567 | return pSample->m_Index; |
568 | } |
569 | |
570 | int CSound::LoadWV(const char *pFilename, int StorageType) |
571 | { |
572 | // no need to load sound when we are running with no sound |
573 | if(!m_SoundEnabled) |
574 | return -1; |
575 | |
576 | if(!m_pStorage) |
577 | return -1; |
578 | |
579 | CSample *pSample = AllocSample(); |
580 | if(!pSample) |
581 | { |
582 | dbg_msg(sys: "sound/wv" , fmt: "failed to allocate sample ID. filename='%s'" , pFilename); |
583 | return -1; |
584 | } |
585 | |
586 | void *pData; |
587 | unsigned DataSize; |
588 | if(!m_pStorage->ReadFile(pFilename, Type: StorageType, ppResult: &pData, pResultLen: &DataSize)) |
589 | { |
590 | UnloadSample(SampleId: pSample->m_Index); |
591 | dbg_msg(sys: "sound/wv" , fmt: "failed to open file. filename='%s'" , pFilename); |
592 | return -1; |
593 | } |
594 | |
595 | const bool DecodeSuccess = DecodeWV(Sample&: *pSample, pData, DataSize); |
596 | free(ptr: pData); |
597 | if(!DecodeSuccess) |
598 | { |
599 | UnloadSample(SampleId: pSample->m_Index); |
600 | return -1; |
601 | } |
602 | |
603 | if(g_Config.m_Debug) |
604 | dbg_msg(sys: "sound/wv" , fmt: "loaded %s" , pFilename); |
605 | |
606 | RateConvert(Sample&: *pSample); |
607 | return pSample->m_Index; |
608 | } |
609 | |
610 | int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) |
611 | { |
612 | // no need to load sound when we are running with no sound |
613 | if(!m_SoundEnabled && !FromEditor) |
614 | return -1; |
615 | |
616 | if(!pData) |
617 | return -1; |
618 | |
619 | CSample *pSample = AllocSample(); |
620 | if(!pSample) |
621 | return -1; |
622 | |
623 | if(!DecodeOpus(Sample&: *pSample, pData, DataSize)) |
624 | { |
625 | UnloadSample(SampleId: pSample->m_Index); |
626 | return -1; |
627 | } |
628 | |
629 | RateConvert(Sample&: *pSample); |
630 | return pSample->m_Index; |
631 | } |
632 | |
633 | int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) |
634 | { |
635 | // no need to load sound when we are running with no sound |
636 | if(!m_SoundEnabled && !FromEditor) |
637 | return -1; |
638 | |
639 | if(!pData) |
640 | return -1; |
641 | |
642 | CSample *pSample = AllocSample(); |
643 | if(!pSample) |
644 | return -1; |
645 | |
646 | if(!DecodeWV(Sample&: *pSample, pData, DataSize)) |
647 | { |
648 | UnloadSample(SampleId: pSample->m_Index); |
649 | return -1; |
650 | } |
651 | |
652 | RateConvert(Sample&: *pSample); |
653 | return pSample->m_Index; |
654 | } |
655 | |
656 | void CSound::UnloadSample(int SampleId) |
657 | { |
658 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
659 | return; |
660 | |
661 | Stop(SampleId); |
662 | |
663 | // Free data |
664 | CSample &Sample = m_aSamples[SampleId]; |
665 | free(ptr: Sample.m_pData); |
666 | Sample.m_pData = nullptr; |
667 | |
668 | // Free slot |
669 | if(Sample.m_NextFreeSampleIndex == SAMPLE_INDEX_USED) |
670 | { |
671 | Sample.m_NextFreeSampleIndex = m_FirstFreeSampleIndex; |
672 | m_FirstFreeSampleIndex = Sample.m_Index; |
673 | } |
674 | } |
675 | |
676 | float CSound::GetSampleTotalTime(int SampleId) |
677 | { |
678 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
679 | return 0.0f; |
680 | |
681 | return m_aSamples[SampleId].TotalTime(); |
682 | } |
683 | |
684 | float CSound::GetSampleCurrentTime(int SampleId) |
685 | { |
686 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
687 | return 0.0f; |
688 | |
689 | const CLockScope LockScope(m_SoundLock); |
690 | CSample *pSample = &m_aSamples[SampleId]; |
691 | for(auto &Voice : m_aVoices) |
692 | { |
693 | if(Voice.m_pSample == pSample) |
694 | { |
695 | return Voice.m_Tick / (float)pSample->m_Rate; |
696 | } |
697 | } |
698 | |
699 | return pSample->m_PausedAt / (float)pSample->m_Rate; |
700 | } |
701 | |
702 | void CSound::SetSampleCurrentTime(int SampleId, float Time) |
703 | { |
704 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
705 | return; |
706 | |
707 | const CLockScope LockScope(m_SoundLock); |
708 | CSample *pSample = &m_aSamples[SampleId]; |
709 | for(auto &Voice : m_aVoices) |
710 | { |
711 | if(Voice.m_pSample == pSample) |
712 | { |
713 | Voice.m_Tick = pSample->m_NumFrames * Time; |
714 | return; |
715 | } |
716 | } |
717 | |
718 | pSample->m_PausedAt = pSample->m_NumFrames * Time; |
719 | } |
720 | |
721 | void CSound::SetChannel(int ChannelId, float Vol, float Pan) |
722 | { |
723 | m_aChannels[ChannelId].m_Vol = (int)(Vol * 255.0f); |
724 | m_aChannels[ChannelId].m_Pan = (int)(Pan * 255.0f); // TODO: this is only on and off right now |
725 | } |
726 | |
727 | void CSound::SetListenerPos(float x, float y) |
728 | { |
729 | m_CenterX.store(i: (int)x, m: std::memory_order_relaxed); |
730 | m_CenterY.store(i: (int)y, m: std::memory_order_relaxed); |
731 | } |
732 | |
733 | void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume) |
734 | { |
735 | if(!Voice.IsValid()) |
736 | return; |
737 | |
738 | int VoiceId = Voice.Id(); |
739 | |
740 | const CLockScope LockScope(m_SoundLock); |
741 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
742 | return; |
743 | |
744 | Volume = clamp(val: Volume, lo: 0.0f, hi: 1.0f); |
745 | m_aVoices[VoiceId].m_Vol = (int)(Volume * 255.0f); |
746 | } |
747 | |
748 | void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff) |
749 | { |
750 | if(!Voice.IsValid()) |
751 | return; |
752 | |
753 | int VoiceId = Voice.Id(); |
754 | |
755 | const CLockScope LockScope(m_SoundLock); |
756 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
757 | return; |
758 | |
759 | Falloff = clamp(val: Falloff, lo: 0.0f, hi: 1.0f); |
760 | m_aVoices[VoiceId].m_Falloff = Falloff; |
761 | } |
762 | |
763 | void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y) |
764 | { |
765 | if(!Voice.IsValid()) |
766 | return; |
767 | |
768 | int VoiceId = Voice.Id(); |
769 | |
770 | const CLockScope LockScope(m_SoundLock); |
771 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
772 | return; |
773 | |
774 | m_aVoices[VoiceId].m_X = x; |
775 | m_aVoices[VoiceId].m_Y = y; |
776 | } |
777 | |
778 | void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) |
779 | { |
780 | if(!Voice.IsValid()) |
781 | return; |
782 | |
783 | int VoiceId = Voice.Id(); |
784 | |
785 | const CLockScope LockScope(m_SoundLock); |
786 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
787 | return; |
788 | |
789 | if(!m_aVoices[VoiceId].m_pSample) |
790 | return; |
791 | |
792 | int Tick = 0; |
793 | bool IsLooping = m_aVoices[VoiceId].m_Flags & ISound::FLAG_LOOP; |
794 | uint64_t TickOffset = m_aVoices[VoiceId].m_pSample->m_Rate * TimeOffset; |
795 | if(m_aVoices[VoiceId].m_pSample->m_NumFrames > 0 && IsLooping) |
796 | Tick = TickOffset % m_aVoices[VoiceId].m_pSample->m_NumFrames; |
797 | else |
798 | Tick = clamp(val: TickOffset, lo: (uint64_t)0, hi: (uint64_t)m_aVoices[VoiceId].m_pSample->m_NumFrames); |
799 | |
800 | // at least 200msec off, else depend on buffer size |
801 | float Threshold = maximum(a: 0.2f * m_aVoices[VoiceId].m_pSample->m_Rate, b: (float)m_MaxFrames); |
802 | if(absolute(a: m_aVoices[VoiceId].m_Tick - Tick) > Threshold) |
803 | { |
804 | // take care of looping (modulo!) |
805 | if(!(IsLooping && (minimum(a: m_aVoices[VoiceId].m_Tick, b: Tick) + m_aVoices[VoiceId].m_pSample->m_NumFrames - maximum(a: m_aVoices[VoiceId].m_Tick, b: Tick)) <= Threshold)) |
806 | { |
807 | m_aVoices[VoiceId].m_Tick = Tick; |
808 | } |
809 | } |
810 | } |
811 | |
812 | void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius) |
813 | { |
814 | if(!Voice.IsValid()) |
815 | return; |
816 | |
817 | int VoiceId = Voice.Id(); |
818 | |
819 | const CLockScope LockScope(m_SoundLock); |
820 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
821 | return; |
822 | |
823 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_CIRCLE; |
824 | m_aVoices[VoiceId].m_Circle.m_Radius = maximum(a: 0.0f, b: Radius); |
825 | } |
826 | |
827 | void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) |
828 | { |
829 | if(!Voice.IsValid()) |
830 | return; |
831 | |
832 | int VoiceId = Voice.Id(); |
833 | |
834 | const CLockScope LockScope(m_SoundLock); |
835 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
836 | return; |
837 | |
838 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_RECTANGLE; |
839 | m_aVoices[VoiceId].m_Rectangle.m_Width = maximum(a: 0.0f, b: Width); |
840 | m_aVoices[VoiceId].m_Rectangle.m_Height = maximum(a: 0.0f, b: Height); |
841 | } |
842 | |
843 | ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags, float x, float y) |
844 | { |
845 | const CLockScope LockScope(m_SoundLock); |
846 | |
847 | // search for voice |
848 | int VoiceId = -1; |
849 | for(int i = 0; i < NUM_VOICES; i++) |
850 | { |
851 | int NextId = (m_NextVoice + i) % NUM_VOICES; |
852 | if(!m_aVoices[NextId].m_pSample) |
853 | { |
854 | VoiceId = NextId; |
855 | m_NextVoice = NextId + 1; |
856 | break; |
857 | } |
858 | } |
859 | |
860 | // voice found, use it |
861 | int Age = -1; |
862 | if(VoiceId != -1) |
863 | { |
864 | m_aVoices[VoiceId].m_pSample = &m_aSamples[SampleId]; |
865 | m_aVoices[VoiceId].m_pChannel = &m_aChannels[ChannelId]; |
866 | if(Flags & FLAG_LOOP) |
867 | { |
868 | m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt; |
869 | } |
870 | else if(Flags & FLAG_PREVIEW) |
871 | { |
872 | m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt; |
873 | m_aSamples[SampleId].m_PausedAt = 0; |
874 | } |
875 | else |
876 | { |
877 | m_aVoices[VoiceId].m_Tick = 0; |
878 | } |
879 | m_aVoices[VoiceId].m_Vol = 255; |
880 | m_aVoices[VoiceId].m_Flags = Flags; |
881 | m_aVoices[VoiceId].m_X = (int)x; |
882 | m_aVoices[VoiceId].m_Y = (int)y; |
883 | m_aVoices[VoiceId].m_Falloff = 0.0f; |
884 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_CIRCLE; |
885 | m_aVoices[VoiceId].m_Circle.m_Radius = 1500; |
886 | Age = m_aVoices[VoiceId].m_Age; |
887 | } |
888 | |
889 | return CreateVoiceHandle(Index: VoiceId, Age); |
890 | } |
891 | |
892 | ISound::CVoiceHandle CSound::PlayAt(int ChannelId, int SampleId, int Flags, float x, float y) |
893 | { |
894 | return Play(ChannelId, SampleId, Flags: Flags | ISound::FLAG_POS, x, y); |
895 | } |
896 | |
897 | ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags) |
898 | { |
899 | return Play(ChannelId, SampleId, Flags, x: 0, y: 0); |
900 | } |
901 | |
902 | void CSound::Pause(int SampleId) |
903 | { |
904 | // TODO: a nice fade out |
905 | const CLockScope LockScope(m_SoundLock); |
906 | CSample *pSample = &m_aSamples[SampleId]; |
907 | for(auto &Voice : m_aVoices) |
908 | { |
909 | if(Voice.m_pSample == pSample) |
910 | { |
911 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
912 | Voice.m_pSample = nullptr; |
913 | } |
914 | } |
915 | } |
916 | |
917 | void CSound::Stop(int SampleId) |
918 | { |
919 | // TODO: a nice fade out |
920 | const CLockScope LockScope(m_SoundLock); |
921 | CSample *pSample = &m_aSamples[SampleId]; |
922 | for(auto &Voice : m_aVoices) |
923 | { |
924 | if(Voice.m_pSample == pSample) |
925 | { |
926 | if(Voice.m_Flags & FLAG_LOOP) |
927 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
928 | else |
929 | Voice.m_pSample->m_PausedAt = 0; |
930 | Voice.m_pSample = nullptr; |
931 | } |
932 | } |
933 | } |
934 | |
935 | void CSound::StopAll() |
936 | { |
937 | // TODO: a nice fade out |
938 | const CLockScope LockScope(m_SoundLock); |
939 | for(auto &Voice : m_aVoices) |
940 | { |
941 | if(Voice.m_pSample) |
942 | { |
943 | if(Voice.m_Flags & FLAG_LOOP) |
944 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
945 | else |
946 | Voice.m_pSample->m_PausedAt = 0; |
947 | } |
948 | Voice.m_pSample = nullptr; |
949 | } |
950 | } |
951 | |
952 | void CSound::StopVoice(CVoiceHandle Voice) |
953 | { |
954 | if(!Voice.IsValid()) |
955 | return; |
956 | |
957 | int VoiceId = Voice.Id(); |
958 | |
959 | const CLockScope LockScope(m_SoundLock); |
960 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
961 | return; |
962 | |
963 | m_aVoices[VoiceId].m_pSample = nullptr; |
964 | m_aVoices[VoiceId].m_Age++; |
965 | } |
966 | |
967 | bool CSound::IsPlaying(int SampleId) |
968 | { |
969 | const CLockScope LockScope(m_SoundLock); |
970 | const CSample *pSample = &m_aSamples[SampleId]; |
971 | return std::any_of(first: std::begin(arr&: m_aVoices), last: std::end(arr&: m_aVoices), pred: [pSample](const auto &Voice) { return Voice.m_pSample == pSample; }); |
972 | } |
973 | |
974 | void CSound::PauseAudioDevice() |
975 | { |
976 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 1); |
977 | } |
978 | |
979 | void CSound::UnpauseAudioDevice() |
980 | { |
981 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 0); |
982 | } |
983 | |
984 | IEngineSound *CreateEngineSound() { return new CSound; } |
985 | |