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 #ifndef VAVOOM_S_LOCAL_HEADER
27 #define VAVOOM_S_LOCAL_HEADER
30 /*# define AL_ALEXT_PROTOTYPES*/
31 # ifdef VAVOOM_USE_MOJOAL
32 # include "mojoal/AL/al.h"
33 # include "mojoal/AL/alc.h"
37 # include <AL/alext.h>
39 // linux headers doesn't define this
47 // our velocities are almost always zero, so why?
48 //#define VV_SND_ALLOW_VELOCITY
54 SSCMD_WaitUntilDone
, // used by PLAYUNTILDONE
77 ROLLOFF_Doom
, // linear rolloff with a logarithmic volume scale
78 ROLLOFF_Linear
, // linear rolloff with a linear volume scale
79 ROLLOFF_Log
, // logarithmic rolloff (standard hardware type)
80 ROLLOFF_Custom
, // lookup volume from SNDCURVE
86 enum { ST_Invalid
= -1, ST_NotLoaded
= 0, ST_Loading
= 1, ST_Loaded
= 2 };
88 atomic_int loadedStateVar
; // ST_XXX
90 VName TagName
; // name, by whitch sound is recognised in script
91 int LumpNum
; // lump number of sfx
93 int Priority
; // higher priority takes precendence
94 int NumChannels
; // total number of channels a sound type may occupy
96 float VolumeAmp
; // from sndinfo, cannot exceed 1.0
98 float Attenuation
; // multiplies the attenuation passed to S_Sound
99 // for "$alias", link is index of "real" sound
100 int Link
; // usually `-1`
101 int *Sounds
; // for random sounds, Link is count (and bRandomHeader is set)
103 // if set, this is a list of random sounds, `Link` is count, `Sounds` is index array
105 // set for "$playersounddup", `Link` is some number of something reserved (TODO: figure this out)
114 VVA_FORCEINLINE
int GetLoadedState () noexcept
{ return atomic_get(&loadedStateVar
); }
115 VVA_FORCEINLINE
void SetLoadedState (int value
) noexcept
{ atomic_set(&loadedStateVar
, value
); }
126 REVERBF_DecayTimeScale
= 0x01,
127 REVERBF_ReflectionsScale
= 0x02,
128 REVERBF_ReflectionsDelayScale
= 0x04,
129 REVERBF_ReverbScale
= 0x08,
130 REVERBF_ReverbDelayScale
= 0x10,
131 REVERBF_DecayHFLimit
= 0x20,
132 REVERBF_EchoTimeScale
= 0x40,
133 REVERBF_ModulationTimeScale
= 0x80,
137 struct VReverbProperties
{
139 float EnvironmentSize
;
140 float EnvironmentDiffusion
;
148 float ReflectionsDelay
;
149 float ReflectionsPanX
;
150 float ReflectionsPanY
;
151 float ReflectionsPanZ
;
159 float ModulationTime
;
160 float ModulationDepth
;
161 float AirAbsorptionHF
;
164 float RoomRolloffFactor
;
171 #if defined(VAVOOM_REVERB)
177 VReverbProperties Props
;
183 typedef ALCboolean (ALC_APIENTRY
*alcSetThreadContextFn
) (ALCcontext
*context
);
184 typedef ALCcontext
*(ALC_APIENTRY
*alcGetThreadContextFn
) (void);
186 typedef void (ALC_APIENTRY
*alDeferUpdatesSOFTFn
) (void);
187 typedef void (ALC_APIENTRY
*alProcessUpdatesSOFTFn
) (void);
189 typedef const ALchar
*(ALC_APIENTRY
*alGetStringiSOFTFn
) (ALenum pname
, ALsizei index
);
191 // sound device interface
192 // this class implements the only supported OpenAL Soft driver
193 class VOpenALDevice
{
195 //enum { MAX_VOICES = 256-4 };
196 enum { MAX_VOICES
= 64+2 };
198 enum { NUM_STRM_BUFFERS
= 8*2 };
199 enum { STRM_BUFFER_SIZE
= 1024*8 };
205 vint32 RealMaxVoices
;
207 bool HasTreadContext
;
208 alcSetThreadContextFn p_alcSetThreadContext
;
209 alcGetThreadContextFn p_alcGetThreadContext
;
212 alDeferUpdatesSOFTFn p_alDeferUpdatesSOFT
;
213 alProcessUpdatesSOFTFn p_alProcessUpdatesSOFT
;
215 bool HasForceSpatialize
;
216 ALenum alSrcSpatSoftEnum
;
217 //ALenum alSrcSpatSoftValue; // AL_AUTO_SOFT
219 ALuint StrmSampleRate
;
221 ALuint StrmBuffers
[NUM_STRM_BUFFERS
];
222 ALuint StrmAvailableBuffers
[NUM_STRM_BUFFERS
];
223 int StrmNumAvailableBuffers
;
225 vint16 StrmDataBuffer
[STRM_BUFFER_SIZE
*2]; // temp buffer we can use to decode data
227 alGetStringiSOFTFn p_alGetStringiSOFT
;
228 ALenum alNumResSoftValue
;
229 ALenum alDefResSoftValue
;
230 ALenum alResNameSoftValue
;
231 ALenum alSrcResamplerSoftValue
;
233 TArray
<VStr
> ResamplerNames
;
234 ALint defaultResampler
;
236 // if sound is queued to be loaded, we'll remember sound source here
242 TMapNC
<int, PendingSrc
*> sourcesPending
; // key is sound id
243 TMapNC
<ALuint
, int> srcPendingSet
; // key is source id, value is sound id
244 TMapNC
<ALuint
, bool> srcErrorSet
; // key is source id
245 TMapNC
<ALuint
, bool> activeSourceSet
; // key is source id
248 static bool IsError (const char *errmsg
, bool errabort
=false);
249 static void ClearError (); // reset error flag
251 bool AllocSource (ALuint
*src
);
253 // records one new pending sound
254 // allocates sound source, records,
255 // returns `true` on success, `false` on error
256 // `src` must not be NULL! returns source id on success
257 // returns `VSoundManager::LS_Error` or `VSoundManager::LS_Pending`
258 int RecordPendingSound (int sound_id
, ALuint
*src
);
260 // returns VSoundManager::LS_XXX
261 // if not errored, sets `src` to new sound source
262 int LoadSound (int sound_id
, ALuint
*src
);
265 int CommonPlaySound (bool is3d
, int sound_id
, const TVec
&clorig
, const TVec
&origin
, const TVec
&velocity
,
266 float volume
, float pitch
, bool Loop
, float atten
);
268 void UpdateSourceRolloff (ALuint src
, int sound_id
, const TVec
&clorig
, const TVec
&origin
,
269 const TVec
&velocity
, float atten
);
276 int SetChannels (int);
278 // to disable random pitch, use zero pitch; negative pitch means "use default"
279 int PlaySound (int sound_id
, float volume
, float pitch
, bool Loop
, float atten
);
280 int PlaySound3D (int sound_id
, const TVec
&clorig
, const TVec
&origin
, const TVec
&velocity
,
281 float volume
, float pitch
, bool Loop
, float atten
);
282 void UpdateChannel3D (int Handle
, int sound_id
, const TVec
&clorig
, const TVec
&Org
, const TVec
&Vel
, float atten
);
283 bool IsChannelPlaying (int Handle
);
284 void StopChannel (int Handle
);
285 void UpdateListener (const TVec
&org
, const TVec
&vel
, const TVec
&fwd
, const TVec
&/*right*/, const TVec
&up
286 #if defined(VAVOOM_REVERB)
291 // all stream functions should be thread-safe
292 bool OpenStream (int Rate
, int Bits
, int Channels
);
294 int GetStreamAvailable ();
295 vint16
*GetStreamBuffer ();
296 void SetStreamData (vint16
*data
, int len
);
297 void SetStreamVolume (float vol
);
299 void ResumeStream ();
300 void SetStreamPitch (float pitch
);
302 void AddCurrentThread ();
303 void RemoveCurrentThread ();
305 // call this to start batch update
306 void StartBatchUpdate ();
307 // and this to commit it
308 void FinishBatchUpdate ();
310 int GetResamplerCount () const noexcept
{ return ResamplerNames
.length(); }
311 VStr
GetResamplerName (int idx
) const noexcept
{ return (idx
>= 0 && idx
< ResamplerNames
.length() ? ResamplerNames
[idx
] : VStr()); }
312 int GetDefaultResampler () const noexcept
{ return defaultResampler
; }
314 // WARNING! this must be called from the main thread, i.e.
315 // from the thread that calls `PlaySound*()` API!
316 // returns `true` if that sound was pending
317 bool NotifySoundLoaded (int sound_id
, bool success
);
320 class VOpenALDevice
{};
327 // loader of sound samples
328 class VSampleLoader
: public VInterface
{
333 static VSampleLoader
*List
;
336 static void InsertIntoList (VSampleLoader
*&list
, VSampleLoader
*codec
) noexcept
;
339 VSampleLoader () = delete;
340 VV_DISABLE_COPY(VSampleLoader
)
341 VSampleLoader (int prio
) : Next(nullptr), Priority(prio
) { InsertIntoList(List
, this); }
342 virtual void Load (sfxinfo_t
&Sfx
, VStream
&Strm
) = 0;
343 virtual const char *GetName () const noexcept
= 0;
345 // codec must be initialized, and it will not be owned
346 void LoadFromAudioCodec (sfxinfo_t
&Sfx
, VAudioCodec
*Codec
);
350 // streamed audio decoder interface
351 class VAudioCodec
: public VInterface
{
358 VAudioCodec () : SampleRate(44100), SampleBits(16), NumChannels(2) {}
359 // always decodes interleaved stereo, returns number of frames
360 // `NumFrames` is number of stereo samples (frames), and it will never be zero
361 // it should always decode `NumFrames`, unless end-of-stream happens
362 virtual int Decode (vint16
*Data
, int NumFrames
) = 0;
363 virtual bool Finished () = 0;
364 virtual void Restart () = 0;
368 // audio codec creator
369 // `sign` will always contain at least 4 bytes
370 typedef VAudioCodec
*(*CreatorFn
) (VStream
*InStrm
, const vuint8 sign
[], int signsize
);
372 // description of an audio codec
373 struct FAudioCodecDesc
{
374 const char *Description
;
377 FAudioCodecDesc
*Next
;
379 static FAudioCodecDesc
*List
;
382 static void InsertIntoList (FAudioCodecDesc
*&list
, FAudioCodecDesc
*codec
) noexcept
;
385 FAudioCodecDesc () = delete;
386 VV_DISABLE_COPY(FAudioCodecDesc
)
387 // codecs with `hasGoodSignature` will be checked last
388 FAudioCodecDesc (const char *InDescription
, CreatorFn InCreator
, int prio
)
389 : Description(InDescription
)
393 InsertIntoList(List
, this);
397 // priority for decoders without a signature
398 #define AUDIO_DEFAULT_PRIO (100)
400 // priority for decoders without a signature
401 #define AUDIO_NO_SIGNATURE (1000)
403 // audio codec registration helper
404 #define IMPLEMENT_AUDIO_CODEC(TClass,Description) \
405 FAudioCodecDesc TClass##Desc(Description, TClass::Create, AUDIO_DEFAULT_PRIO);
407 #define IMPLEMENT_AUDIO_CODEC_EX(TClass,Description,Prio) \
408 FAudioCodecDesc TClass##Desc(Description, TClass::Create, Prio);
411 // quick MUS to MIDI converter
418 TArrayNC
<vuint8
> Data
; // primary data
423 vint32 Mus2MidChannel
[16];
425 static const vuint8 Mus2MidControl
[15];
426 static const vuint8 TrackEnd
[];
427 static const vuint8 MidiKey
[];
428 static const vuint8 MidiTempo
[];
430 int FirstChannelAvailable ();
431 void TWriteByte (int, vuint8
);
432 void TWriteBuf (int, const vuint8
*, int);
433 void TWriteVarLen (int, vuint32
);
434 vuint32
ReadTime (VStream
&);
435 bool Convert (VStream
&);
436 void WriteMIDIFile (VStream
&);
440 int Run (VStream
&, VStream
&);
444 // music player (controls streaming thread)
445 class VStreamMusicPlayer
{
446 friend class VStreamMusicPlayerWorker
;
448 // stream player is using a separate thread
450 mythread_mutex stpPingLock
;
451 mythread_cond stpPingCond
;
452 mythread_mutex stpLockPong
;
453 mythread_cond stpCondPong
;
459 //WARNING! never use/access those directly!
462 // current playing song info
463 bool CurrLoop
; // access with data locked
464 VStr CurrSong
; // access with data locked
468 VOpenALDevice
*SoundDevice
; // `nullptr` means "shutdown called"
471 atomic_int loopCounter
;
472 atomic_int dataSpinlock
;
474 // streamer thread ping/pong bussiness
476 STP_Quit
, // stop playing, and quit immediately
477 STP_Start
, // start playing current stream
478 STP_Restart
, // restart playing current stream
479 STP_Stop
, // stop current stream
480 STP_Pause
, // pause current stream
481 STP_Resume
, // resume current stream
482 STP_IsPlaying
, // check if current stream is playing
483 STP_SetPitch
, // change stream pitch
485 // the following two commands will replace current music with the new one
486 // music name is in `namebuf`
490 volatile STPCommand stpcmd
;
491 volatile bool stpIsPlaying
; // it will return `STP_IsPlaying` result here
492 volatile float stpNewPitch
;
493 volatile float stpNewVolume
;
495 bool stpThreadWaitPing (unsigned int msecs
);
496 void stpThreadSendPong ();
498 void stpThreadSendCommand (STPCommand acmd
);
500 inline void LockData () noexcept
{
501 // this returns old value; wait unit it will be zero
502 while (atomic_cmp_xchg(&dataSpinlock
, 0, 1)) {}
505 inline void UnlockData () noexcept
{
506 atomic_set(&dataSpinlock
, 0);
510 VStreamMusicPlayer
*plr
;
511 inline DataLocker (VStreamMusicPlayer
*aplr
) noexcept
: plr(aplr
) { plr
->LockData(); }
512 inline ~DataLocker () noexcept
{ plr
->UnlockData(); }
513 DataLocker (const DataLocker
&) = delete;
514 DataLocker
&operator = (const DataLocker
&) = delete;
518 inline VStreamMusicPlayer (VOpenALDevice
*InSoundDevice
)
520 , threadInited(false)
526 , SoundDevice(InSoundDevice
)
529 , stpIsPlaying(false)
534 inline ~VStreamMusicPlayer () { Shutdown(); }
538 void Play (VAudioCodec
*InCodec
, const char *InName
, bool InLoop
);
544 void SetPitch (float pitch
);
545 void SetVolume (float volume
, bool fromStreamThread
=false);
547 VStr
GetCurrentSong ();
548 bool IsCurrentSongLooped ();
549 float GetNewVolume ();
551 // all play functions will reset loop counter
552 inline void ResetLoopCounter () noexcept
{ atomic_set(&loopCounter
, 0); }
553 inline int GetLoopCounter () noexcept
{ return atomic_get(&loopCounter
); }
554 inline void IncLoopCounter () noexcept
{ atomic_increment(&loopCounter
); }
556 void LoadAndPlay (const char *InName
, bool InLoop
);
559 class VStreamMusicPlayerWorker
{
560 friend class VStreamMusicPlayer
;
562 static bool doTick (VStreamMusicPlayer
*strm
);
563 static MYTHREAD_RET_TYPE
streamPlayerThread (void *adevobj
);
567 //**************************************************************************
569 // MIDI and MUS file header structures.
571 //**************************************************************************
573 #define MUSMAGIC "MUS\032"
574 #define MIDIMAGIC "MThd"
579 char ID
[4]; // identifier "MUS" 0x1A
582 vuint16 NumChannels
; // count of primary channels
583 vuint16 NumSecChannels
; // count of secondary channels (?)
584 vuint16 InstrumentCount
;
599 // ////////////////////////////////////////////////////////////////////////// //
603 enum { MIDI_MAX_CHANNEL
= 16, };
605 enum /*event type*/ {
606 MIDI_NOOP
= 0, // special synthesized event type
611 CONTROL_CHANGE
= 0xb0,
612 PROGRAM_CHANGE
= 0xc0,
613 CHANNEL_PRESSURE
= 0xd0,
619 MIDI_META_EVENT
= 0xff,
625 MIDI_COPYRIGHT
= 0x02,
626 MIDI_TRACK_NAME
= 0x03,
627 MIDI_INST_NAME
= 0x04,
630 MIDI_CUE_POINT
= 0x07,
631 MIDI_CHANNEL
= 0x20, // channel for the following meta
633 MIDI_SET_TEMPO
= 0x51,
634 MIDI_SMPTE_OFFSET
= 0x54,
635 MIDI_TIME_SIGNATURE
= 0x58,
636 MIDI_KEY_SIGNATURE
= 0x59,
637 MIDI_SEQUENCER_EVENT
= 0x7f,
643 const vuint8
*tkdata
;
646 double nextetime
; // milliseconds
647 // after last track command, wait a little
651 vuint8 runningStatus
;
652 vuint8 lastMetaChannel
; // 0xff: all
653 VStr copyright
; // track copyright
654 VStr tname
; // track name
655 VStr iname
; // instrument name
658 MidiTrack () : datasize(0), tkdata(nullptr), pos(nullptr), song(nullptr), nextetime(0), fadeoff(0), runningStatus(0), lastMetaChannel(0xff) {}
660 void setup (MIDIData
*asong
, const vuint8
*adata
, vint32 alen
) {
661 if (alen
<= 0) adata
= nullptr;
662 if (!adata
) alen
= 0;
669 inline void abort (bool full
) { if (tkdata
) pos
= tkdata
+datasize
; if (full
) fadeoff
= 0; }
671 inline void reset () {
674 fadeoff
= (song
->type
!= 2 ? 100 : 0);
676 lastMetaChannel
= 0xff;
682 inline bool isEndOfData () const { return (!tkdata
|| pos
== tkdata
+datasize
); }
683 inline bool isEOT () const { return (isEndOfData() ? (nextEventTime() <= song
->currtime
) : false); }
685 inline int getPos () const { return (tkdata
? (int)(ptrdiff_t)(pos
-tkdata
) : 0); }
686 inline int getLeft () const { return (tkdata
? (int)(ptrdiff_t)(tkdata
+datasize
-pos
) : 0); }
688 inline const vuint8
*getCurPosPtr () const { return pos
; }
690 inline int size () const { return datasize
; }
691 inline vuint8
dataAt (int pos
) const { return (tkdata
&& datasize
> 0 && pos
>= 0 && pos
< datasize
? tkdata
[pos
] : 0); }
692 inline vuint8
operator [] (int ofs
) const {
693 if (!tkdata
|| !datasize
) return 0;
695 const int left
= getLeft();
696 if (ofs
>= left
) return 0;
699 if (ofs
== MIN_VINT32
) return 0;
701 const int pos
= getLeft();
702 if (ofs
> pos
) return 0;
703 return tkdata
[pos
-ofs
];
707 inline MIDIData
*getSong () { return song
; }
708 inline const MIDIData
*getSong () const { return song
; }
710 inline vuint8
peekNextMidiByte () {
711 if (isEndOfData()) return 0;
715 inline vuint8
getNextMidiByte () {
716 if (isEndOfData()) return 0;
720 inline void skipNextMidiBytes (int len
) {
721 while (len
-- > 0) (void)getNextMidiByte();
724 // reads a variable-length SMF number
725 vuint32
readVarLen () {
729 if (left
== 0) { abort(true); return 0; }
731 vuint8 t
= getNextMidiByte();
732 res
= (res
<<7)|(t
&0x7fu
);
733 if ((t
&0x80) == 0) break;
738 inline vuint32
getDeltaTic () { return readVarLen(); }
740 inline double nextEventTime () const { return (nextetime
+(isEndOfData() ? fadeoff
: 0)); }
742 inline void advanceTics (vuint32 tics
) {
743 if (isEndOfData()) return;
744 nextetime
+= song
->tic2ms(tics
);
750 vuint8 type
= MIDI_NOOP
;
752 vuint32 data1
= 0; // payload size for MIDI_SYSEX and MIDI_SEQUENCER_EVENT
754 // this is for MIDI_SYSEX and MIDI_SEQUENCER_EVENT
755 const void *payload
= nullptr;
758 // timemsecs -- time for the current event (need not to monotonically increase, can jump around)
759 // note: time can jump around due to events from different tracks
760 typedef void (*EventCBType
) (double timemsecs
, const MidiEvent
&ev
, void *udata
);
764 TArray
<MidiTrack
> tracks
;
768 double currtime
; // milliseconds
769 int currtrack
; // for midi type 2
775 inline void setTempo (vint32 atempo
) {
776 if (atempo
<= 0) atempo
= 480000;
780 inline double tic2ms (vuint32 tic
) const { return (((double)tic
*tempo
)/(delta
*1000.0)); }
781 inline double ms2tic (double msec
) const { return ((msec
*delta
*1000.0)/(double)tempo
); }
785 bool runTrack (int tidx
, EventCBType cb
, void *udata
);
788 VV_DISABLE_COPY(MIDIData
)
792 inline bool isValid () const { return (type
!= 0xff && delta
!= 0 && tempo
!= 0); }
793 inline bool isEmpty () const { return (!isValid() || tracks
.length() == 0); }
797 static bool isMidiStream (VStream
&strm
);
798 bool parseStream (VStream
&strm
);
803 // >0: number of frames one can generate until next event
804 int decodeStep (EventCBType cb
, int SampleRate
, void *udata
);
813 extern VCvarB snd_sf2_autoload
;
814 extern VCvarS snd_sf2_file
;
816 extern bool SoundHasBadApple
;
818 extern TArray
<VStr
> sf2FileList
;
820 extern vuint8
*S_SoundCurve
;
821 extern int S_SoundCurveSize
;
823 bool SF2_NeedDiskScan ();
824 void SF2_SetDiskScanned (bool v
);
825 void SF2_ScanDiskBanks (); // this fills `sf2FileList`