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
17extern "C" {
18#include <opusfile.h>
19#include <wavpack.h>
20}
21
22#include <cmath>
23
24static constexpr int SAMPLE_INDEX_USED = -2;
25static constexpr int SAMPLE_INDEX_FULL = -1;
26
27void 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
186static 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
204int 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
264int CSound::Update()
265{
266 UpdateVolume();
267 return 0;
268}
269
270void 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
278void 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
291CSample *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
309void 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
345bool 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
405static const void *s_pWVBuffer = nullptr;
406static int s_WVBufferPosition = 0;
407static int s_WVBufferSize = 0;
408
409static 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)
418static int ReadData(void *pId, void *pBuffer, int Size)
419{
420 (void)pId;
421 return ReadDataOld(pBuffer, Size);
422}
423
424static int ReturnFalse(void *pId)
425{
426 (void)pId;
427 return 0;
428}
429
430static unsigned int GetPos(void *pId)
431{
432 (void)pId;
433 return s_WVBufferPosition;
434}
435
436static unsigned int GetLength(void *pId)
437{
438 (void)pId;
439 return s_WVBufferSize;
440}
441
442static int PushBackByte(void *pId, int Char)
443{
444 s_WVBufferPosition -= 1;
445 return 0;
446}
447#endif
448
449bool 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
530int 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
570int 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
610int 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
633int 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
656void 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
676float 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
684float 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
702void 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
721void 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
727void 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
733void 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
748void 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
763void 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
778void 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
812void 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
827void 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
843ISound::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
892ISound::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
897ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags)
898{
899 return Play(ChannelId, SampleId, Flags, x: 0, y: 0);
900}
901
902void 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
917void 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
935void 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
952void 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
967bool 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
974void CSound::PauseAudioDevice()
975{
976 SDL_PauseAudioDevice(dev: m_Device, pause_on: 1);
977}
978
979void CSound::UnpauseAudioDevice()
980{
981 SDL_PauseAudioDevice(dev: m_Device, pause_on: 0);
982}
983
984IEngineSound *CreateEngineSound() { return new CSound; }
985