Advertisement
Kitomas

sfx.hpp 2023-12-08

Dec 8th, 2023
607
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 10.20 KB | None | 0 0
  1. //todo: make device fade within 10ms
  2. //note: lock sfx when playing sound effect
  3. #pragma once
  4.  
  5. #include <SDL2/SDL.h>
  6.  
  7. #include <algorithm>
  8. #include <string>
  9. #include <vector>
  10.  
  11.  
  12. #define DEFAULT_SAMPLERATE (44100)
  13. #define DEFAULT_BITRATE    (DEFAULT_SAMPLERATE*sizeof(sfx_f32s)*8)
  14. #define DEFAULT_BUFFERLEN (4096)
  15.  
  16.  
  17.  
  18.  
  19. /*******/
  20. /* sfx */
  21. /*******/
  22.  
  23. extern const float _sfx_i8inv;  // = 1/2^7
  24. extern const float _sfx_i16inv; // = 1/2^15
  25.  
  26. //for converting i8/i16 to f32
  27. #define _sfx_i8conv(_v)  ( (float)((Sint8)((_v)-128)) * _sfx_i8inv  )
  28. #define _sfx_i16conv(_v) ( (float)(        (_v)     ) * _sfx_i16inv )
  29.  
  30.  
  31.  
  32. struct sfx_u8s  {
  33.   Uint8  l,r;
  34.   sfx_u8s() : l(0), r(0) {}
  35.   sfx_u8s(const Uint8 _l, const Uint8 _r) : l(_l), r(_r) {}
  36. };
  37.  
  38. struct sfx_i16s {
  39.   Sint16 l,r;
  40.   sfx_i16s() : l(0), r(0) {}
  41.   sfx_i16s(const Sint16 _l, const Sint16 _r) : l(_l), r(_r) {}
  42. };
  43.  
  44. struct sfx_f32s {
  45.   float l,r;
  46.   sfx_f32s() : l(0), r(0) {}
  47.   sfx_f32s(const float& _l, const float& _r) : l(_l), r(_r) {}
  48.  
  49.   sfx_f32s(const Uint8&  smp){ l = _sfx_i8conv( smp);  r = _sfx_i8conv( smp); }
  50.   sfx_f32s(const Sint16& smp){ l = _sfx_i16conv(smp);  r = _sfx_i16conv(smp); }
  51.   sfx_f32s(const float& smp){ l = smp;  r = smp; }
  52.  
  53.   sfx_f32s(const sfx_u8s&  smp){ l = _sfx_i8conv( smp.l);  r = _sfx_i8conv( smp.r); }
  54.   sfx_f32s(const sfx_i16s& smp){ l = _sfx_i16conv(smp.l);  r = _sfx_i16conv(smp.r); }
  55.   sfx_f32s(const sfx_f32s& smp){ l = smp.l;  r = smp.r; }
  56.  
  57.  
  58.   sfx_f32s& operator+=(const sfx_f32s& smp){ l += smp.l;  r += smp.r;  return *this; }
  59.   sfx_f32s& operator-=(const sfx_f32s& smp){ l -= smp.l;  r -= smp.r;  return *this; }
  60.   sfx_f32s& operator*=(const sfx_f32s& smp){ l *= smp.l;  r *= smp.r;  return *this; }
  61.  
  62.   sfx_f32s& clip(){ l = std::clamp(l, -1.0f,1.0f);  r = std::clamp(r, -1.0f,1.0f);  return *this; }
  63.   sfx_f32s& unit(){ l = std::clamp(l,  0.0f,1.0f);  r = std::clamp(r,  0.0f,1.0f);  return *this; }
  64. };
  65.  
  66.  
  67.  
  68.  
  69. /*************/
  70. /* sfx_class */
  71. /*************/
  72.  
  73. struct sfx_pcm; //forward declaration
  74.  
  75. struct sfx_track { //64B
  76.   sfx_pcm*         pcm = nullptr; //audio data; track considered available/inactive if nullptr
  77.   Uint64     timeStamp; //result of SDL_GetTicks64() called at time of queueing an audio clip
  78.  
  79.   double      position; //sample position, including fraction
  80.   double         speed; //what amount to increase position by every sample
  81.   double    speedDelta; //what number to apply to speed every sample
  82.  
  83.   sfx_f32s      volume; //left and right channel volumes; 0.0f -> 1.0f
  84.   sfx_f32s volumeDelta; //determines the rate at which volume changes each sample
  85.   float            pan; //the current pan of the track; -1.0f -> 1.0f (should be applied AFTER volume)
  86.  
  87.   Uint16         loops; //number of times to loop before deactivating clip (-1 for endless loop)
  88.   bool      stopOnMute = true; //'deactivate track when volume or speed reaches 0?'
  89.   bool        stopping = false; //used inside _sfx_mixTrack
  90. };
  91.  
  92.  
  93.  
  94. class sfx_class { //104B
  95.   bool                 _valid = false;
  96.   bool               _closing = false;
  97.   bool               _fadeOut = false;
  98.   bool               _playing = false;
  99.   SDL_AudioDeviceID _deviceID = 0;
  100.  
  101.   Uint64 _timeStampStart = 0;
  102.   Uint64   _timeStampEnd = 0;
  103.  
  104.   std::vector<sfx_track>* _tracks = nullptr;
  105.   SDL_mutex*                _lock = nullptr;
  106.  
  107.   sfx_f32s    _volume = 1.0f;
  108.   float          _pan = 1.0f;
  109.  
  110.   Uint32 _fadeInDelay = 0;
  111.   float  _fadeDelta = 0;
  112.   float _fadeVolume = 0;
  113.  
  114.   Uint32      _sampleRate = DEFAULT_SAMPLERATE;
  115.   Uint32    _bufferLength = DEFAULT_BUFFERLEN;
  116.   std::string _deviceName;
  117.  
  118.  
  119. public:
  120.   bool isValid() const { return _valid; }
  121.   bool isClosing() const { return _closing; }
  122.   bool isFadingOut() const { return _fadeOut; }
  123.   bool isPlaying() const { return _playing; }
  124.   SDL_AudioDeviceID getDeviceID() const { return _deviceID; }
  125.   Uint64 getTimeStampStart() const { return _timeStampStart; }
  126.   Uint64 getTimeStampEnd() const { return _timeStampEnd; }
  127.   size_t getNumTracks() const { return _tracks->size(); }
  128.   sfx_f32s getVolume() const { return _volume; }
  129.   sfx_f32s getPan() const { return _pan; }
  130.   Uint32 getSampleRate() const { return _sampleRate; }
  131.   Uint32 getBufferLength() const { return _bufferLength; }
  132.   std::string& getDeviceName(){ return _deviceName; }
  133.  
  134.   Uint32 getActiveTracks(){
  135.     if(!_valid) throw "invalid sfx_class instance";
  136.     lock(true);
  137.     if(_closing) return 0;
  138.  
  139.     Uint32 amount = 0;
  140.     std::vector<sfx_track>& tracksRef = *_tracks;
  141.     size_t numTracks = tracksRef.size();
  142.  
  143.     for(size_t i=0; i<numTracks; ++i)
  144.       if(tracksRef.at(i).pcm != nullptr) ++amount;
  145.  
  146.     lock(false);
  147.     return amount;
  148.   }
  149.  
  150.   void setVolume(sfx_f32s& volume){
  151.     if(!_valid) throw "invalid sfx_class instance";
  152.     _volume = volume;
  153.   }
  154.   void setPan(float pan){
  155.     if(!_valid) throw "invalid sfx_class instance";
  156.     _pan = pan;
  157.   }
  158.  
  159.   //should be used solely by _sfx_callback related stuff
  160.    //(these are hardly public functions; do not touch these, seriously)
  161.   Uint32& _getFadeInDelay(){ return _fadeInDelay; }
  162.   float& _getFadeDelta(){ return _fadeDelta; }
  163.   float& _getFadeVolume(){ return _fadeVolume; }
  164.   std::vector<sfx_track>& _getTracks(){ return *_tracks; }
  165.   void _setPlaying(bool playState){ _playing = playState; }
  166.   void _setTimeStampStart(){ _timeStampStart = SDL_GetTicks64(); }
  167.   void _setTimeStampEnd(){ _timeStampEnd = SDL_GetTicks64(); }
  168.  
  169.  
  170.   sfx_class(const Uint32 numTracks,
  171.             const int sampleRate = DEFAULT_SAMPLERATE,
  172.             const std::string& deviceName = "",
  173.             const Uint32 bufferLength = DEFAULT_BUFFERLEN);
  174.  
  175.   ~sfx_class(){
  176.     lock(true);
  177.     _closing = true;
  178.     _fadeOut = true; //probably redundant
  179.  
  180.     if(_deviceID > 0){
  181.       SDL_CloseAudioDevice(_deviceID);
  182.       _playing = false;
  183.       _deviceID = 0;
  184.     }
  185.  
  186.     delete _tracks;
  187.     _tracks = nullptr;
  188.     lock(false);
  189.     _valid = false;
  190.  
  191.     if(_lock != nullptr){
  192.       SDL_DestroyMutex(_lock);
  193.       _lock = nullptr;
  194.     }
  195.   }
  196.  
  197.  
  198.   void lock(bool lockState){
  199.     if(!_valid) throw "invalid sfx_class instance";
  200.     int failure;
  201.     if(lockState) failure = (SDL_LockMutex(_lock)<0);
  202.     else          failure = (SDL_UnlockMutex(_lock)<0);
  203.     if(failure) throw SDL_GetError();
  204.   }
  205.  
  206.   void lockDevice(bool lockState){
  207.     if(!_valid) throw "invalid sfx_class instance";
  208.     if(lockState) SDL_LockAudioDevice(_deviceID);
  209.     else          SDL_UnlockAudioDevice(_deviceID);
  210.   }
  211.  
  212.  
  213.   void pauseDevice(bool pauseState);
  214.   void pauseDeviceAndWait(bool pauseState);
  215.  
  216.  
  217.   int play(const sfx_pcm* pcm);
  218. };
  219.  
  220.  
  221.  
  222.  
  223. /***********/
  224. /* sfx_pcm */
  225. /***********/
  226.  
  227. #define SFX_PCM_MAGIC (0x4D43506B)
  228. struct sfx_pcm { //an altered (but backwards compatible) version of kit_acodecPCM; 72B
  229.   Uint32                   magic = 0;                  // (0x00) = 0x4D43506B = "kPCM" (no terminator)
  230.   SDL_AudioFormat         format = AUDIO_F32;          // (0x04) The data format of the stream
  231.   Uint16              headerSize = sizeof(sfx_pcm);    // (0x06) = sizeof(sfx_pcm)
  232.   Uint64                dataSize = 0;                  // (0x08) The size of the PCM buffer, in bytes
  233.  
  234.   Uint64               loopStart = 0;                  // (0x10) Which sample to loop back to
  235.   Uint64                 loopEnd = 0;                  // (0x18) Which sample to restart the loop on
  236.  
  237.   Uint64              numSamples = 0;                  // (0x20) # of sample frames in stream
  238.   Uint32              sampleRate = DEFAULT_SAMPLERATE; // (0x28) The stream's sample rate, in Hz
  239.   Uint32                 bitRate = DEFAULT_BITRATE;    // (0x2C) The audio's bit rate (per second)
  240.  
  241.   Uint16               loopCount = 0;                  // (0x30) # of times to loop audio (-1 = inf loop)
  242.   Uint16                channels = 2;                  // (0x32) # of interlaced channels in the stream
  243.   Uint8             bitRemainder = 0;                  // (0x34) = bitsPerSample%8
  244.   Uint8                userflags = 0;                  // (0x35) User-defined (is just padding otherwise)
  245.   Uint16               uservalue = 0;                  // (0x36) User-defined (is just padding otherwise)
  246.   std::vector<sfx_f32s>* samples = nullptr;            // (0x38) Sample data (appears as nullptr in file)
  247.   sfx_class*                 sfx = nullptr;            // (0x40) Bound sfx class (appears as nullptr in file)
  248.  
  249.   // Samples will be converted to f32s at deviceSampleRate Hz
  250.   sfx_pcm(const std::string& filePath, const sfx_class* sfx_ptr = nullptr);
  251.  
  252.   ~sfx_pcm(){
  253.     magic = 0; //a magic of 0 indicates an invalid pcm struct
  254.     delete samples;
  255.     samples = nullptr; //just in case
  256.   }
  257.  
  258.  
  259.   void print(const size_t samplesToPrint = 0){
  260.     SDL_Log("magic        = \"%.4s\" (0x%08X)", (char*)&magic, magic);
  261.     switch(format){
  262.     case AUDIO_U8 : SDL_Log("format       = AUDIO_U8 (0x%04X)",  format); break;
  263.     case AUDIO_S16: SDL_Log("format       = AUDIO_S16 (0x%04X)", format); break;
  264.     case AUDIO_F32: SDL_Log("format       = AUDIO_F32 (0x%04X)", format); break;
  265.     default:        SDL_Log("format       = UNKNOWN (0x%04X)",   format); }
  266.     SDL_Log("headerSize   = %u", headerSize);
  267.     SDL_Log("dataSize     = %u", (unsigned)dataSize); //overflows if dataSize > 4GB
  268.  
  269.     SDL_Log("loopStart    = %u", (unsigned)loopStart);
  270.     SDL_Log("loopEnd      = %u", (unsigned)loopEnd);
  271.  
  272.     SDL_Log("numSamples   = %u", (unsigned)numSamples);
  273.     SDL_Log("sampleRate   = %u", sampleRate);
  274.     SDL_Log("bitrate      = %u", bitRate);
  275.  
  276.     SDL_Log("loopCount    = %u", loopCount);
  277.     SDL_Log("channels     = %u", channels);
  278.     SDL_Log("bitRemainder = %u", bitRemainder);
  279.     SDL_Log("userflags    = 0x%02X", userflags);
  280.     SDL_Log("uservalue    = %u", uservalue);
  281.  
  282.     if(samplesToPrint > 0){
  283.       if(format!=AUDIO_F32 || channels!=2) throw "data type is not f32s";
  284.       Uint64 sampleRange = std::min(samplesToPrint, numSamples);
  285.       std::vector<sfx_f32s>& samplesRef = *samples;
  286.  
  287.       for(Uint32 i=0; i<sampleRange; ++i)
  288.         SDL_Log("%4u: %7.4f, %7.4f", i, samplesRef.at(i).l,samplesRef.at(i).r);
  289.     }
  290.   }
  291.  
  292.   void setSfx(sfx_class* sfx_ptr){ sfx = sfx_ptr; }
  293.  
  294.  
  295.   int play() {
  296.     if(sfx != nullptr) return sfx->play(this);
  297.     else throw "no sfx_class instance currently bound";
  298.   }
  299. };
  300.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement