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 | if(!g_Config.m_SndEnable) |
211 | return 0; |
212 | |
213 | if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) |
214 | { |
215 | dbg_msg(sys: "sound" , fmt: "unable to init SDL audio: %s" , SDL_GetError()); |
216 | return -1; |
217 | } |
218 | |
219 | m_MixingRate = g_Config.m_SndRate; |
220 | |
221 | SDL_AudioSpec Format, FormatOut; |
222 | Format.freq = m_MixingRate; |
223 | Format.format = AUDIO_S16; |
224 | Format.channels = 2; |
225 | Format.samples = g_Config.m_SndBufferSize; |
226 | Format.callback = SdlCallback; |
227 | Format.userdata = this; |
228 | |
229 | // Open the audio device and start playing sound! |
230 | m_Device = SDL_OpenAudioDevice(device: nullptr, iscapture: 0, desired: &Format, obtained: &FormatOut, allowed_changes: 0); |
231 | |
232 | if(m_Device == 0) |
233 | { |
234 | dbg_msg(sys: "sound" , fmt: "unable to open audio: %s" , SDL_GetError()); |
235 | return -1; |
236 | } |
237 | else |
238 | dbg_msg(sys: "sound" , fmt: "sound init successful using audio driver '%s'" , SDL_GetCurrentAudioDriver()); |
239 | |
240 | m_MaxFrames = FormatOut.samples * 2; |
241 | #if defined(CONF_VIDEORECORDER) |
242 | m_MaxFrames = maximum<uint32_t>(a: m_MaxFrames, b: 1024 * 2); // make the buffer bigger just in case |
243 | #endif |
244 | m_pMixBuffer = (int *)calloc(nmemb: m_MaxFrames * 2, size: sizeof(int)); |
245 | |
246 | m_FirstFreeSampleIndex = 0; |
247 | for(size_t i = 0; i < std::size(m_aSamples) - 1; ++i) |
248 | { |
249 | m_aSamples[i].m_Index = i; |
250 | m_aSamples[i].m_NextFreeSampleIndex = i + 1; |
251 | } |
252 | m_aSamples[std::size(m_aSamples) - 1].m_Index = std::size(m_aSamples) - 1; |
253 | m_aSamples[std::size(m_aSamples) - 1].m_NextFreeSampleIndex = SAMPLE_INDEX_FULL; |
254 | |
255 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 0); |
256 | |
257 | m_SoundEnabled = true; |
258 | Update(); |
259 | return 0; |
260 | } |
261 | |
262 | int CSound::Update() |
263 | { |
264 | UpdateVolume(); |
265 | return 0; |
266 | } |
267 | |
268 | void CSound::UpdateVolume() |
269 | { |
270 | int WantedVolume = g_Config.m_SndVolume; |
271 | if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute) |
272 | WantedVolume = 0; |
273 | m_SoundVolume.store(i: WantedVolume, m: std::memory_order_relaxed); |
274 | } |
275 | |
276 | void CSound::Shutdown() |
277 | { |
278 | for(unsigned SampleId = 0; SampleId < NUM_SAMPLES; SampleId++) |
279 | { |
280 | UnloadSample(SampleId); |
281 | } |
282 | |
283 | SDL_CloseAudioDevice(dev: m_Device); |
284 | SDL_QuitSubSystem(SDL_INIT_AUDIO); |
285 | free(ptr: m_pMixBuffer); |
286 | m_pMixBuffer = nullptr; |
287 | } |
288 | |
289 | CSample *CSound::AllocSample() |
290 | { |
291 | if(m_FirstFreeSampleIndex == SAMPLE_INDEX_FULL) |
292 | return nullptr; |
293 | |
294 | CSample *pSample = &m_aSamples[m_FirstFreeSampleIndex]; |
295 | if(pSample->m_pData != nullptr || pSample->m_NextFreeSampleIndex == SAMPLE_INDEX_USED) |
296 | { |
297 | char aError[128]; |
298 | str_format(buffer: aError, buffer_size: sizeof(aError), format: "Sample was not unloaded (index=%d, next=%d, duration=%f, data=%p)" , |
299 | pSample->m_Index, pSample->m_NextFreeSampleIndex, pSample->TotalTime(), pSample->m_pData); |
300 | dbg_assert(false, aError); |
301 | } |
302 | m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex; |
303 | pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED; |
304 | return pSample; |
305 | } |
306 | |
307 | void CSound::RateConvert(CSample &Sample) const |
308 | { |
309 | dbg_assert(Sample.m_pData != nullptr, "Sample is not loaded" ); |
310 | // make sure that we need to convert this sound |
311 | if(Sample.m_Rate == m_MixingRate) |
312 | return; |
313 | |
314 | // allocate new data |
315 | const int NumFrames = (int)((Sample.m_NumFrames / (float)Sample.m_Rate) * m_MixingRate); |
316 | short *pNewData = (short *)calloc(nmemb: (size_t)NumFrames * Sample.m_Channels, size: sizeof(short)); |
317 | |
318 | for(int i = 0; i < NumFrames; i++) |
319 | { |
320 | // resample TODO: this should be done better, like linear at least |
321 | float a = i / (float)NumFrames; |
322 | int f = (int)(a * Sample.m_NumFrames); |
323 | if(f >= Sample.m_NumFrames) |
324 | f = Sample.m_NumFrames - 1; |
325 | |
326 | // set new data |
327 | if(Sample.m_Channels == 1) |
328 | pNewData[i] = Sample.m_pData[f]; |
329 | else if(Sample.m_Channels == 2) |
330 | { |
331 | pNewData[i * 2] = Sample.m_pData[f * 2]; |
332 | pNewData[i * 2 + 1] = Sample.m_pData[f * 2 + 1]; |
333 | } |
334 | } |
335 | |
336 | // free old data and apply new |
337 | free(ptr: Sample.m_pData); |
338 | Sample.m_pData = pNewData; |
339 | Sample.m_NumFrames = NumFrames; |
340 | Sample.m_Rate = m_MixingRate; |
341 | } |
342 | |
343 | bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const |
344 | { |
345 | int OpusError = 0; |
346 | OggOpusFile *pOpusFile = op_open_memory(data: (const unsigned char *)pData, size: DataSize, error: &OpusError); |
347 | if(pOpusFile) |
348 | { |
349 | const int NumChannels = op_channel_count(of: pOpusFile, li: -1); |
350 | if(NumChannels > 2) |
351 | { |
352 | op_free(of: pOpusFile); |
353 | dbg_msg(sys: "sound/opus" , fmt: "file is not mono or stereo." ); |
354 | return false; |
355 | } |
356 | |
357 | const int NumSamples = op_pcm_total(of: pOpusFile, li: -1); // per channel! |
358 | if(NumSamples < 0) |
359 | { |
360 | op_free(of: pOpusFile); |
361 | dbg_msg(sys: "sound/opus" , fmt: "failed to get number of samples, error %d" , NumSamples); |
362 | return false; |
363 | } |
364 | |
365 | short *pSampleData = (short *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(short)); |
366 | |
367 | int Pos = 0; |
368 | while(Pos < NumSamples) |
369 | { |
370 | const int Read = op_read(of: pOpusFile, pcm: pSampleData + Pos * NumChannels, buf_size: (NumSamples - Pos) * NumChannels, li: nullptr); |
371 | if(Read < 0) |
372 | { |
373 | free(ptr: pSampleData); |
374 | op_free(of: pOpusFile); |
375 | dbg_msg(sys: "sound/opus" , fmt: "op_read error %d at %d" , Read, Pos); |
376 | return false; |
377 | } |
378 | else if(Read == 0) // EOF |
379 | break; |
380 | Pos += Read; |
381 | } |
382 | |
383 | op_free(of: pOpusFile); |
384 | |
385 | Sample.m_pData = pSampleData; |
386 | Sample.m_NumFrames = Pos; |
387 | Sample.m_Rate = 48000; |
388 | Sample.m_Channels = NumChannels; |
389 | Sample.m_LoopStart = -1; |
390 | Sample.m_LoopEnd = -1; |
391 | Sample.m_PausedAt = 0; |
392 | } |
393 | else |
394 | { |
395 | dbg_msg(sys: "sound/opus" , fmt: "failed to decode sample, error %d" , OpusError); |
396 | return false; |
397 | } |
398 | |
399 | return true; |
400 | } |
401 | |
402 | // TODO: Update WavPack to get rid of these global variables |
403 | static const void *s_pWVBuffer = nullptr; |
404 | static int s_WVBufferPosition = 0; |
405 | static int s_WVBufferSize = 0; |
406 | |
407 | static int ReadDataOld(void *pBuffer, int Size) |
408 | { |
409 | int ChunkSize = minimum(a: Size, b: s_WVBufferSize - s_WVBufferPosition); |
410 | mem_copy(dest: pBuffer, source: (const char *)s_pWVBuffer + s_WVBufferPosition, size: ChunkSize); |
411 | s_WVBufferPosition += ChunkSize; |
412 | return ChunkSize; |
413 | } |
414 | |
415 | #if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX) |
416 | static int ReadData(void *pId, void *pBuffer, int Size) |
417 | { |
418 | (void)pId; |
419 | return ReadDataOld(pBuffer, Size); |
420 | } |
421 | |
422 | static int ReturnFalse(void *pId) |
423 | { |
424 | (void)pId; |
425 | return 0; |
426 | } |
427 | |
428 | static unsigned int GetPos(void *pId) |
429 | { |
430 | (void)pId; |
431 | return s_WVBufferPosition; |
432 | } |
433 | |
434 | static unsigned int GetLength(void *pId) |
435 | { |
436 | (void)pId; |
437 | return s_WVBufferSize; |
438 | } |
439 | |
440 | static int PushBackByte(void *pId, int Char) |
441 | { |
442 | s_WVBufferPosition -= 1; |
443 | return 0; |
444 | } |
445 | #endif |
446 | |
447 | bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const |
448 | { |
449 | char aError[100]; |
450 | |
451 | dbg_assert(s_pWVBuffer == nullptr, "DecodeWV already in use" ); |
452 | s_pWVBuffer = pData; |
453 | s_WVBufferSize = DataSize; |
454 | s_WVBufferPosition = 0; |
455 | |
456 | #if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX) |
457 | WavpackStreamReader Callback = {.read_bytes: 0}; |
458 | Callback.can_seek = ReturnFalse; |
459 | Callback.get_length = GetLength; |
460 | Callback.get_pos = GetPos; |
461 | Callback.push_back_byte = PushBackByte; |
462 | Callback.read_bytes = ReadData; |
463 | WavpackContext *pContext = WavpackOpenFileInputEx(reader: &Callback, wv_id: (void *)1, wvc_id: 0, error: aError, flags: 0, norm_offset: 0); |
464 | #else |
465 | WavpackContext *pContext = WavpackOpenFileInput(ReadDataOld, aError); |
466 | #endif |
467 | if(pContext) |
468 | { |
469 | const int NumSamples = WavpackGetNumSamples(wpc: pContext); |
470 | const int BitsPerSample = WavpackGetBitsPerSample(wpc: pContext); |
471 | const unsigned int SampleRate = WavpackGetSampleRate(wpc: pContext); |
472 | const int NumChannels = WavpackGetNumChannels(wpc: pContext); |
473 | |
474 | if(NumChannels > 2) |
475 | { |
476 | dbg_msg(sys: "sound/wv" , fmt: "file is not mono or stereo." ); |
477 | s_pWVBuffer = nullptr; |
478 | return false; |
479 | } |
480 | |
481 | if(BitsPerSample != 16) |
482 | { |
483 | dbg_msg(sys: "sound/wv" , fmt: "bps is %d, not 16" , BitsPerSample); |
484 | s_pWVBuffer = nullptr; |
485 | return false; |
486 | } |
487 | |
488 | int *pBuffer = (int *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(int)); |
489 | if(!WavpackUnpackSamples(wpc: pContext, buffer: pBuffer, samples: NumSamples)) |
490 | { |
491 | free(ptr: pBuffer); |
492 | dbg_msg(sys: "sound/wv" , fmt: "WavpackUnpackSamples failed. NumSamples=%d, NumChannels=%d" , NumSamples, NumChannels); |
493 | s_pWVBuffer = nullptr; |
494 | return false; |
495 | } |
496 | |
497 | Sample.m_pData = (short *)calloc(nmemb: (size_t)NumSamples * NumChannels, size: sizeof(short)); |
498 | |
499 | int *pSrc = pBuffer; |
500 | short *pDst = Sample.m_pData; |
501 | for(int i = 0; i < NumSamples * NumChannels; i++) |
502 | *pDst++ = (short)*pSrc++; |
503 | |
504 | free(ptr: pBuffer); |
505 | #ifdef CONF_WAVPACK_CLOSE_FILE |
506 | WavpackCloseFile(wpc: pContext); |
507 | #endif |
508 | |
509 | Sample.m_NumFrames = NumSamples; |
510 | Sample.m_Rate = SampleRate; |
511 | Sample.m_Channels = NumChannels; |
512 | Sample.m_LoopStart = -1; |
513 | Sample.m_LoopEnd = -1; |
514 | Sample.m_PausedAt = 0; |
515 | |
516 | s_pWVBuffer = nullptr; |
517 | } |
518 | else |
519 | { |
520 | dbg_msg(sys: "sound/wv" , fmt: "failed to decode sample (%s)" , aError); |
521 | s_pWVBuffer = nullptr; |
522 | return false; |
523 | } |
524 | |
525 | return true; |
526 | } |
527 | |
528 | int CSound::LoadOpus(const char *pFilename, int StorageType) |
529 | { |
530 | // no need to load sound when we are running with no sound |
531 | if(!m_SoundEnabled) |
532 | return -1; |
533 | |
534 | if(!m_pStorage) |
535 | return -1; |
536 | |
537 | CSample *pSample = AllocSample(); |
538 | if(!pSample) |
539 | { |
540 | dbg_msg(sys: "sound/opus" , fmt: "failed to allocate sample ID. filename='%s'" , pFilename); |
541 | return -1; |
542 | } |
543 | |
544 | void *pData; |
545 | unsigned DataSize; |
546 | if(!m_pStorage->ReadFile(pFilename, Type: StorageType, ppResult: &pData, pResultLen: &DataSize)) |
547 | { |
548 | UnloadSample(SampleId: pSample->m_Index); |
549 | dbg_msg(sys: "sound/opus" , fmt: "failed to open file. filename='%s'" , pFilename); |
550 | return -1; |
551 | } |
552 | |
553 | const bool DecodeSuccess = DecodeOpus(Sample&: *pSample, pData, DataSize); |
554 | free(ptr: pData); |
555 | if(!DecodeSuccess) |
556 | { |
557 | UnloadSample(SampleId: pSample->m_Index); |
558 | return -1; |
559 | } |
560 | |
561 | if(g_Config.m_Debug) |
562 | dbg_msg(sys: "sound/opus" , fmt: "loaded %s" , pFilename); |
563 | |
564 | RateConvert(Sample&: *pSample); |
565 | return pSample->m_Index; |
566 | } |
567 | |
568 | int CSound::LoadWV(const char *pFilename, int StorageType) |
569 | { |
570 | // no need to load sound when we are running with no sound |
571 | if(!m_SoundEnabled) |
572 | return -1; |
573 | |
574 | if(!m_pStorage) |
575 | return -1; |
576 | |
577 | CSample *pSample = AllocSample(); |
578 | if(!pSample) |
579 | { |
580 | dbg_msg(sys: "sound/wv" , fmt: "failed to allocate sample ID. filename='%s'" , pFilename); |
581 | return -1; |
582 | } |
583 | |
584 | void *pData; |
585 | unsigned DataSize; |
586 | if(!m_pStorage->ReadFile(pFilename, Type: StorageType, ppResult: &pData, pResultLen: &DataSize)) |
587 | { |
588 | UnloadSample(SampleId: pSample->m_Index); |
589 | dbg_msg(sys: "sound/wv" , fmt: "failed to open file. filename='%s'" , pFilename); |
590 | return -1; |
591 | } |
592 | |
593 | const bool DecodeSuccess = DecodeWV(Sample&: *pSample, pData, DataSize); |
594 | free(ptr: pData); |
595 | if(!DecodeSuccess) |
596 | { |
597 | UnloadSample(SampleId: pSample->m_Index); |
598 | return -1; |
599 | } |
600 | |
601 | if(g_Config.m_Debug) |
602 | dbg_msg(sys: "sound/wv" , fmt: "loaded %s" , pFilename); |
603 | |
604 | RateConvert(Sample&: *pSample); |
605 | return pSample->m_Index; |
606 | } |
607 | |
608 | int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) |
609 | { |
610 | // no need to load sound when we are running with no sound |
611 | if(!m_SoundEnabled && !FromEditor) |
612 | return -1; |
613 | |
614 | if(!pData) |
615 | return -1; |
616 | |
617 | CSample *pSample = AllocSample(); |
618 | if(!pSample) |
619 | return -1; |
620 | |
621 | if(!DecodeOpus(Sample&: *pSample, pData, DataSize)) |
622 | { |
623 | UnloadSample(SampleId: pSample->m_Index); |
624 | return -1; |
625 | } |
626 | |
627 | RateConvert(Sample&: *pSample); |
628 | return pSample->m_Index; |
629 | } |
630 | |
631 | int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) |
632 | { |
633 | // no need to load sound when we are running with no sound |
634 | if(!m_SoundEnabled && !FromEditor) |
635 | return -1; |
636 | |
637 | if(!pData) |
638 | return -1; |
639 | |
640 | CSample *pSample = AllocSample(); |
641 | if(!pSample) |
642 | return -1; |
643 | |
644 | if(!DecodeWV(Sample&: *pSample, pData, DataSize)) |
645 | { |
646 | UnloadSample(SampleId: pSample->m_Index); |
647 | return -1; |
648 | } |
649 | |
650 | RateConvert(Sample&: *pSample); |
651 | return pSample->m_Index; |
652 | } |
653 | |
654 | void CSound::UnloadSample(int SampleId) |
655 | { |
656 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
657 | return; |
658 | |
659 | Stop(SampleId); |
660 | |
661 | // Free data |
662 | CSample &Sample = m_aSamples[SampleId]; |
663 | free(ptr: Sample.m_pData); |
664 | Sample.m_pData = nullptr; |
665 | |
666 | // Free slot |
667 | if(Sample.m_NextFreeSampleIndex == SAMPLE_INDEX_USED) |
668 | { |
669 | Sample.m_NextFreeSampleIndex = m_FirstFreeSampleIndex; |
670 | m_FirstFreeSampleIndex = Sample.m_Index; |
671 | } |
672 | } |
673 | |
674 | float CSound::GetSampleTotalTime(int SampleId) |
675 | { |
676 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
677 | return 0.0f; |
678 | |
679 | return m_aSamples[SampleId].TotalTime(); |
680 | } |
681 | |
682 | float CSound::GetSampleCurrentTime(int SampleId) |
683 | { |
684 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
685 | return 0.0f; |
686 | |
687 | const CLockScope LockScope(m_SoundLock); |
688 | CSample *pSample = &m_aSamples[SampleId]; |
689 | for(auto &Voice : m_aVoices) |
690 | { |
691 | if(Voice.m_pSample == pSample) |
692 | { |
693 | return Voice.m_Tick / (float)pSample->m_Rate; |
694 | } |
695 | } |
696 | |
697 | return pSample->m_PausedAt / (float)pSample->m_Rate; |
698 | } |
699 | |
700 | void CSound::SetSampleCurrentTime(int SampleId, float Time) |
701 | { |
702 | if(SampleId == -1 || SampleId >= NUM_SAMPLES) |
703 | return; |
704 | |
705 | const CLockScope LockScope(m_SoundLock); |
706 | CSample *pSample = &m_aSamples[SampleId]; |
707 | for(auto &Voice : m_aVoices) |
708 | { |
709 | if(Voice.m_pSample == pSample) |
710 | { |
711 | Voice.m_Tick = pSample->m_NumFrames * Time; |
712 | return; |
713 | } |
714 | } |
715 | |
716 | pSample->m_PausedAt = pSample->m_NumFrames * Time; |
717 | } |
718 | |
719 | void CSound::SetChannel(int ChannelId, float Vol, float Pan) |
720 | { |
721 | m_aChannels[ChannelId].m_Vol = (int)(Vol * 255.0f); |
722 | m_aChannels[ChannelId].m_Pan = (int)(Pan * 255.0f); // TODO: this is only on and off right now |
723 | } |
724 | |
725 | void CSound::SetListenerPos(float x, float y) |
726 | { |
727 | m_CenterX.store(i: (int)x, m: std::memory_order_relaxed); |
728 | m_CenterY.store(i: (int)y, m: std::memory_order_relaxed); |
729 | } |
730 | |
731 | void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume) |
732 | { |
733 | if(!Voice.IsValid()) |
734 | return; |
735 | |
736 | int VoiceId = Voice.Id(); |
737 | |
738 | const CLockScope LockScope(m_SoundLock); |
739 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
740 | return; |
741 | |
742 | Volume = clamp(val: Volume, lo: 0.0f, hi: 1.0f); |
743 | m_aVoices[VoiceId].m_Vol = (int)(Volume * 255.0f); |
744 | } |
745 | |
746 | void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff) |
747 | { |
748 | if(!Voice.IsValid()) |
749 | return; |
750 | |
751 | int VoiceId = Voice.Id(); |
752 | |
753 | const CLockScope LockScope(m_SoundLock); |
754 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
755 | return; |
756 | |
757 | Falloff = clamp(val: Falloff, lo: 0.0f, hi: 1.0f); |
758 | m_aVoices[VoiceId].m_Falloff = Falloff; |
759 | } |
760 | |
761 | void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y) |
762 | { |
763 | if(!Voice.IsValid()) |
764 | return; |
765 | |
766 | int VoiceId = Voice.Id(); |
767 | |
768 | const CLockScope LockScope(m_SoundLock); |
769 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
770 | return; |
771 | |
772 | m_aVoices[VoiceId].m_X = x; |
773 | m_aVoices[VoiceId].m_Y = y; |
774 | } |
775 | |
776 | void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) |
777 | { |
778 | if(!Voice.IsValid()) |
779 | return; |
780 | |
781 | int VoiceId = Voice.Id(); |
782 | |
783 | const CLockScope LockScope(m_SoundLock); |
784 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
785 | return; |
786 | |
787 | if(!m_aVoices[VoiceId].m_pSample) |
788 | return; |
789 | |
790 | int Tick = 0; |
791 | bool IsLooping = m_aVoices[VoiceId].m_Flags & ISound::FLAG_LOOP; |
792 | uint64_t TickOffset = m_aVoices[VoiceId].m_pSample->m_Rate * TimeOffset; |
793 | if(m_aVoices[VoiceId].m_pSample->m_NumFrames > 0 && IsLooping) |
794 | Tick = TickOffset % m_aVoices[VoiceId].m_pSample->m_NumFrames; |
795 | else |
796 | Tick = clamp(val: TickOffset, lo: (uint64_t)0, hi: (uint64_t)m_aVoices[VoiceId].m_pSample->m_NumFrames); |
797 | |
798 | // at least 200msec off, else depend on buffer size |
799 | float Threshold = maximum(a: 0.2f * m_aVoices[VoiceId].m_pSample->m_Rate, b: (float)m_MaxFrames); |
800 | if(absolute(a: m_aVoices[VoiceId].m_Tick - Tick) > Threshold) |
801 | { |
802 | // take care of looping (modulo!) |
803 | 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)) |
804 | { |
805 | m_aVoices[VoiceId].m_Tick = Tick; |
806 | } |
807 | } |
808 | } |
809 | |
810 | void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius) |
811 | { |
812 | if(!Voice.IsValid()) |
813 | return; |
814 | |
815 | int VoiceId = Voice.Id(); |
816 | |
817 | const CLockScope LockScope(m_SoundLock); |
818 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
819 | return; |
820 | |
821 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_CIRCLE; |
822 | m_aVoices[VoiceId].m_Circle.m_Radius = maximum(a: 0.0f, b: Radius); |
823 | } |
824 | |
825 | void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) |
826 | { |
827 | if(!Voice.IsValid()) |
828 | return; |
829 | |
830 | int VoiceId = Voice.Id(); |
831 | |
832 | const CLockScope LockScope(m_SoundLock); |
833 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
834 | return; |
835 | |
836 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_RECTANGLE; |
837 | m_aVoices[VoiceId].m_Rectangle.m_Width = maximum(a: 0.0f, b: Width); |
838 | m_aVoices[VoiceId].m_Rectangle.m_Height = maximum(a: 0.0f, b: Height); |
839 | } |
840 | |
841 | ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags, float x, float y) |
842 | { |
843 | const CLockScope LockScope(m_SoundLock); |
844 | |
845 | // search for voice |
846 | int VoiceId = -1; |
847 | for(int i = 0; i < NUM_VOICES; i++) |
848 | { |
849 | int NextId = (m_NextVoice + i) % NUM_VOICES; |
850 | if(!m_aVoices[NextId].m_pSample) |
851 | { |
852 | VoiceId = NextId; |
853 | m_NextVoice = NextId + 1; |
854 | break; |
855 | } |
856 | } |
857 | |
858 | // voice found, use it |
859 | int Age = -1; |
860 | if(VoiceId != -1) |
861 | { |
862 | m_aVoices[VoiceId].m_pSample = &m_aSamples[SampleId]; |
863 | m_aVoices[VoiceId].m_pChannel = &m_aChannels[ChannelId]; |
864 | if(Flags & FLAG_LOOP) |
865 | { |
866 | m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt; |
867 | } |
868 | else if(Flags & FLAG_PREVIEW) |
869 | { |
870 | m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt; |
871 | m_aSamples[SampleId].m_PausedAt = 0; |
872 | } |
873 | else |
874 | { |
875 | m_aVoices[VoiceId].m_Tick = 0; |
876 | } |
877 | m_aVoices[VoiceId].m_Vol = 255; |
878 | m_aVoices[VoiceId].m_Flags = Flags; |
879 | m_aVoices[VoiceId].m_X = (int)x; |
880 | m_aVoices[VoiceId].m_Y = (int)y; |
881 | m_aVoices[VoiceId].m_Falloff = 0.0f; |
882 | m_aVoices[VoiceId].m_Shape = ISound::SHAPE_CIRCLE; |
883 | m_aVoices[VoiceId].m_Circle.m_Radius = 1500; |
884 | Age = m_aVoices[VoiceId].m_Age; |
885 | } |
886 | |
887 | return CreateVoiceHandle(Index: VoiceId, Age); |
888 | } |
889 | |
890 | ISound::CVoiceHandle CSound::PlayAt(int ChannelId, int SampleId, int Flags, float x, float y) |
891 | { |
892 | return Play(ChannelId, SampleId, Flags: Flags | ISound::FLAG_POS, x, y); |
893 | } |
894 | |
895 | ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags) |
896 | { |
897 | return Play(ChannelId, SampleId, Flags, x: 0, y: 0); |
898 | } |
899 | |
900 | void CSound::Pause(int SampleId) |
901 | { |
902 | // TODO: a nice fade out |
903 | const CLockScope LockScope(m_SoundLock); |
904 | CSample *pSample = &m_aSamples[SampleId]; |
905 | for(auto &Voice : m_aVoices) |
906 | { |
907 | if(Voice.m_pSample == pSample) |
908 | { |
909 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
910 | Voice.m_pSample = nullptr; |
911 | } |
912 | } |
913 | } |
914 | |
915 | void CSound::Stop(int SampleId) |
916 | { |
917 | // TODO: a nice fade out |
918 | const CLockScope LockScope(m_SoundLock); |
919 | CSample *pSample = &m_aSamples[SampleId]; |
920 | for(auto &Voice : m_aVoices) |
921 | { |
922 | if(Voice.m_pSample == pSample) |
923 | { |
924 | if(Voice.m_Flags & FLAG_LOOP) |
925 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
926 | else |
927 | Voice.m_pSample->m_PausedAt = 0; |
928 | Voice.m_pSample = nullptr; |
929 | } |
930 | } |
931 | } |
932 | |
933 | void CSound::StopAll() |
934 | { |
935 | // TODO: a nice fade out |
936 | const CLockScope LockScope(m_SoundLock); |
937 | for(auto &Voice : m_aVoices) |
938 | { |
939 | if(Voice.m_pSample) |
940 | { |
941 | if(Voice.m_Flags & FLAG_LOOP) |
942 | Voice.m_pSample->m_PausedAt = Voice.m_Tick; |
943 | else |
944 | Voice.m_pSample->m_PausedAt = 0; |
945 | } |
946 | Voice.m_pSample = nullptr; |
947 | } |
948 | } |
949 | |
950 | void CSound::StopVoice(CVoiceHandle Voice) |
951 | { |
952 | if(!Voice.IsValid()) |
953 | return; |
954 | |
955 | int VoiceId = Voice.Id(); |
956 | |
957 | const CLockScope LockScope(m_SoundLock); |
958 | if(m_aVoices[VoiceId].m_Age != Voice.Age()) |
959 | return; |
960 | |
961 | m_aVoices[VoiceId].m_pSample = nullptr; |
962 | m_aVoices[VoiceId].m_Age++; |
963 | } |
964 | |
965 | bool CSound::IsPlaying(int SampleId) |
966 | { |
967 | const CLockScope LockScope(m_SoundLock); |
968 | const CSample *pSample = &m_aSamples[SampleId]; |
969 | 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; }); |
970 | } |
971 | |
972 | void CSound::PauseAudioDevice() |
973 | { |
974 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 1); |
975 | } |
976 | |
977 | void CSound::UnpauseAudioDevice() |
978 | { |
979 | SDL_PauseAudioDevice(dev: m_Device, pause_on: 0); |
980 | } |
981 | |
982 | IEngineSound *CreateEngineSound() { return new CSound; } |
983 | |