/* +-------------------------------------------------------+ ____/ \____ /| Open source game framework licensed to freely use, | \ / / | copy, and modify, created for dank.game | +--\ ^__^ /--+ | | | ~/ \~ | | Download at https://open.shampoo.ooo/shampoo/spacebox | | ~~~~~~~~~~~~ | +-------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #pragma once #include #include #include #include #include #include #include "SDL.h" #include "SDL_mixer.h" #include "filesystem.hpp" #include "Log.hpp" namespace sb::audio { /*! * @return channel ID of the SDL mixer channel with the loudest volume setting */ int loudest_channel(); /*! * @return volume of the loudest SDL mixer channel, normalized to between 0.0 and 1.0 */ float loudest_channel_volume(); /*! * Convert floating point volume between 0.0 and 1.0 to SDL 8-bit unsigned integer volume between 0 and `MIX_MAX_VOLUME`. * * @param volume Volume between 0.0 and 1.0 * @return Same volume represented in the range 0 to `MIX_MAX_VOLUME` */ std::uint8_t convert_volume(float volume); /*! * Convert volume between 0 and `MIX_MAX_VOLUME` to a float between 0.0 and 1.0. * * @param volume Volume between 0 and `MIX_MAX_VOLUME` * @return Same volume represented as a float between 0.0 and 1.0 */ float normalize_volume(std::uint8_t volume); /*! * Load audio from an OGG or WAV file and play it. * * Each instance contains an SDL `Mix_Chunk` audio data struct and automatically finds a open channel to play it on when play is * requested. The chunk can be playing on multiple channels simultaneously. * * There are some differences between how `Mix_Chunk` and `Mix_Music` can be used with the API, most notably that multiple chunks * can be playing on multiple channels simultaneously, but only one music object can be playing at a time. This is most likely * because `Mix_Music` objects are streamed and `Mix_Chunk` are loaded into memory, but I haven't verified that by looking at SDL's * code. */ class Chunk { private: std::shared_ptr chunk; bool _enabled = true; /* -1 means loop forever, any other value is the number of times to loop */ int loops = 0; public: /*! * Create an empty audio chunk. */ Chunk() = default; /*! * Create an audio chunk and load it with data from an OGG or WAV file at the given path. If the given path does not contain * loadable data, a warning will be printed, and an empty chunk will be created. * * This will call Chunk::load(const fs::path&) automatically. * * @param path path to an OGG or WAV file */ Chunk(const fs::path& path); /*! * Load audio data from an OGG or WAV file at the given path, replacing any existing audio data in this object. If the given * path does not contain loadable data, a warning will be printed, and existing audio data will not be overwritten. * * @param path path to an OGG or WAV file */ void load(const fs::path& path); /*! * @return the volume of the audio chunk in the range 0.0 - 1.0 */ float volume() const; /*! * @param level set the volume of the chunk to a value between 0.0 and 1.0 */ void volume(float level); /*! * Set the volume of all channels currently playing the audio chunk to a given level. This does not modify the volume of the * chunk itself. * * @param level Volume between 0.0 and 1.0 */ void channel_volume(float level); /*! * Set audio to loop forever. */ void loop(); /*! * @param count Number of times audio should loop when played */ void loop(int count); /*! * Play the audio data loaded into this object once on the first available free channel. Optionally fade in playback over a * given amount of time in seconds. If chunk has been disabled using `Chunk::enabled(bool)`, do nothing instead, and return * -1. * * This always plays the audio from the beginning even if the audio is paused. To resume instead, use Chunk::resume(). Any * audio chunks currently paused will be stopped. * * If the audio is playing already, however, it will continue to play on that channel, and it will be played again from the * beginning on another channel. * * @param fade Length of fade in in seconds * @param channel The SDL mixer channel to play the sound on. A value of -1 means choose a channel automatically. * @return The channel assigned to the audio chunk */ int play(float fade = 0.0f, int channel = -1); /*! * Stop playback of this audio chunk on all channels it is playing on. Optionally fade out over the given amount of time in * seconds. * * @param fade length of fade out in seconds */ void stop(float fade = 0.0f); /*! * Pause audio chunk on all channels it is playing on. */ void pause(); /*! * Resume audio chunk on all channels it is playing on. */ void resume(); /*! * @return True if the audio is playing on any channel, false otherwise. Note that paused or fading audio is considered * playing. */ bool playing() const; /*! * @return True if the audio is paused on any channels, false otherwise. */ bool paused() const; /*! * @return True if the audio is fading in or out on any channels, false otherwise. */ bool fading() const; /*! * By default, a chunk object is enabled to play audio. If the chunk is disabled, however, the play function will not * be able to be used. This function can be used both to check the state and to set the state. * * @param state Set to false to disable, true to enable, or omit to check the current state * @return True if button is set to enabled */ bool enabled(std::optional state = std::nullopt); }; /*! * Load audio from an OGG or WAV file and play it on a single channel dedicated to music. Only one music object can be playing * at a time. * * This class interfaces with SDL mixer's API and Mix_Music audio data pointer. * * There are some differences between how Mix_Chunk and Mix_Music can be used with the API, most notably that multiple chunks can * be playing on multiple channels simultaneously, but only one music object can be playing at a time. This is most likely because * Mix_Music objects are streamed and Mix_Chunk are loaded into memory, but I haven't verified that by looking at the internal code. */ class Music { private: std::shared_ptr music; public: Music() = default; Music(const fs::path& path); void load(const fs::path& path); void play(int loops = -1); }; };