spacebox/src/Audio.hpp

214 lines
7.5 KiB
C++

/* +-------------------------------------------------------+
____/ \____ /| 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 <regex>
#include <map>
#include <functional>
#include <algorithm>
#include <cmath>
#include <optional>
#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<Mix_Chunk> 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<bool> 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<Mix_Music> music;
public:
Music() = default;
Music(const fs::path& path);
void load(const fs::path& path);
void play(int loops = -1);
};
};