1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
28 #include "snd_local.h"
30 //#define VCCRUN_SOUND_THREAD_DEBUG
31 //#define VCCRUN_SOUND_THREAD_DEBUG_TICKER
33 #ifdef VCCRUN_SOUND_THREAD_DEBUG
34 # define SDLOG(...) GLog.WriteLine(__VA_ARGS__)
36 # define SDLOG(...) do {} while (0)
40 extern VCvarF snd_master_volume
;
43 //==========================================================================
45 // VStreamMusicPlayerWorker::doTick
47 // returns `true` if caller should perform stopping
49 //==========================================================================
50 bool VStreamMusicPlayerWorker::doTick (VStreamMusicPlayer
*strm
) {
51 if (!strm
->StrmOpened
) return false;
52 if (strm
->Stopping
&& strm
->FinishTime
+0.5 < Sys_Time()) {
61 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
62 GLog
.Logf(NAME_Debug
, "doTick: decoding... (%d)", strm
->SoundDevice
->GetStreamAvailable());
64 int loopsAllowed
= 42;
65 const double stt
= Sys_Time();
66 for (int Len
= strm
->SoundDevice
->GetStreamAvailable(); Len
; Len
= strm
->SoundDevice
->GetStreamAvailable()) {
67 vint16
*Data
= strm
->SoundDevice
->GetStreamBuffer();
69 int decodedFromLoop
= 0, loopCount
= 0;
70 while (!strm
->Stopping
&& StartPos
< Len
) {
71 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
72 GLog
.Logf(NAME_Debug
, "doTick: trying to decode %d samples (len=%d, stpos=%d)", Len
-StartPos
, Len
, StartPos
);
74 int SamplesDecoded
= strm
->Codec
->Decode(Data
+StartPos
*2, Len
-StartPos
);
75 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
76 GLog
.Logf(NAME_Debug
, "doTick: decoded %d samples", SamplesDecoded
);
78 if (SamplesDecoded
< 0) SamplesDecoded
= 0;
79 StartPos
+= SamplesDecoded
;
80 decodedFromLoop
+= SamplesDecoded
;
81 if (strm
->Codec
->Finished() || SamplesDecoded
<= 0) {
83 strm
->IncLoopCounter();
85 { VStreamMusicPlayer::DataLocker
dlock(strm
); bislooped
= strm
->CurrLoop
; }
88 strm
->Codec
->Restart();
90 if (loopCount
== 1) decodedFromLoop
= 0;
91 if (loopCount
== 3 && decodedFromLoop
< 256) {
92 GLog
.Logf(NAME_Warning
, "Looped music stream is too short, aborting it");
93 VStreamMusicPlayer::DataLocker
dlock(strm
);
94 strm
->CurrLoop
= false;
95 strm
->Stopping
= true;
96 strm
->FinishTime
= Sys_Time();
99 // we'll wait for some time to finish playing
100 strm
->Stopping
= true;
101 strm
->FinishTime
= Sys_Time();
103 } else if (StartPos
< Len
) {
104 // should never happen
105 GLog
.Logf(NAME_Warning
, "Music stream decoded less but is not finished.");
106 strm
->Stopping
= true;
107 strm
->FinishTime
= Sys_Time();
110 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
111 GLog
.Logf(NAME_Debug
, "doTick: setting data; len=%d; stpos=%d", Len
, StartPos
);
113 if (strm
->Stopping
&& StartPos
< Len
) memset(Data
+StartPos
*2, 0, (Len
-StartPos
)*4);
114 strm
->SoundDevice
->SetStreamData(Data
, Len
);
115 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
116 GLog
.Logf(NAME_Debug
, "doTick: decoded; len=%d; stpos=%d; time=%g", Len
, StartPos
, Sys_Time()-stt
);
118 if (--loopsAllowed
<= 0) break;
119 if (Sys_Time()-stt
> 0.5) break;
121 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
122 GLog
.Log(NAME_Debug
, "doTick: DONE decoing...");
128 //==========================================================================
130 // VStreamMusicPlayer::stpThreadWaitPing
132 // use this in streamer thread to check if there was a ping.
133 // returns `true` if ping was received.
135 //==========================================================================
136 bool VStreamMusicPlayer::stpThreadWaitPing (unsigned int msecs
) {
137 if (msecs
< 1) msecs
= 1;
138 mythread_condtime ctime
;
139 mythread_condtime_set(&ctime
, &stpPingCond
, msecs
);
140 auto res
= mythread_cond_timedwait(&stpPingCond
, &stpPingLock
, &ctime
);
145 //==========================================================================
147 // VStreamMusicPlayer::stpThreadSendPong
149 // use this in streamer thread to notify main thead that it can go on
151 //==========================================================================
152 void VStreamMusicPlayer::stpThreadSendPong () {
153 SDLOG("STP: getting lock for pong sending");
154 // we'll aquire lock if another thread is in cond_wait
155 mythread_mutex_lock(&stpLockPong
);
156 SDLOG("STP: releasing lock for pong sending");
157 // and immediately release it
158 mythread_mutex_unlock(&stpLockPong
);
160 mythread_cond_signal(&stpCondPong
);
164 //==========================================================================
166 // VStreamMusicPlayer::stpThreadSendCommand
168 // send command to streamer thread, wait for it to be processed
170 //==========================================================================
171 void VStreamMusicPlayer::stpThreadSendCommand (STPCommand acmd
) {
173 SDLOG("MAIN: sending command %u", (unsigned)stpcmd
);
174 SDLOG("MAIN: getting lock for ping sending");
175 // we'll aquire lock if another thread is in cond_wait
176 mythread_mutex_lock(&stpPingLock
);
177 SDLOG("MAIN: releasing lock for ping sending");
178 // and immediately release it
179 mythread_mutex_unlock(&stpPingLock
);
181 mythread_cond_signal(&stpPingCond
);
182 SDLOG("MAIN: ping sent.");
183 if (acmd
== STP_Quit
) {
184 SDLOG("MAIN: waiting for streamer thread to stop");
185 // wait for it to complete
186 mythread_join(stpThread
);
187 SDLOG("MAIN: streamer thread stopped");
189 mythread_cond_wait(&stpCondPong
, &stpLockPong
);
190 SDLOG("MAIN: pong received.");
195 //==========================================================================
197 // VStreamMusicPlayerWorker::streamPlayerThread
199 //==========================================================================
200 MYTHREAD_RET_TYPE
VStreamMusicPlayerWorker::streamPlayerThread (void *adevobj
) {
201 Sys_PinOtherThread();
202 VStreamMusicPlayer
*strm
= (VStreamMusicPlayer
*)adevobj
;
203 mythread_mutex_lock(&strm
->stpPingLock
);
204 // set sound device context for this thread
205 strm
->SoundDevice
->AddCurrentThread();
206 strm
->SoundDevice
->SetStreamPitch(strm
->stpNewPitch
);
207 strm
->SoundDevice
->SetStreamVolume(strm
->stpNewVolume
);
208 // send "we are ready" signal
209 strm
->stpThreadSendPong();
212 VStr lastLoadedSongName
;
213 bool lastSongWasLooped
= false;
214 bool doLoadNewSong
= false;
215 SDLOG("STP: streaming thread started.");
217 //SDLOG("STP: streaming thread waiting");
218 if (strm
->stpThreadWaitPing(100*5)) {
219 SDLOG("STP: streaming thread received the command: %u", (unsigned)strm
->stpcmd
);
222 switch (strm
->stpcmd
) {
223 case VStreamMusicPlayer::STP_Quit
: // stop playing, and quit immediately
224 case VStreamMusicPlayer::STP_Stop
: // stop current stream
225 doQuit
= (strm
->stpcmd
== VStreamMusicPlayer::STP_Quit
);
226 SDLOG("STP: %s", (doQuit
? "quit" : "stop"));
227 if (strm
->StrmOpened
) {
228 // unpause it, just in case
230 strm
->SoundDevice
->ResumeStream();
231 strm
->Paused
= false;
233 strm
->SoundDevice
->CloseStream();
234 strm
->StrmOpened
= false;
236 strm
->Codec
= nullptr;
238 lastLoadedSongName
.clear();
240 case VStreamMusicPlayer::STP_Start
: // start playing current stream
242 strm
->StrmOpened
= true;
243 strm
->SoundDevice
->SetStreamPitch(1.0f
);
244 strm
->SoundDevice
->SetStreamVolume(strm
->stpNewVolume
);
246 VStreamMusicPlayer::DataLocker
dlock(strm
);
247 lastLoadedSongName
= strm
->CurrSong
.cloneUniqueMT();
250 case VStreamMusicPlayer::STP_Restart
: // start playing current stream
251 SDLOG("STP: restart");
252 if (!lastLoadedSongName
.isEmpty()) {
253 doLoadNewSong
= true;
254 doLoop
= lastSongWasLooped
;
255 newSongName
= lastLoadedSongName
;
258 case VStreamMusicPlayer::STP_Pause
: // pause current stream
260 if (strm
->StrmOpened
) {
261 strm
->SoundDevice
->PauseStream();
265 case VStreamMusicPlayer::STP_Resume
: // resume current stream
266 SDLOG("STP: resume");
267 if (strm
->StrmOpened
) {
268 strm
->SoundDevice
->ResumeStream();
269 strm
->Paused
= false;
272 case VStreamMusicPlayer::STP_IsPlaying
: // check if current stream is playing
273 SDLOG("STP: playing state request");
274 strm
->stpIsPlaying
= strm
->StrmOpened
;
276 case VStreamMusicPlayer::STP_SetPitch
:
277 SDLOG("STP: pitch change");
278 if (strm
->StrmOpened
) {
279 strm
->SoundDevice
->SetStreamPitch(strm
->stpNewPitch
);
282 case VStreamMusicPlayer::STP_SetVolume
:
283 SDLOG("STP: volume change");
284 strm
->SoundDevice
->SetStreamVolume(strm
->stpNewVolume
);
286 case VStreamMusicPlayer::STP_PlaySongLooped
:
287 case VStreamMusicPlayer::STP_PlaySong
:
288 SDLOG("STP: play song X");
289 doLoadNewSong
= true;
290 doLoop
= (strm
->stpcmd
!= VStreamMusicPlayer::STP_PlaySong
);
291 newSongName
= VStr(strm
->namebuf
);
294 // quit doesn't require pong
297 SDLOG("STP: sending pong");
298 strm
->stpThreadSendPong();
299 SDLOG("STP: pong sent.");
303 strm
->ResetLoopCounter();
304 doLoadNewSong
= false;
307 if (strm
->StrmOpened
) {
309 // unpause it, just in case
311 strm
->SoundDevice
->ResumeStream();
312 strm
->Paused
= false;
314 strm
->SoundDevice
->CloseStream();
315 strm
->StrmOpened
= false;
317 strm
->Codec
= nullptr;
321 lastSongWasLooped
= doLoop
;
322 lastLoadedSongName
= newSongName
.cloneUniqueMT();
323 bool success
= false;
325 if (!newSongName
.isEmpty() && GAudio
) {
326 // unlock thread, so other song commands won't stall
327 // k8: nope, it won't work anyway
328 //mythread_mutex_unlock(&stpPingLock);
329 //mythread_mutex_lock(&stpPingLock);
330 //GLog.Logf("STRM: loading song '%s'", *newSongName);
331 VAudioCodec
*codec
= GAudio
->LoadSongInternal(*newSongName
, wasPlaying
, true); // called from streaming thread
333 bool xopened
= strm
->SoundDevice
->OpenStream(codec
->SampleRate
, codec
->SampleBits
, codec
->NumChannels
);
335 GLog
.WriteLine(NAME_Warning
, "cannot' start song '%s'", *newSongName
);
338 //GLog.Logf("STRM: starting song '%s'", *newSongName);
341 VStreamMusicPlayer::DataLocker
dlock(strm
);
342 strm
->CurrSong
= newSongName
.cloneUniqueMT();
343 strm
->CurrLoop
= doLoop
;
345 strm
->Stopping
= false;
346 strm
->stpNewVolume
= strm
->lastVolume
;
347 strm
->stpNewPitch
= 1.0f
;
348 //stpThreadSendCommand(STP_Start);
349 strm
->StrmOpened
= true;
350 strm
->SoundDevice
->SetStreamPitch(1.0f
);
351 strm
->SoundDevice
->SetStreamVolume(strm
->stpNewVolume
);
356 if (!success
) lastLoadedSongName
.clear();
359 if (strm
->StrmOpened
) {
360 //SDLOG("STP: streaming thread ticking");
361 // advance playing stream
362 #ifndef VCCRUN_SOUND_THREAD_DUMMY
364 // unpause it, just in case
366 strm
->SoundDevice
->ResumeStream();
367 strm
->Paused
= false;
369 strm
->SoundDevice
->CloseStream();
370 strm
->StrmOpened
= false;
372 strm
->Codec
= nullptr;
377 strm
->SoundDevice
->RemoveCurrentThread();
378 mythread_mutex_unlock(&strm
->stpPingLock
);
379 SDLOG("STP: streaming thread complete.");
381 return MYTHREAD_RET_VALUE
;
385 //==========================================================================
387 // VStreamMusicPlayer::Init
389 //==========================================================================
390 void VStreamMusicPlayer::Init () {
391 mythread_mutex_init(&stpPingLock
);
392 mythread_cond_init(&stpPingCond
);
394 mythread_mutex_init(&stpLockPong
);
395 mythread_cond_init(&stpCondPong
);
397 // init for the first pong
398 mythread_mutex_lock(&stpLockPong
);
400 // create stream player thread
401 if (mythread_create(&stpThread
, &VStreamMusicPlayerWorker::streamPlayerThread
, this)) Sys_Error("OpenAL driver cannot create streaming thread");
402 // wait for the first pong
403 mythread_cond_wait(&stpCondPong
, &stpLockPong
);
404 SDLOG("MAIN: first pong received.");
409 //==========================================================================
411 // VStreamMusicPlayer::Shutdown
413 //==========================================================================
414 void VStreamMusicPlayer::Shutdown () {
416 if (developer
) GLog
.Log(NAME_Dev
, "VStreamMusicPlayer::Shutdown(): sending quit command");
417 stpThreadSendCommand(STP_Quit
); // this joins player thread
418 mythread_mutex_unlock(&stpLockPong
);
421 mythread_mutex_destroy(&stpPingLock
);
422 mythread_cond_destroy(&stpPingCond
);
424 mythread_mutex_destroy(&stpLockPong
);
425 mythread_cond_destroy(&stpCondPong
);
427 if (developer
) GLog
.Log(NAME_Dev
, "VStreamMusicPlayer::Shutdown(): shutdown complete!");
428 SDLOG("MAIN: destroyed mutexes and conds");
429 SoundDevice
= nullptr;
431 threadInited
= false;
435 //==========================================================================
437 // VStreamMusicPlayer::Play
439 //==========================================================================
440 void VStreamMusicPlayer::Play (VAudioCodec
*InCodec
, const char *InName
, bool InLoop
) {
442 GLog
.Logf(NAME_Error
, "*** cannot load song to uninited stream player!");
445 stpThreadSendCommand(STP_Stop
);
446 if (InName
&& InName
[0]) {
447 bool xopened
= SoundDevice
->OpenStream(InCodec
->SampleRate
, InCodec
->SampleBits
, InCodec
->NumChannels
);
449 GLog
.WriteLine("WARNING: cannot' start song '%s'", InName
);
452 // no need to lock anything here, because no song is playing, but just in case
454 DataLocker
dlock(this);
459 stpNewVolume
= lastVolume
;
462 stpThreadSendCommand(STP_Start
);
467 //==========================================================================
469 // VStreamMusicPlayer::LoadAndPlay
471 //==========================================================================
472 void VStreamMusicPlayer::LoadAndPlay (const char *InName
, bool InLoop
) {
474 GLog
.Logf(NAME_Error
, "*** cannot load song to uninited stream player!");
477 if (!InName
) InName
= "";
478 memset(namebuf
, 0, sizeof(namebuf
));
479 strncpy(namebuf
, InName
, sizeof(namebuf
)-1);
480 stpThreadSendCommand(InLoop
? STP_PlaySongLooped
: STP_PlaySong
);
484 //==========================================================================
486 // VStreamMusicPlayer::Pause
488 //==========================================================================
489 void VStreamMusicPlayer::Pause () {
490 if (threadInited
) stpThreadSendCommand(STP_Pause
);
494 //==========================================================================
496 // VStreamMusicPlayer::Resume
498 //==========================================================================
499 void VStreamMusicPlayer::Resume () {
500 if (threadInited
) stpThreadSendCommand(STP_Resume
);
504 //==========================================================================
506 // VStreamMusicPlayer::Stop
508 //==========================================================================
509 void VStreamMusicPlayer::Stop () {
510 if (threadInited
) stpThreadSendCommand(STP_Stop
);
514 //==========================================================================
516 // VStreamMusicPlayer::Restart
518 //==========================================================================
519 void VStreamMusicPlayer::Restart () {
520 if (threadInited
) stpThreadSendCommand(STP_Restart
);
524 //==========================================================================
526 // VStreamMusicPlayer::IsPlaying
528 //==========================================================================
529 bool VStreamMusicPlayer::IsPlaying () {
530 if (!threadInited
) return false;
531 stpThreadSendCommand(STP_IsPlaying
);
536 //==========================================================================
538 // VStreamMusicPlayer::SetPitch
540 //==========================================================================
541 void VStreamMusicPlayer::SetPitch (float pitch
) {
543 if (threadInited
) stpThreadSendCommand(STP_SetPitch
);
547 //==========================================================================
549 // VStreamMusicPlayer::SetVolume
551 //==========================================================================
552 void VStreamMusicPlayer::SetVolume (float volume
, bool fromStreamThread
) {
553 volume
= clampval(volume
*snd_master_volume
.asFloat(), 0.0f
, 1.0f
);
554 if (volume
== lastVolume
) return;
556 stpNewVolume
= volume
;
557 if (!fromStreamThread
) {
558 if (threadInited
) stpThreadSendCommand(STP_SetVolume
);
563 //==========================================================================
565 // VStreamMusicPlayer::GetCurrentSong
567 //==========================================================================
568 VStr
VStreamMusicPlayer::GetCurrentSong () {
569 DataLocker
dlock(this);
574 //==========================================================================
576 // VStreamMusicPlayer::IsCurrentSongLooped
578 //==========================================================================
579 bool VStreamMusicPlayer::IsCurrentSongLooped () {
580 DataLocker
dlock(this);
585 //==========================================================================
587 // VStreamMusicPlayer::GetNewVolume
589 //==========================================================================
590 float VStreamMusicPlayer::GetNewVolume () {