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 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
262int CSound::Update()
263{
264 UpdateVolume();
265 return 0;
266}
267
268void 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
276void 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
289CSample *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
307void 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
343bool 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
403static const void *s_pWVBuffer = nullptr;
404static int s_WVBufferPosition = 0;
405static int s_WVBufferSize = 0;
406
407static 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)
416static int ReadData(void *pId, void *pBuffer, int Size)
417{
418 (void)pId;
419 return ReadDataOld(pBuffer, Size);
420}
421
422static int ReturnFalse(void *pId)
423{
424 (void)pId;
425 return 0;
426}
427
428static unsigned int GetPos(void *pId)
429{
430 (void)pId;
431 return s_WVBufferPosition;
432}
433
434static unsigned int GetLength(void *pId)
435{
436 (void)pId;
437 return s_WVBufferSize;
438}
439
440static int PushBackByte(void *pId, int Char)
441{
442 s_WVBufferPosition -= 1;
443 return 0;
444}
445#endif
446
447bool 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
528int 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
568int 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
608int 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
631int 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
654void 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
674float 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
682float 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
700void 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
719void 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
725void 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
731void 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
746void 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
761void 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
776void 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
810void 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
825void 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
841ISound::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
890ISound::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
895ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags)
896{
897 return Play(ChannelId, SampleId, Flags, x: 0, y: 0);
898}
899
900void 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
915void 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
933void 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
950void 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
965bool 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
972void CSound::PauseAudioDevice()
973{
974 SDL_PauseAudioDevice(dev: m_Device, pause_on: 1);
975}
976
977void CSound::UnpauseAudioDevice()
978{
979 SDL_PauseAudioDevice(dev: m_Device, pause_on: 0);
980}
981
982IEngineSound *CreateEngineSound() { return new CSound; }
983