engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / sound / snd_local.h
blob2b3897f65100b0244f558ede9ddfdd81acd172f0
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
25 //**************************************************************************
26 #ifndef VAVOOM_S_LOCAL_HEADER
27 #define VAVOOM_S_LOCAL_HEADER
29 #ifdef CLIENT
30 /*# define AL_ALEXT_PROTOTYPES*/
31 # ifdef VAVOOM_USE_MOJOAL
32 # include "mojoal/AL/al.h"
33 # include "mojoal/AL/alc.h"
34 # else
35 # include <AL/al.h>
36 # include <AL/alc.h>
37 # include <AL/alext.h>
38 # endif
39 // linux headers doesn't define this
40 # ifndef OPENAL
41 # define OPENAL
42 # endif
43 #endif
45 #include "sound.h"
47 // our velocities are almost always zero, so why?
48 //#define VV_SND_ALLOW_VELOCITY
51 enum ESSCmds {
52 SSCMD_None,
53 SSCMD_Play,
54 SSCMD_WaitUntilDone, // used by PLAYUNTILDONE
55 SSCMD_PlayTime,
56 SSCMD_PlayRepeat,
57 SSCMD_PlayLoop,
58 SSCMD_Delay,
59 SSCMD_DelayOnce,
60 SSCMD_DelayRand,
61 SSCMD_Volume,
62 SSCMD_VolumeRel,
63 SSCMD_VolumeRand,
64 SSCMD_StopSound,
65 SSCMD_Attenuation,
66 SSCMD_RandomSequence,
67 SSCMD_Branch,
68 SSCMD_Select,
69 SSCMD_End
72 class VSoundSeqNode;
75 // rolloff types
76 enum {
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
84 // SoundFX struct
85 struct sfxinfo_t {
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
95 float ChangePitch;
96 float VolumeAmp; // from sndinfo, cannot exceed 1.0
97 FRolloffInfo Rolloff;
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
104 bool bRandomHeader;
105 // set for "$playersounddup", `Link` is some number of something reserved (TODO: figure this out)
106 bool bPlayerReserve;
107 bool bSingular;
109 vuint32 SampleRate;
110 int SampleBits;
111 vuint32 DataSize;
112 void *Data;
114 VVA_FORCEINLINE int GetLoadedState () noexcept { return atomic_get(&loadedStateVar); }
115 VVA_FORCEINLINE void SetLoadedState (int value) noexcept { atomic_set(&loadedStateVar, value); }
118 struct seq_info_t {
119 VName Name;
120 VName Slot;
121 vint32 *Data;
122 vint32 StopSound;
125 enum {
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 {
138 int Environment;
139 float EnvironmentSize;
140 float EnvironmentDiffusion;
141 int Room;
142 int RoomHF;
143 int RoomLF;
144 float DecayTime;
145 float DecayHFRatio;
146 float DecayLFRatio;
147 int Reflections;
148 float ReflectionsDelay;
149 float ReflectionsPanX;
150 float ReflectionsPanY;
151 float ReflectionsPanZ;
152 int Reverb;
153 float ReverbDelay;
154 float ReverbPanX;
155 float ReverbPanY;
156 float ReverbPanZ;
157 float EchoTime;
158 float EchoDepth;
159 float ModulationTime;
160 float ModulationDepth;
161 float AirAbsorptionHF;
162 float HFReference;
163 float LFReference;
164 float RoomRolloffFactor;
165 float Diffusion;
166 float Density;
167 int Flags;
171 #if defined(VAVOOM_REVERB)
172 struct VReverbInfo {
173 VReverbInfo *Next;
174 const char *Name;
175 int Id;
176 bool Builtin;
177 VReverbProperties Props;
179 #endif
182 #ifdef CLIENT
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 {
194 private:
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 };
201 ALCdevice *Device;
202 ALCcontext *Context;
203 ALuint *Buffers;
204 vint32 BufferCount;
205 vint32 RealMaxVoices;
207 bool HasTreadContext;
208 alcSetThreadContextFn p_alcSetThreadContext;
209 alcGetThreadContextFn p_alcGetThreadContext;
211 bool HasBatchUpdate;
212 alDeferUpdatesSOFTFn p_alDeferUpdatesSOFT;
213 alProcessUpdatesSOFTFn p_alProcessUpdatesSOFT;
215 bool HasForceSpatialize;
216 ALenum alSrcSpatSoftEnum;
217 //ALenum alSrcSpatSoftValue; // AL_AUTO_SOFT
219 ALuint StrmSampleRate;
220 ALuint StrmFormat;
221 ALuint StrmBuffers[NUM_STRM_BUFFERS];
222 ALuint StrmAvailableBuffers[NUM_STRM_BUFFERS];
223 int StrmNumAvailableBuffers;
224 ALuint StrmSource;
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
237 struct PendingSrc {
238 ALuint src;
239 int sound_id;
240 PendingSrc *next;
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
247 private:
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);
264 private:
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);
271 public:
272 VOpenALDevice ();
273 ~VOpenALDevice ();
275 bool Init();
276 int SetChannels (int);
277 void Shutdown ();
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)
287 , VReverbInfo *Env
288 #endif
291 // all stream functions should be thread-safe
292 bool OpenStream (int Rate, int Bits, int Channels);
293 void CloseStream ();
294 int GetStreamAvailable ();
295 vint16 *GetStreamBuffer ();
296 void SetStreamData (vint16 *data, int len);
297 void SetStreamVolume (float vol);
298 void PauseStream ();
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);
319 #else
320 class VOpenALDevice {};
321 #endif
324 class VAudioCodec;
327 // loader of sound samples
328 class VSampleLoader : public VInterface {
329 public:
330 VSampleLoader *Next;
331 int Priority;
333 static VSampleLoader *List;
335 private:
336 static void InsertIntoList (VSampleLoader *&list, VSampleLoader *codec) noexcept;
338 public:
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 {
352 public:
353 int SampleRate;
354 int SampleBits;
355 int NumChannels;
357 public:
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;
375 CreatorFn Creator;
376 int Priority;
377 FAudioCodecDesc *Next;
379 static FAudioCodecDesc *List;
381 private:
382 static void InsertIntoList (FAudioCodecDesc *&list, FAudioCodecDesc *codec) noexcept;
384 public:
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)
390 , Creator(InCreator)
391 , Priority(prio)
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
412 class VQMus2Mid {
413 private:
414 struct VTrack {
415 vint32 DeltaTime;
416 vuint8 LastEvent;
417 vint8 Vel;
418 TArrayNC<vuint8> Data; // primary data
421 VTrack Tracks[32];
422 vuint16 TrackCnt;
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 &);
437 void FreeTracks ();
439 public:
440 int Run (VStream &, VStream &);
444 // music player (controls streaming thread)
445 class VStreamMusicPlayer {
446 friend class VStreamMusicPlayerWorker;
447 private:
448 // stream player is using a separate thread
449 mythread stpThread;
450 mythread_mutex stpPingLock;
451 mythread_cond stpPingCond;
452 mythread_mutex stpLockPong;
453 mythread_cond stpCondPong;
454 float lastVolume;
455 bool threadInited;
456 char namebuf[1024];
458 private:
459 //WARNING! never use/access those directly!
460 bool StrmOpened;
461 VAudioCodec *Codec;
462 // current playing song info
463 bool CurrLoop; // access with data locked
464 VStr CurrSong; // access with data locked
465 bool Stopping;
466 bool Paused;
467 double FinishTime;
468 VOpenALDevice *SoundDevice; // `nullptr` means "shutdown called"
470 private:
471 atomic_int loopCounter;
472 atomic_int dataSpinlock;
474 // streamer thread ping/pong bussiness
475 enum STPCommand {
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
484 STP_SetVolume,
485 // the following two commands will replace current music with the new one
486 // music name is in `namebuf`
487 STP_PlaySong,
488 STP_PlaySongLooped,
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);
509 struct DataLocker {
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;
517 public:
518 inline VStreamMusicPlayer (VOpenALDevice *InSoundDevice)
519 : lastVolume(1.0f)
520 , threadInited(false)
521 , StrmOpened(false)
522 , Codec(nullptr)
523 , CurrLoop(false)
524 , Stopping(false)
525 , Paused(false)
526 , SoundDevice(InSoundDevice)
527 , loopCounter(0)
528 , dataSpinlock(0)
529 , stpIsPlaying(false)
530 , stpNewPitch(1.0f)
531 , stpNewVolume(1.0f)
534 inline ~VStreamMusicPlayer () { Shutdown(); }
536 void Init ();
537 void Shutdown ();
538 void Play (VAudioCodec *InCodec, const char *InName, bool InLoop);
539 void Pause ();
540 void Resume ();
541 void Stop ();
542 void Restart ();
543 bool IsPlaying ();
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;
561 private:
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"
576 #pragma pack(1)
578 struct FMusHeader {
579 char ID[4]; // identifier "MUS" 0x1A
580 vuint16 ScoreSize;
581 vuint16 ScoreStart;
582 vuint16 NumChannels; // count of primary channels
583 vuint16 NumSecChannels; // count of secondary channels (?)
584 vuint16 InstrumentCount;
585 vuint16 Dummy;
588 struct MIDheader {
589 char ID[4];
590 vuint32 hdr_size;
591 vuint16 type;
592 vuint16 num_tracks;
593 vuint16 divisions;
596 #pragma pack()
599 // ////////////////////////////////////////////////////////////////////////// //
600 // SMF parser
601 class MIDIData {
602 public:
603 enum { MIDI_MAX_CHANNEL = 16, };
605 enum /*event type*/ {
606 MIDI_NOOP = 0, // special synthesized event type
607 // channel messages
608 NOTE_OFF = 0x80,
609 NOTE_ON = 0x90,
610 KEY_PRESSURE = 0xa0,
611 CONTROL_CHANGE = 0xb0,
612 PROGRAM_CHANGE = 0xc0,
613 CHANNEL_PRESSURE = 0xd0,
614 PITCH_BEND = 0xe0,
615 // system exclusive
616 MIDI_SYSEX = 0xf0,
617 MIDI_EOX = 0xf7,
618 // meta event
619 MIDI_META_EVENT = 0xff,
622 enum /*metaevent*/ {
623 MIDI_SEQ_NUM = 0x00,
624 MIDI_TEXT = 0x01,
625 MIDI_COPYRIGHT = 0x02,
626 MIDI_TRACK_NAME = 0x03,
627 MIDI_INST_NAME = 0x04,
628 MIDI_LYRIC = 0x05,
629 MIDI_MARKER = 0x06,
630 MIDI_CUE_POINT = 0x07,
631 MIDI_CHANNEL = 0x20, // channel for the following meta
632 MIDI_EOT = 0x2f,
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,
640 struct MidiTrack {
641 private:
642 vint32 datasize;
643 const vuint8 *tkdata;
644 const vuint8 *pos;
645 MIDIData *song;
646 double nextetime; // milliseconds
647 // after last track command, wait a little
648 double fadeoff;
650 public:
651 vuint8 runningStatus;
652 vuint8 lastMetaChannel; // 0xff: all
653 VStr copyright; // track copyright
654 VStr tname; // track name
655 VStr iname; // instrument name
657 public:
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;
663 datasize = alen;
664 tkdata = adata;
665 song = asong;
666 reset();
669 inline void abort (bool full) { if (tkdata) pos = tkdata+datasize; if (full) fadeoff = 0; }
671 inline void reset () {
672 pos = tkdata;
673 nextetime = 0;
674 fadeoff = (song->type != 2 ? 100 : 0);
675 runningStatus = 0;
676 lastMetaChannel = 0xff;
677 copyright.clear();
678 tname.clear();
679 iname.clear();
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;
694 if (ofs >= 0) {
695 const int left = getLeft();
696 if (ofs >= left) return 0;
697 return pos[ofs];
698 } else {
699 if (ofs == MIN_VINT32) return 0;
700 ofs = -ofs;
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;
712 return *pos;
715 inline vuint8 getNextMidiByte () {
716 if (isEndOfData()) return 0;
717 return *pos++;
720 inline void skipNextMidiBytes (int len) {
721 while (len-- > 0) (void)getNextMidiByte();
724 // reads a variable-length SMF number
725 vuint32 readVarLen () {
726 vuint32 res = 0;
727 int left = 4;
728 for (;;) {
729 if (left == 0) { abort(true); return 0; }
730 --left;
731 vuint8 t = getNextMidiByte();
732 res = (res<<7)|(t&0x7fu);
733 if ((t&0x80) == 0) break;
735 return res;
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);
748 public:
749 struct MidiEvent {
750 vuint8 type = MIDI_NOOP;
751 vuint8 channel = 0;
752 vuint32 data1 = 0; // payload size for MIDI_SYSEX and MIDI_SEQUENCER_EVENT
753 vuint32 data2 = 0;
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);
762 protected:
763 // midi song info
764 TArray<MidiTrack> tracks;
765 vuint16 type;
766 vuint16 delta;
767 vuint32 tempo;
768 double currtime; // milliseconds
769 int currtrack; // for midi type 2
770 // loaded MIDI data
771 vuint8 *midiData;
772 int dataSize;
774 protected:
775 inline void setTempo (vint32 atempo) {
776 if (atempo <= 0) atempo = 480000;
777 tempo = atempo;
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); }
783 bool parseMem ();
785 bool runTrack (int tidx, EventCBType cb, void *udata);
787 public:
788 VV_DISABLE_COPY(MIDIData)
789 MIDIData ();
790 ~MIDIData ();
792 inline bool isValid () const { return (type != 0xff && delta != 0 && tempo != 0); }
793 inline bool isEmpty () const { return (!isValid() || tracks.length() == 0); }
795 void clear ();
797 static bool isMidiStream (VStream &strm);
798 bool parseStream (VStream &strm);
800 public:
801 // <0: error
802 // 0: done decoding
803 // >0: number of frames one can generate until next event
804 int decodeStep (EventCBType cb, int SampleRate, void *udata);
806 bool isFinished ();
808 void restart ();
809 void abort ();
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`
828 #endif