VoxelEngine/src/audio/audio.hpp
2025-01-22 08:18:17 +03:00

523 lines
17 KiB
C++

#pragma once
#include <filesystem>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include "typedefs.hpp"
#include "settings.hpp"
namespace fs = std::filesystem;
namespace audio {
/// @brief playing speaker uid
using speakerid_t = int64_t;
/// @brief duration unit is a second
using duration_t = double;
/// @brief not important sounds (steps, random noises)
constexpr inline int PRIORITY_LOW = 0;
/// @brief default sounds priority
constexpr inline int PRIORITY_NORMAL = 5;
/// @brief streams and important sounds
constexpr inline int PRIORITY_HIGH = 10;
class Speaker;
/// @brief Audio speaker states
enum class State { playing, paused, stopped };
/// @brief Mixer channel controls speakers volume and effects
/// There is main channel 'master' and sub-channels like 'regular', 'music',
/// 'ambient'...
class Channel {
/// @brief Channel name
std::string name;
/// @brief Channel volume setting
float volume = 1.0f;
bool paused = false;
public:
Channel(std::string name);
/// @brief Get channel volume
float getVolume() const;
/// @brief Set channel volume
/// @param volume value in range [0.0, 1.0]
void setVolume(float volume);
/// @brief Get channel name
const std::string& getName() const;
inline void setPaused(bool flag) {
if (flag == paused) return;
if (flag) {
pause();
} else {
resume();
}
}
/// @brief Pause all speakers in channel
void pause();
/// @brief Unpause all speakers paused by this channel
void resume();
/// @brief Check if the channel is paused
bool isPaused() const;
};
/// @brief Pulse-code modulation data
struct PCM {
/// @brief May contain 8 bit and 16 bit PCM data
std::vector<char> data;
/// @brief Total number of mono samples (sampleRate per second)
size_t totalSamples;
/// @brief Track channels number (1 - mono, 2 - stereo)
uint8_t channels;
/// @brief Sample bit depth (8 and 16 supported)
/// does not depend on the channels number
uint8_t bitsPerSample;
/// @brief Number of mono samples per second (example: 44100)
uint sampleRate;
/// @brief Audio source is seekable
bool seekable;
PCM(std::vector<char> data,
size_t totalSamples,
uint8_t channels,
uint8_t bitsPerSample,
uint sampleRate,
bool seekable)
: data(std::move(data)),
totalSamples(totalSamples),
channels(channels),
bitsPerSample(bitsPerSample),
sampleRate(sampleRate),
seekable(seekable) {
}
/// @brief Get total audio duration
/// @return duration in seconds
inline duration_t getDuration() const {
return static_cast<duration_t>(totalSamples) /
static_cast<duration_t>(sampleRate);
}
};
/// @brief audio::PCMStream is a data source for audio::Stream
class PCMStream {
public:
virtual ~PCMStream() {};
/// @brief Read samples data to buffer
/// @param buffer destination buffer
/// @param bufferSize destination buffer size
/// @param loop loop stream (seek to start when end reached)
/// @return size of data received
/// (always equals bufferSize if seekable and looped)
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Close stream
virtual void close() = 0;
/// @brief Check if stream is open
virtual bool isOpen() const = 0;
/// @brief Get total samples number if seekable or 0
virtual size_t getTotalSamples() const = 0;
/// @brief Get total audio track duration if seekable or 0.0
virtual duration_t getTotalDuration() const = 0;
/// @brief Get number of audio channels
/// @return 1 if mono, 2 if stereo
virtual uint getChannels() const = 0;
/// @brief Get audio sampling frequency
/// @return number of mono samples per second
virtual uint getSampleRate() const = 0;
/// @brief Get number of bits per mono sample
/// @return 8 or 16
virtual uint getBitsPerSample() const = 0;
/// @brief Check if the stream does support seek feature
virtual bool isSeekable() const = 0;
/// @brief Move playhead to the selected sample number
/// @param position selected sample number
virtual void seek(size_t position) = 0;
/// @brief Value returning by read(...) in case of error
static const size_t ERROR = -1;
};
/// @brief Audio streaming interface
class Stream {
public:
virtual ~Stream() {};
/// @brief Get pcm data source
/// @return PCM stream or nullptr if audio::openStream
/// keepSource argument is set to false
virtual std::shared_ptr<PCMStream> getSource() const = 0;
/// @brief Create new speaker bound to the Stream
/// and having high priority
/// @param loop is stream looped (required for correct buffers preload)
/// @param channel channel index
/// @return speaker id or 0
virtual std::unique_ptr<Speaker> createSpeaker(
bool loop, int channel
) = 0;
/// @brief Unbind previous speaker and bind new speaker to the stream
/// @param speaker speaker id or 0 if all you need is unbind speaker
virtual void bindSpeaker(speakerid_t speaker) = 0;
/// @brief Get id of the bound speaker
/// @return speaker id or 0 if no speaker bound
virtual speakerid_t getSpeaker() const = 0;
/// @brief Update stream state (preload samples if needed)
/// @param delta time elapsed since the last update
virtual void update(double delta) = 0;
/// @brief Get current stream time
virtual duration_t getTime() const = 0;
/// @brief Set playhead to the selected time
/// @param time selected time
virtual void setTime(duration_t time) = 0;
};
/// @brief Sound is an audio asset that supposed to support many
/// simultaneously playing instances (speakers).
/// So it's audio data is stored in memory.
class Sound {
public:
/// @brief Sound variants will be chosen randomly to play
std::vector<std::shared_ptr<Sound>> variants;
virtual ~Sound() {
}
/// @brief Get sound duration
/// @return duration in seconds (>= 0.0)
virtual duration_t getDuration() const = 0;
/// @brief Get sound PCM data
/// @return PCM data or nullptr
virtual std::shared_ptr<PCM> getPCM() const = 0;
/// @brief Create new sound instance
/// @param priority instance priority. High priority instance can
/// take out speaker from low priority instance
/// @param channel channel index
/// @return new speaker with sound bound or nullptr
/// if all speakers are in use
virtual std::unique_ptr<Speaker> newInstance(int priority, int channel)
const = 0;
};
/// @brief Audio source controller interface.
/// @attention Speaker is not supposed to be reused
class Speaker {
public:
virtual ~Speaker() {
}
/// @brief Synchronize the speaker with channel settings
/// @param channel speaker channel
virtual void update(const Channel* channel) = 0;
/// @brief Check speaker channel index
virtual int getChannel() const = 0;
/// @brief Get current speaker state
/// @return speaker state
virtual State getState() const = 0;
/// @brief Get speaker audio gain
/// @return speaker audio gain value
virtual float getVolume() const = 0;
/// @brief Set speaker audio gain (must be positive)
/// @param volume new gain value
virtual void setVolume(float volume) = 0;
/// @brief Get speaker pitch multiplier
/// @return pitch multiplier
virtual float getPitch() const = 0;
/// @brief Set speaker pitch multiplier
/// @param pitch new pitch multiplier (must be positive)
virtual void setPitch(float pitch) = 0;
/// @brief Check if speaker audio is in loop
/// @return true if audio is in loop
virtual bool isLoop() const = 0;
/// @brief Enable/disable audio loop
/// @param loop loop mode
virtual void setLoop(bool loop) = 0;
/// @brief Play, replay or resume audio
virtual void play() = 0;
/// @brief Pause playing audio and keep speaker alive
virtual void pause() = 0;
/// @brief Stop and destroy speaker
virtual void stop() = 0;
/// @brief Get current time position of playing audio
/// @return time position in seconds
virtual duration_t getTime() const = 0;
/// @brief Get playing audio total duration
virtual duration_t getDuration() const = 0;
/// @brief Set playing audio time position
/// @param time time position in seconds
virtual void setTime(duration_t time) = 0;
/// @brief Set speaker 3D position in the world
/// @param pos new position
virtual void setPosition(glm::vec3 pos) = 0;
/// @brief Get speaker 3D position in the world
/// @return position
virtual glm::vec3 getPosition() const = 0;
/// @brief Set speaker movement velocity used for Doppler effect
/// @param vel velocity vector
virtual void setVelocity(glm::vec3 vel) = 0;
/// @brief Get speaker movement velocity used for Doppler effect
/// @return velocity vector
virtual glm::vec3 getVelocity() const = 0;
/// @brief Get speaker priority
/// @return speaker priority value
virtual int getPriority() const = 0;
/// @brief Determines if the position is relative to the listener
/// @param relative true - relative to the listener (default: false)
virtual void setRelative(bool relative) = 0;
/// @brief Determines if the position is relative to the listener
virtual bool isRelative() const = 0;
/// @brief Check if speaker is playing
inline bool isPlaying() const {
return getState() == State::playing;
}
/// @brief Check if speaker is paused
inline bool isPaused() const {
return getState() == State::paused;
}
/// @brief Check if speaker is stopped
inline bool isStopped() const {
return getState() == State::stopped;
}
};
class Backend {
public:
virtual ~Backend() {};
virtual std::unique_ptr<Sound> createSound(
std::shared_ptr<PCM> pcm, bool keepPCM
) = 0;
virtual std::unique_ptr<Stream> openStream(
std::shared_ptr<PCMStream> stream, bool keepSource
) = 0;
virtual void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) = 0;
virtual void update(double delta) = 0;
/// @brief Check if backend is an abstraction that does not internally
/// work with actual audio data or play anything
virtual bool isDummy() const = 0;
};
/// @brief Initialize audio system or use no audio mode
/// @param enabled try to initialize actual audio
void initialize(bool enabled, AudioSettings& settings);
/// @brief Load audio file info and PCM data
/// @param file audio file
/// @param headerOnly read header only
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return PCM audio data
std::unique_ptr<PCM> load_PCM(const fs::path& file, bool headerOnly);
/// @brief Load sound from file
/// @param file audio file path
/// @param keepPCM store PCM data in sound to make it accessible with
/// Sound::getPCM
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return new Sound instance
std::unique_ptr<Sound> load_sound(const fs::path& file, bool keepPCM);
/// @brief Create new sound from PCM data
/// @param pcm PCM data
/// @param keepPCM store PCM data in sound to make it accessible with
/// Sound::getPCM
/// @return new Sound instance
std::unique_ptr<Sound> create_sound(std::shared_ptr<PCM> pcm, bool keepPCM);
/// @brief Open new PCM stream from file
/// @param file audio file path
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return new PCMStream instance
std::unique_ptr<PCMStream> open_PCM_stream(const fs::path& file);
/// @brief Open new audio stream from file
/// @param file audio file path
/// @param keepSource store PCMStream in stream to make it accessible with
/// Stream::getSource
/// @return new Stream instance
std::unique_ptr<Stream> open_stream(const fs::path& file, bool keepSource);
/// @brief Open new audio stream from source
/// @param stream PCM data source
/// @param keepSource store PCMStream in stream to make it accessible with
/// Stream::getSource
/// @return new Stream instance
std::unique_ptr<Stream> open_stream(
std::shared_ptr<PCMStream> stream, bool keepSource
);
/// @brief Configure 3D listener
/// @param position listener position
/// @param velocity listener velocity (used for Doppler effect)
/// @param lookAt point the listener look at
/// @param up camera up vector
void set_listener(
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
);
/// @brief Play 3D sound in the world
/// @param sound target sound
/// @param position sound world position
/// @param relative position speaker relative to listener
/// @param volume sound volume [0.0-1.0]
/// @param pitch sound pitch multiplier [0.0-...]
/// @param loop loop sound
/// @param priority sound priority
/// (PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH)
/// @param channel channel index
/// @return speaker id or 0
speakerid_t play(
Sound* sound,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int priority,
int channel
);
/// @brief Play stream
/// @param stream target stream
/// @param position stream world position
/// @param relative position speaker relative to listener
/// @param volume stream volume [0.0-1.0]
/// @param pitch stream pitch multiplier [0.0-...]
/// @param loop loop stream
/// @param channel channel index
/// @return speaker id or 0
speakerid_t play(
const std::shared_ptr<Stream>& stream,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int channel
);
/// @brief Play stream from file
/// @param file audio file path
/// @param position stream world position
/// @param relative position speaker relative to listener
/// @param volume stream volume [0.0-1.0]
/// @param pitch stream pitch multiplier [0.0-...]
/// @param loop loop stream
/// @param channel channel index
/// @return speaker id or 0
speakerid_t play_stream(
const fs::path& file,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int channel
);
/// @brief Get speaker by id
/// @param id speaker id
/// @return speaker or nullptr
Speaker* get_speaker(speakerid_t id);
/// @brief Create new channel.
/// All non-builtin channels will be destroyed on audio::reset() call
/// @param name channel name
/// @return new channel index
int create_channel(const std::string& name);
/// @brief Get channel index by name
/// @param name channel name
/// @return channel index or -1
int get_channel_index(const std::string& name);
/// @brief Get channel by index. 0 - is master channel
/// @param index channel index
/// @return channel or nullptr
Channel* get_channel(int index);
/// @brief Get channel by name.
/// @param name channel name
/// @return channel or nullptr
Channel* get_channel(const std::string& name);
/// @brief Get stream associated with speaker
/// @param id speaker id
/// @return stream or nullptr
std::shared_ptr<Stream> get_associated_stream(speakerid_t id);
/// @brief Get alive speakers number (including paused)
size_t count_speakers();
/// @brief Get playing streams number (including paused)
size_t count_streams();
/// @brief Update audio streams and sound instanced
/// @param delta time elapsed since the last update (seconds)
void update(double delta);
/// @brief Stop all playing audio in channel, reset channel state
void reset_channel(int channel);
/// @brief Finalize audio system
void close();
};