Kitomas

sfx_class.cpp as of 2023-12-15

Dec 15th, 2023
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 8.64 KB | None | 0 0
  1. #include "_sfx.hpp"
  2.  
  3. char const _sfx_err_invalid[] = "invalid sfx_class instance";
  4.  
  5.  
  6.  
  7.  
  8. static inline int numBitsU32(Uint32 n){
  9.     int active = 0;
  10.     while(n != 0){
  11.         if(n&1) ++active;
  12.         n >>= 1;
  13.     }
  14.     return active;
  15. }
  16.  
  17.  
  18. extern void _sfx_callback(void* userdata, Uint8* _stream, int size);
  19.  
  20.  
  21. sfx_class::sfx_class(const Uint32 numTracks,
  22.                      const int sampleRate,
  23.                      const std::string& deviceName,
  24.                      const Uint32 bufferLength)
  25. {
  26.   if(numTracks == 0) throw "numTracks = 0";
  27.   if(numTracks > 1024) throw "numTracks > 1024";
  28.   if(sampleRate < 1000) throw "sampleRate < 1000";
  29.   if(sampleRate > 384000) throw "sampleRate > 384000";
  30.   if(bufferLength < 32) throw "bufferLength < 32";
  31.   if(bufferLength > 65535) throw "bufferLength > 65535";
  32.   if(numBitsU32(bufferLength) != 1) throw "bufferLength is not a power of 2";
  33.   const char* deviceNameRaw = nullptr;
  34.   if(deviceName != "") deviceNameRaw = deviceName.c_str();
  35.  
  36.   SDL_AudioSpec specWant, specHave;
  37.   specWant.freq     = sampleRate;
  38.   specWant.format   = AUDIO_F32;
  39.   specWant.channels = 2;
  40.   specWant.samples  = bufferLength;
  41.   specWant.callback = _sfx_callback;
  42.   specWant.userdata = this;
  43.  
  44.  
  45.   _deviceID = SDL_OpenAudioDevice(deviceNameRaw,0,&specWant,&specHave,0);
  46.   if(_deviceID == 0) throw SDL_GetError();
  47.  
  48.   _tracks = new std::vector<sfx_track>(numTracks);
  49.   if(_tracks == nullptr){
  50.     SDL_CloseAudioDevice(_deviceID);
  51.     throw "failed to make tracks vector";
  52.   }
  53.  
  54.   _lock = SDL_CreateMutex();
  55.   if(_lock == nullptr){
  56.     SDL_CloseAudioDevice(_deviceID);
  57.     delete _tracks;
  58.     throw SDL_GetError();
  59.   }
  60.  
  61.   _volume       = 1.0f;
  62.   _pan          = 0.0f;
  63.   _fadeDelta    = 1.0f / ( ((float)sampleRate)*_fadeDeltaSeconds );
  64.   _fadeVolume   = 0.0f;
  65.   _sampleRate   = sampleRate;
  66.   _bufferLength = bufferLength;
  67.   _deviceName   = SDL_GetAudioDeviceName(_deviceID,0);
  68.  
  69.   _valid = true; //set valid to true to indicate success
  70. }
  71.  
  72.  
  73.  
  74. sfx_class::~sfx_class(){
  75.   lock(true);
  76.   _closing = true;
  77.   _fadeOut = true; //probably redundant
  78.  
  79.   if(_deviceID > 0){
  80.     SDL_CloseAudioDevice(_deviceID);
  81.     _playing = false;
  82.     _deviceID = 0;
  83.   }
  84.  
  85.   delete _auxStream;
  86.   _auxStream = nullptr;
  87.  
  88.   delete _tracks;
  89.   _tracks = nullptr;
  90.  
  91.   lock(false); //will throw if _valid is set to false before this
  92.   _valid = false;
  93.  
  94.   if(_lock != nullptr){
  95.     SDL_mutex* lock = _lock;
  96.     _lock = nullptr;
  97.     SDL_DestroyMutex(lock);
  98.   }
  99. }
  100.  
  101.  
  102.  
  103. void sfx_class::setVolume(sfx_f32s volume){
  104.   if(!_valid) throw _sfx_err_invalid;
  105.   _volume = volume.unit();
  106. }
  107.  
  108.  
  109.  
  110. void sfx_class::setPan(float pan){
  111.   if(!_valid) throw _sfx_err_invalid;
  112.   _pan = std::clamp(pan, -1.0f,1.0f);
  113. }
  114.  
  115.  
  116.  
  117.  
  118. void sfx_class::lock(bool lockState){
  119.   if(!_valid) throw _sfx_err_invalid;
  120.   int failure;
  121.   if(lockState) failure = (SDL_LockMutex(_lock)<0);
  122.   else          failure = (SDL_UnlockMutex(_lock)<0);
  123.   if(failure) throw SDL_GetError();
  124. }
  125.  
  126.  
  127.  
  128. void sfx_class::lockDevice(bool lockState){
  129.   if(!_valid) throw _sfx_err_invalid;
  130.   if(lockState) SDL_LockAudioDevice(_deviceID);
  131.   else          SDL_UnlockAudioDevice(_deviceID);
  132. }
  133.  
  134.  
  135.  
  136. void sfx_class::pauseDevice(bool pauseState){
  137.   if(!_valid) throw _sfx_err_invalid;
  138.   if(_closing) return; //return early if closing
  139.   lock(true);
  140.  
  141.   //this should occur when _sfx_callback's pause thread fails,
  142.    //or if the callback itself does
  143.   if(_fadeInDelay == 0xffffffff){ //if equal to -1
  144.     SDL_PauseAudioDevice(_deviceID,1);
  145.     _playing = false;
  146.     _fadeInDelay = 0;
  147.   }
  148.  
  149.   _fadeOut = pauseState;
  150.   if(!pauseState && !_playing){
  151.     //the purpose of fadeInDelay is to mute for some samples
  152.      //to give the sdl audio device some time to warm up,
  153.      //otherwise artifacts start to occur (for me, anyway)
  154.     _fadeInDelay = _sampleRate*_fadeInDelaySeconds;
  155.     if(!_timeStampStart) _timeStampStart = SDL_GetTicks64();
  156.     if(!_timeStampEnd  ) _timeStampEnd   = SDL_GetTicks64();
  157.     SDL_PauseAudioDevice(_deviceID,0);
  158.     _playing = true;
  159.   }
  160.  
  161.   lock(false);
  162. }
  163.  
  164.  
  165.  
  166. void sfx_class::pauseDeviceAndWait(bool pauseState){
  167.   Uint64 startTimeMS = SDL_GetTicks64();
  168.   bool wasPaused = !_playing;
  169.   pauseDevice(pauseState); //should throw if sfx is invalid
  170.   if(_closing) return;
  171.  
  172.   if(pauseState){
  173.     int timeoutCount = 0;
  174.     do {
  175.       SDL_Delay(_fadeDeltaSeconds*1000);
  176.       timeoutCount += _fadeDeltaSeconds*1000;
  177.       if(timeoutCount >= 2000) throw "device pause timed out";
  178.     } while(_playing);
  179.  
  180.   } else if(wasPaused){ //delay only if previously paused
  181.     Uint32 totalDelayMS = _totalFadeDelay*1000;
  182.     Uint32 timeDifferenceMS = SDL_GetTicks64()-startTimeMS;
  183.     SDL_Delay( std::max(totalDelayMS-timeDifferenceMS,0u)+1 ); //+1 just in case
  184.  
  185.   }
  186.  
  187. }
  188.  
  189.  
  190.  
  191.  
  192. void sfx_class::waitForTracks(Uint64 timeoutMS){
  193.   Uint64 timeStamp = SDL_GetTicks64();
  194.   if(timeoutMS == 0) timeoutMS = 0xffffffffffffffff; //wait forever if 0
  195.   while(getNumActiveTracks()){
  196.     SDL_Delay(10);
  197.     if((SDL_GetTicks64()-timeStamp)>timeoutMS) throw "waitForTracks() timed out!";
  198.   }
  199. }
  200.  
  201.  
  202.  
  203. void sfx_class::waitForTrack(int track, Uint64 timeoutMS){
  204.   Uint64 timeStamp = SDL_GetTicks64();
  205.   if(timeoutMS == 0) timeoutMS = 0xffffffffffffffff; //wait forever if 0
  206.   while(isTrackPlaying(track)){
  207.     SDL_Delay(10);
  208.     if((SDL_GetTicks64()-timeStamp)>timeoutMS) throw "waitForTracks() timed out!";
  209.   }
  210. }
  211.  
  212.  
  213.  
  214. int sfx_class::play(const sfx_pcm* pcm,
  215.                     sfx_f32s volume,
  216.                     double speed,
  217.                     float pan)
  218. {
  219.   Uint64 timeStamp = SDL_GetTicks64();
  220.   if(!_valid) throw _sfx_err_invalid;
  221.   if(pcm == nullptr) throw "pcm = nullptr";
  222.   if(pcm->magic != SFX_PCM_MAGIC) throw "pcm is invalid";
  223.   lock(true);
  224.  
  225.   //will return -1 if no available track was found
  226.   int queuedTrackID = -1;
  227.   std::vector<sfx_track>& tracksRef = *_tracks;
  228.   size_t numTracks = tracksRef.size();
  229.  
  230.  
  231.   for(size_t i=0; i<numTracks; ++i){
  232.     sfx_track& track = tracksRef.at(i);
  233.  
  234.     if(track.pcm == nullptr){
  235.       track.timeStamp   = timeStamp;
  236.       track.speed       = speed;
  237.       track.speedDelta  = 0.0;
  238.       track.volume      = volume;
  239.       track.volumeDelta = 0.0f;
  240.       track.pan         = pan;
  241.       track.loops       = pcm->loopCount;
  242.       track.stopOnMute  = true;
  243.       track.stopping    = false;
  244.  
  245.       track.pcm         = (sfx_pcm*)pcm;
  246.  
  247.       queuedTrackID = i; break;
  248.     }
  249.   }
  250.  
  251.   lock(false);
  252.   return queuedTrackID;
  253. }
  254.  
  255.  
  256.  
  257. void sfx_class::stop(int track){
  258.   if(!_valid) throw _sfx_err_invalid;
  259.   setTrackVolumeDelta(track,-_fadeDeltaSeconds);
  260. }
  261.  
  262.  
  263.  
  264. void sfx_class::stopAll(){
  265.   if(!_valid) throw _sfx_err_invalid;
  266.   size_t numTracks = _tracks->size();
  267.   for(size_t i=0; i<numTracks; ++i) setTrackVolumeDelta(i,-_fadeDeltaSeconds);
  268. }
  269.  
  270.  
  271.  
  272.  
  273. Uint32 sfx_class::getNumActiveTracks(){
  274.   if(!_valid) throw _sfx_err_invalid;
  275.   lock(true);
  276.   if(_closing) return 0;
  277.   if(_tracks == nullptr) return 0;
  278.  
  279.   Uint32 amount = 0;
  280.   std::vector<sfx_track>& tracksRef = *_tracks;
  281.   size_t numTracks = tracksRef.size();
  282.  
  283.   for(size_t i=0; i<numTracks; ++i)
  284.     if(tracksRef.at(i).pcm != nullptr) ++amount;
  285.  
  286.   lock(false);
  287.   return amount;
  288. }
  289.  
  290.  
  291.  
  292. bool sfx_class::isTrackPlaying(int track){
  293.   if(!_valid) throw _sfx_err_invalid;
  294.   if(track < 0) return false;
  295.   return _tracks->at(track).pcm != nullptr;
  296. }
  297.  
  298.  
  299.  
  300. sfx_track* sfx_class::getTrackPtr(int track){
  301.   if(!_valid) throw _sfx_err_invalid;
  302.   if(track < 0) return nullptr;
  303.   return &_tracks->at(track);
  304. }
  305.  
  306.  
  307.  
  308. void sfx_class::setTrackVolumeDelta(int track, sfx_f32s volumeDeltaSeconds){
  309.   if(!_valid) throw _sfx_err_invalid;
  310.   if(track < 0) return;
  311.   sfx_track* _track = &_tracks->at(track);
  312.   _track->volumeDelta.l = (1.0f/volumeDeltaSeconds.l)/_sampleRate;
  313.   _track->volumeDelta.r = (1.0f/volumeDeltaSeconds.r)/_sampleRate;
  314. }
  315.  
  316.  
  317.  
  318. void sfx_class::setTrackSpeedDelta(int track, double speedDeltaSeconds){
  319.   if(!_valid) throw _sfx_err_invalid;
  320.   if(track < 0) return;
  321.   _tracks->at(track).speedDelta = (1.0f/speedDeltaSeconds)/_sampleRate;
  322. }
  323.  
  324.  
  325.  
  326. void sfx_class::setAux(sfx_auxCallback callback, void* userdata,
  327.                        SDL_AudioFormat format, Uint16 channels)
  328. {
  329.   if(callback == nullptr) throw "callback = nullptr";
  330.   if(_format_invalid(format)) throw "format is invalid";
  331.   if(channels!=1 && channels!=2) throw "channels must be either 1 or 2";
  332.  
  333.   size_t auxStream_len = _bufferLength * (SDL_AUDIO_BITSIZE(format)/8);
  334.   delete _auxStream;
  335.   _auxStream = new std::vector<char>(auxStream_len);
  336.   if(_auxStream == nullptr) throw "failed to create _auxStream";
  337.  
  338.   _auxCallback = callback;
  339.   _auxUserdata = userdata;
  340.   _auxFormat   = format;
  341.   _auxChannels = channels;
  342. }
  343.  
Add Comment
Please, Sign In to add comment