From 5ada8a311e09cafbc63f51dea6a7ed5cc0dff8a0 Mon Sep 17 00:00:00 2001 From: Razakhel Date: Fri, 10 Jan 2025 20:53:54 +0100 Subject: [PATCH] [Audio/AudioData] Added a type dedicated to just audio data - A Sound can be constructed with and load an AudioData object - The individual members of Sound are replaced with AudioData - Microphone's and WavFormat's functions now return & take AudioData objects instead of Sound ones - Removed Microphone::recoverSound(), now useless - Removed the Internal::SoundAccess type, now unneeded --- examples/audioDemo.cpp | 28 ++++---- include/RaZ/Audio/AudioData.hpp | 30 ++++++++ include/RaZ/Audio/AudioSystem.hpp | 11 --- include/RaZ/Audio/Microphone.hpp | 27 +++----- include/RaZ/Audio/Sound.hpp | 50 +++++--------- include/RaZ/Data/WavFormat.hpp | 14 ++-- include/RaZ/RaZ.hpp | 1 + src/RaZ/Audio/Microphone.cpp | 32 +++------ src/RaZ/Audio/Sound.cpp | 110 ++++++++++++++++-------------- src/RaZ/Audio/SoundEffect.cpp | 2 +- src/RaZ/Audio/SoundEffectSlot.cpp | 2 +- src/RaZ/Data/WavLoad.cpp | 43 ++++++------ src/RaZ/Data/WavSave.cpp | 24 +++---- src/RaZ/Script/LuaAudio.cpp | 42 +++++++----- tests/src/RaZ/Data/WavFormat.cpp | 25 +++---- tests/src/RaZ/Script/LuaAudio.cpp | 14 ++-- 16 files changed, 223 insertions(+), 232 deletions(-) create mode 100644 include/RaZ/Audio/AudioData.hpp diff --git a/examples/audioDemo.cpp b/examples/audioDemo.cpp index 33b9c1e1..af8912e7 100644 --- a/examples/audioDemo.cpp +++ b/examples/audioDemo.cpp @@ -50,6 +50,9 @@ int main() { // Audio // /////////// + static const Raz::AudioData knockAudio = Raz::WavFormat::load(RAZ_ROOT "assets/sounds/knock.wav"); + static const Raz::AudioData waveSeagullsAudio = Raz::WavFormat::load(RAZ_ROOT "assets/sounds/wave_seagulls.wav"); + auto& audio = world.addSystem(); // The Listener entity requires a Transform component @@ -57,7 +60,7 @@ int main() { Raz::Entity& sound = world.addEntity(); auto& soundTrans = sound.addComponent(); - auto& soundComp = sound.addComponent(Raz::WavFormat::load(RAZ_ROOT "assets/sounds/knock.wav")); + auto& soundComp = sound.addComponent(knockAudio); Raz::Microphone microphone(Raz::AudioFormat::MONO_U8, 16000, 1.f); @@ -109,7 +112,6 @@ int main() { listener.setGain(listenerGain); soundComp.init(); - soundComp.load(); soundComp.setRepeat(isRepeating); soundComp.setGain(soundGain); soundComp.setPitch(soundPitch); @@ -162,11 +164,11 @@ int main() { switch (i) { case 0: default: - soundComp = Raz::WavFormat::load(RAZ_ROOT "assets/sounds/knock.wav"); + soundComp.load(knockAudio); break; case 1: - soundComp = Raz::WavFormat::load(RAZ_ROOT "assets/sounds/wave_seagulls.wav"); + soundComp.load(waveSeagullsAudio); break; } @@ -605,7 +607,7 @@ int main() { // Starting application // ////////////////////////// - std::vector captureData; + Raz::AudioData captureData; app.run([&] (const Raz::FrameTimeInfo& timeInfo) { soundTrans.setPosition((moveSource ? Raz::Vec3f(std::sin(timeInfo.globalTime) * 3.f, 0.f, 1.f) : Raz::Vec3f(0.f))); @@ -624,23 +626,23 @@ int main() { constexpr float factorI16 = 1.f / 32767; if (captureBitDepth == 8) { - for (std::size_t i = 0; i < captureData.size(); ++i) { + for (std::size_t i = 0; i < captureData.buffer.size(); ++i) { if (isCaptureStereo) { // Stereo 8 - leftCapturePlot.push(static_cast(captureData[i]) * factorU8 - 1.f); - rightCapturePlot.push(static_cast(captureData[i + 1]) * factorU8 - 1.f); + leftCapturePlot.push(static_cast(captureData.buffer[i]) * factorU8 - 1.f); + rightCapturePlot.push(static_cast(captureData.buffer[i + 1]) * factorU8 - 1.f); ++i; } else { // Mono 8 - monoCapturePlot.push(static_cast(captureData[i]) * factorU8 - 1.f); + monoCapturePlot.push(static_cast(captureData.buffer[i]) * factorU8 - 1.f); } } } else { - for (std::size_t i = 0; i < captureData.size(); i += 2) { + for (std::size_t i = 0; i < captureData.buffer.size(); i += 2) { if (isCaptureStereo) { // Stereo 16 - leftCapturePlot.push(static_cast(static_cast((captureData[i] << 0u) | (captureData[i + 1] << 8u))) * factorI16); - rightCapturePlot.push(static_cast(static_cast((captureData[i + 2] << 0u) | (captureData[i + 3] << 8u))) * factorI16); + leftCapturePlot.push(static_cast(static_cast(captureData.buffer[i] | (captureData.buffer[i + 1] << 8u))) * factorI16); + rightCapturePlot.push(static_cast(static_cast(captureData.buffer[i + 2] | (captureData.buffer[i + 3] << 8u))) * factorI16); i += 2; } else { // Mono 16 - monoCapturePlot.push(static_cast(static_cast((captureData[i] << 0u) | (captureData[i + 1] << 8u))) * factorI16); + monoCapturePlot.push(static_cast(static_cast(captureData.buffer[i] | (captureData.buffer[i + 1] << 8u))) * factorI16); } } } diff --git a/include/RaZ/Audio/AudioData.hpp b/include/RaZ/Audio/AudioData.hpp new file mode 100644 index 00000000..7f9ae73b --- /dev/null +++ b/include/RaZ/Audio/AudioData.hpp @@ -0,0 +1,30 @@ +#pragma once + +#ifndef RAZ_AUDIODATA_HPP +#define RAZ_AUDIODATA_HPP + +#include +#include + +namespace Raz { + +enum class AudioFormat : int { + MONO_U8 = 4352 /* AL_FORMAT_MONO8 */, ///< Mono format on 8 unsigned bits (0 to 255). + STEREO_U8 = 4354 /* AL_FORMAT_STEREO8 */, ///< Stereo format on 8 unsigned bits (0 to 255). + MONO_I16 = 4353 /* AL_FORMAT_MONO16 */, ///< Mono format on 16 signed bits (-32768 to 32767). + STEREO_I16 = 4355 /* AL_FORMAT_STEREO16 */, ///< Stereo format on 16 signed bits (-32768 to 32767). + MONO_F32 = 65552 /* AL_FORMAT_MONO_FLOAT32 */, ///< Mono format on 32 floating-point bits (float). + STEREO_F32 = 65553 /* AL_FORMAT_STEREO_FLOAT32 */, ///< Stereo format on 32 floating-point bits (float). + MONO_F64 = 65554 /* AL_FORMAT_MONO_DOUBLE_EXT */, ///< Mono format on 64 floating-point bits (double). + STEREO_F64 = 65555 /* AL_FORMAT_STEREO_DOUBLE_EXT */ ///< Stereo format on 64 floating-point bits (double). +}; + +struct AudioData { + AudioFormat format {}; + unsigned int frequency {}; + std::vector buffer {}; +}; + +} // namespace Raz + +#endif // RAZ_AUDIODATA_HPP diff --git a/include/RaZ/Audio/AudioSystem.hpp b/include/RaZ/Audio/AudioSystem.hpp index a787d3b7..2fd47983 100644 --- a/include/RaZ/Audio/AudioSystem.hpp +++ b/include/RaZ/Audio/AudioSystem.hpp @@ -9,17 +9,6 @@ namespace Raz { -enum class AudioFormat : int { - MONO_U8 = 4352 /* AL_FORMAT_MONO8 */, ///< Mono format on 8 unsigned bits (0 to 255). - STEREO_U8 = 4354 /* AL_FORMAT_STEREO8 */, ///< Stereo format on 8 unsigned bits (0 to 255). - MONO_I16 = 4353 /* AL_FORMAT_MONO16 */, ///< Mono format on 16 signed bits (-32768 to 32767). - STEREO_I16 = 4355 /* AL_FORMAT_STEREO16 */, ///< Stereo format on 16 signed bits (-32768 to 32767). - MONO_F32 = 65552 /* AL_FORMAT_MONO_FLOAT32 */, ///< Mono format on 32 floating-point bits (float). - STEREO_F32 = 65553 /* AL_FORMAT_STEREO_FLOAT32 */, ///< Stereo format on 32 floating-point bits (float). - MONO_F64 = 65554 /* AL_FORMAT_MONO_DOUBLE_EXT */, ///< Mono format on 64 floating-point bits (double). - STEREO_F64 = 65555 /* AL_FORMAT_STEREO_DOUBLE_EXT */ ///< Stereo format on 64 floating-point bits (double). -}; - class AudioSystem final : public System { public: /// Creates a system handling audio. diff --git a/include/RaZ/Audio/Microphone.hpp b/include/RaZ/Audio/Microphone.hpp index 0c82e02d..32912148 100644 --- a/include/RaZ/Audio/Microphone.hpp +++ b/include/RaZ/Audio/Microphone.hpp @@ -3,11 +3,11 @@ #ifndef RAZ_MICROPHONE_HPP #define RAZ_MICROPHONE_HPP -#include "RaZ/Audio/AudioSystem.hpp" +#include "RaZ/Audio/AudioData.hpp" -namespace Raz { +#include -class Sound; +namespace Raz { class Microphone { public: @@ -45,21 +45,16 @@ class Microphone { /// Recovers the amount of currently captured time. /// \return Available captured duration, in seconds. float recoverAvailableDuration() const noexcept; - /// Recovers captured samples. - /// \note This flushes the recovered captured data; if recovering something, the available sample count right after this call will be less than it was before. - /// \param maxDuration Maximum amount of time to recover, in seconds. Giving a negative value will result in recovering all available samples. - /// \return Captured samples. - std::vector recoverData(float maxDuration = -1.f) const; - /// Recovers captured samples. This overload can be used to avoid reallocating the whole memory range on each call. - /// \note This flushes the recovered captured data; if recovering something, the available sample count right after this call will be less than it was before. - /// \param data Data to be filled with the captured samples. + /// Recovers captured audio data. + /// \note This flushes the captured data; if recovering something, the available sample count right after this call will be less than it was before. /// \param maxDuration Maximum amount of time to recover, in seconds. Giving a negative value will result in recovering all available samples. - void recoverData(std::vector& data, float maxDuration = -1.f) const; - /// Recovers captured samples as a Sound object. - /// \note This flushes the recovered captured data; if recovering something, the available sample count right after this call will be less than it was before. + /// \return Captured audio data. The format & frequency are those of the current audio input device. + AudioData recoverData(float maxDuration = -1.f) const; + /// Recovers captured audio data. This overload can be used to avoid reallocating the whole memory range on each call. + /// \note This flushes the captured data; if recovering something, the available sample count right after this call will be less than it was before. + /// \param data Data to be filled with the captured audio. /// \param maxDuration Maximum amount of time to recover, in seconds. Giving a negative value will result in recovering all available samples. - /// \return Captured sound. - Sound recoverSound(float maxDuration = -1.f) const; + void recoverData(AudioData& data, float maxDuration = -1.f) const; Microphone& operator=(const Microphone&) = delete; Microphone& operator=(Microphone&&) = delete; diff --git a/include/RaZ/Audio/Sound.hpp b/include/RaZ/Audio/Sound.hpp index a6458c26..00b06309 100644 --- a/include/RaZ/Audio/Sound.hpp +++ b/include/RaZ/Audio/Sound.hpp @@ -4,17 +4,13 @@ #define RAZ_SOUND_HPP #include "RaZ/Component.hpp" -#include "RaZ/Audio/AudioSystem.hpp" +#include "RaZ/Audio/AudioData.hpp" #include "RaZ/Data/OwnerValue.hpp" -#include #include -#include namespace Raz { -namespace Internal { class SoundAccess; } - class SoundEffectSlot; template @@ -29,24 +25,25 @@ enum class SoundState : int { }; class Sound final : public Component { - friend Internal::SoundAccess; - friend class Microphone; - public: Sound() { init(); } + explicit Sound(AudioData data) : Sound() { load(std::move(data)); } Sound(const Sound&) = delete; Sound(Sound&&) noexcept = default; - constexpr unsigned int getBufferIndex() const noexcept { return m_buffer; } - constexpr AudioFormat getFormat() const noexcept { return m_format; } - constexpr int getFrequency() const noexcept { return m_frequency; } + constexpr unsigned int getBufferIndex() const noexcept { return m_bufferIndex; } + constexpr const AudioData& getData() const noexcept { return m_data; } - /// Initializes the sound. + /// Initializes the sound. If there is audio data, also loads it into memory. /// \note A Sound must be initialized again after opening an audio device. /// \see AudioSystem::open() void init(); - /// Loads the sound's data into memory. - void load(); + /// Loads the given audio data into memory. + /// @param data Data to be loaded. + void load(AudioData data) { + m_data = std::move(data); + load(); + } /// Sets the sound's pitch multiplier. /// \param pitch Sound's pitch multiplier; must be positive. 1 is the default. void setPitch(float pitch) const noexcept; @@ -128,30 +125,15 @@ class Sound final : public Component { ~Sound() override { destroy(); } private: - OwnerValue::max()> m_buffer {}; - OwnerValue::max()> m_source {}; - - AudioFormat m_format {}; - int m_frequency {}; - std::vector m_data {}; -}; - -namespace Internal { + /// Loads the audio data into memory. + void load(); -/// Class giving direct access to a Sound's private members; useful for file importers. -/// \note This class is not meant to be used in user code. -class SoundAccess { -public: - SoundAccess() = delete; + OwnerValue::max()> m_bufferIndex {}; + OwnerValue::max()> m_sourceIndex {}; - static AudioFormat& getFormat(Sound& sound) { return sound.m_format; } - static int& getFrequency(Sound& sound) { return sound.m_frequency; } - static const std::vector& getData(const Sound& sound) { return sound.m_data; } - static std::vector& getData(Sound& sound) { return sound.m_data; } + AudioData m_data {}; }; -} // namespace Internal - } // namespace Raz #endif // RAZ_SOUND_HPP diff --git a/include/RaZ/Data/WavFormat.hpp b/include/RaZ/Data/WavFormat.hpp index e0bcc63f..b7fdd313 100644 --- a/include/RaZ/Data/WavFormat.hpp +++ b/include/RaZ/Data/WavFormat.hpp @@ -5,20 +5,20 @@ namespace Raz { +struct AudioData; class FilePath; -class Sound; namespace WavFormat { -/// Loads a [WAV](https://en.wikipedia.org/wiki/WAV) audio from a file. +/// Loads audio data from a [WAV](https://en.wikipedia.org/wiki/WAV) file. /// \param filePath File from which to load the audio. -/// \return Imported sound. -Sound load(const FilePath& filePath); +/// \return Imported audio data. +AudioData load(const FilePath& filePath); -/// Saves a sound to a WAV file. +/// Saves audio data to a [WAV](https://en.wikipedia.org/wiki/WAV) file. /// \param filePath File to which to save the sound. -/// \param sound Sound to export data from. -void save(const FilePath& filePath, const Sound& sound); +/// \param data Audio data to export. +void save(const FilePath& filePath, const AudioData& data); } // namespace WavFormat diff --git a/include/RaZ/RaZ.hpp b/include/RaZ/RaZ.hpp index 1507198c..f2ac6d4c 100644 --- a/include/RaZ/RaZ.hpp +++ b/include/RaZ/RaZ.hpp @@ -9,6 +9,7 @@ #include "System.hpp" #include "World.hpp" #include "Animation/Skeleton.hpp" +#include "Audio/AudioData.hpp" #include "Audio/AudioSystem.hpp" #include "Audio/Listener.hpp" #include "Audio/Microphone.hpp" diff --git a/src/RaZ/Audio/Microphone.cpp b/src/RaZ/Audio/Microphone.cpp index 2c9b8617..78f183fb 100644 --- a/src/RaZ/Audio/Microphone.cpp +++ b/src/RaZ/Audio/Microphone.cpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace Raz { @@ -52,7 +53,7 @@ constexpr int recoverFrameSize(AudioFormat format) { break; default: - throw std::invalid_argument("Error: Unhandled audio format"); + throw std::invalid_argument("[Microphone] Unhandled audio format"); } switch (format) { @@ -77,7 +78,7 @@ constexpr int recoverFrameSize(AudioFormat format) { break; default: - throw std::invalid_argument("Error: Unhandled audio format"); + throw std::invalid_argument("[Microphone] Unhandled audio format"); } return channelCount * bitCount / 8; @@ -170,17 +171,19 @@ float Microphone::recoverAvailableDuration() const noexcept { return (static_cast(recoverAvailableSampleCount()) / static_cast(m_frequency)); } -std::vector Microphone::recoverData(float maxDuration) const { - std::vector data; +AudioData Microphone::recoverData(float maxDuration) const { + AudioData data {}; recoverData(data, maxDuration); return data; } -void Microphone::recoverData(std::vector& data, float maxDuration) const { +void Microphone::recoverData(AudioData& data, float maxDuration) const { ZoneScopedN("Microphone::recoverData"); - data.clear(); + data.format = m_format; + data.frequency = m_frequency; + data.buffer.clear(); if (maxDuration == 0.f) return; @@ -193,25 +196,12 @@ void Microphone::recoverData(std::vector& data, float maxDuration) cons if (maxDuration > 0.f) sampleCount = std::min(sampleCount, static_cast(maxDuration * static_cast(m_frequency))); - data.resize(recoverFrameSize(m_format) * sampleCount); + data.buffer.resize(recoverFrameSize(m_format) * sampleCount); - alcCaptureSamples(static_cast(m_device), data.data(), sampleCount); + alcCaptureSamples(static_cast(m_device), data.buffer.data(), sampleCount); checkError(m_device, "Failed to recover captured data"); } -Sound Microphone::recoverSound(float maxDuration) const { - ZoneScopedN("Microphone::recoverSound"); - - Sound sound; - - sound.m_format = m_format; - sound.m_frequency = static_cast(m_frequency); - sound.m_data = recoverData(maxDuration); - sound.load(); - - return sound; -} - void Microphone::destroy() { ZoneScopedN("Microphone::destroy"); diff --git a/src/RaZ/Audio/Sound.cpp b/src/RaZ/Audio/Sound.cpp index f7b24ad5..775961a1 100644 --- a/src/RaZ/Audio/Sound.cpp +++ b/src/RaZ/Audio/Sound.cpp @@ -45,52 +45,32 @@ void Sound::init() { destroy(); Logger::debug("[Sound] Creating buffer..."); - alGenBuffers(1, &m_buffer.get()); + alGenBuffers(1, &m_bufferIndex.get()); checkError("Failed to create a sound buffer"); - Logger::debug("[Sound] Created buffer (ID: " + std::to_string(m_buffer) + ')'); + Logger::debug("[Sound] Created buffer (ID: " + std::to_string(m_bufferIndex) + ')'); Logger::debug("[Sound] Creating source..."); - alGenSources(1, &m_source.get()); + alGenSources(1, &m_sourceIndex.get()); checkError("Failed to create a sound source"); - Logger::debug("[Sound] Created source (ID: " + std::to_string(m_source) + ')'); + Logger::debug("[Sound] Created source (ID: " + std::to_string(m_sourceIndex) + ')'); - Logger::debug("[Sound] Initialized"); -} - -void Sound::load() { - ZoneScopedN("Sound::load"); - - stop(); // Making sure the sound isn't paused or currently playing - alSourcei(m_source, AL_BUFFER, 0); // Detaching the previous buffer (if any) from the source - - if ((m_format == AudioFormat::MONO_F32 || m_format == AudioFormat::STEREO_F32) && !alIsExtensionPresent("AL_EXT_float32")) { - Logger::error("[Sound] Float sound format is not supported by the audio driver."); - return; - } - - if ((m_format == AudioFormat::MONO_F64 || m_format == AudioFormat::STEREO_F64) && !alIsExtensionPresent("AL_EXT_double")) { - Logger::error("[Sound] Double sound format is not supported by the audio driver."); - return; - } - - alBufferData(m_buffer, static_cast(m_format), m_data.data(), static_cast(m_data.size()), m_frequency); - checkError("Failed to send sound information to the buffer"); + if (!m_data.buffer.empty()) + load(); - alSourcei(m_source, AL_BUFFER, static_cast(m_buffer)); - checkError("Failed to map the sound buffer to the source"); + Logger::debug("[Sound] Initialized"); } void Sound::setPitch(float pitch) const noexcept { assert("Error: The source's pitch must be positive." && pitch >= 0.f); - alSourcef(m_source, AL_PITCH, pitch); + alSourcef(m_sourceIndex, AL_PITCH, pitch); checkError("Failed to set the source's pitch"); } float Sound::recoverPitch() const noexcept { float pitch {}; - alGetSourcef(m_source, AL_PITCH, &pitch); + alGetSourcef(m_sourceIndex, AL_PITCH, &pitch); checkError("Failed to recover the source's pitch"); return pitch; @@ -99,14 +79,14 @@ float Sound::recoverPitch() const noexcept { void Sound::setGain(float gain) const noexcept { assert("Error: The source's gain must be positive." && gain >= 0.f); - alSourcef(m_source, AL_GAIN, gain); + alSourcef(m_sourceIndex, AL_GAIN, gain); checkError("Failed to set the source's gain"); } float Sound::recoverGain() const noexcept { float gain {}; - alGetSourcef(m_source, AL_GAIN, &gain); + alGetSourcef(m_sourceIndex, AL_GAIN, &gain); checkError("Failed to recover the source's gain"); return gain; @@ -117,14 +97,14 @@ void Sound::setPosition(const Vec3f& position) const noexcept { } void Sound::setPosition(float x, float y, float z) const noexcept { - alSource3f(m_source, AL_POSITION, x, y, z); + alSource3f(m_sourceIndex, AL_POSITION, x, y, z); checkError("Failed to set the source's position"); } Vec3f Sound::recoverPosition() const noexcept { Vec3f position; - alGetSource3f(m_source, AL_POSITION, &position.x(), &position.y(), &position.z()); + alGetSource3f(m_sourceIndex, AL_POSITION, &position.x(), &position.y(), &position.z()); checkError("Failed to recover the source's position"); return position; @@ -135,14 +115,14 @@ void Sound::setVelocity(const Vec3f& velocity) const noexcept { } void Sound::setVelocity(float x, float y, float z) const noexcept { - alSource3f(m_source, AL_VELOCITY, x, y, z); + alSource3f(m_sourceIndex, AL_VELOCITY, x, y, z); checkError("Failed to set the source's velocity"); } Vec3f Sound::recoverVelocity() const noexcept { Vec3f velocity; - alGetSource3f(m_source, AL_VELOCITY, &velocity.x(), &velocity.y(), &velocity.z()); + alGetSource3f(m_sourceIndex, AL_VELOCITY, &velocity.x(), &velocity.y(), &velocity.z()); checkError("Failed to recover the source's velocity"); return velocity; @@ -150,18 +130,18 @@ Vec3f Sound::recoverVelocity() const noexcept { #if !defined(RAZ_PLATFORM_EMSCRIPTEN) void Sound::linkSlot(const SoundEffectSlot& slot) const noexcept { - alSource3i(m_source, AL_AUXILIARY_SEND_FILTER, static_cast(slot.getIndex()), 0, AL_FILTER_NULL); + alSource3i(m_sourceIndex, AL_AUXILIARY_SEND_FILTER, static_cast(slot.getIndex()), 0, AL_FILTER_NULL); checkError("Failed to link the sound effect slot to the sound"); } void Sound::unlinkSlot() const noexcept { - alSource3i(m_source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL); + alSource3i(m_sourceIndex, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL); checkError("Failed to unlink the sound effect slot from the sound"); } #endif void Sound::setRepeat(bool repeat) const noexcept { - alSourcei(m_source, AL_LOOPING, repeat); + alSourcei(m_sourceIndex, AL_LOOPING, repeat); checkError("Failed to change the sound's repeat state"); } @@ -169,35 +149,35 @@ void Sound::play() const noexcept { if (isPlaying()) return; - alSourcePlay(m_source); + alSourcePlay(m_sourceIndex); checkError("Failed to play/resume the sound"); } void Sound::pause() const noexcept { - alSourcePause(m_source); + alSourcePause(m_sourceIndex); checkError("Failed to pause the sound"); } void Sound::stop() const noexcept { - alSourceStop(m_source); + alSourceStop(m_sourceIndex); checkError("Failed to stop the sound"); } void Sound::rewind() const noexcept { - alSourceRewind(m_source); + alSourceRewind(m_sourceIndex); checkError("Failed to rewind the sound"); } SoundState Sound::recoverState() const noexcept { int state {}; - alGetSourcei(m_source, AL_SOURCE_STATE, &state); + alGetSourcei(m_sourceIndex, AL_SOURCE_STATE, &state); return static_cast(state); } float Sound::recoverElapsedTime() const noexcept { float seconds {}; - alGetSourcef(m_source, AL_SEC_OFFSET, &seconds); + alGetSourcef(m_sourceIndex, AL_SEC_OFFSET, &seconds); return (seconds / 60.f); } @@ -205,34 +185,58 @@ float Sound::recoverElapsedTime() const noexcept { void Sound::destroy() { ZoneScopedN("Sound::destroy"); - if (!m_source.isValid() && !m_buffer.isValid()) + if (!m_sourceIndex.isValid() && !m_bufferIndex.isValid()) return; Logger::debug("[Sound] Destroying..."); - if (m_source.isValid() && alIsSource(m_source)) { - Logger::debug("[Sound] Destroying source (ID: " + std::to_string(m_source) + ")..."); + if (m_sourceIndex.isValid() && alIsSource(m_sourceIndex)) { + Logger::debug("[Sound] Destroying source (ID: " + std::to_string(m_sourceIndex) + ")..."); - alDeleteSources(1, &m_source.get()); + alDeleteSources(1, &m_sourceIndex.get()); checkError("Failed to delete source"); Logger::debug("[Sound] Destroyed source"); } - m_source.reset(); + m_sourceIndex.reset(); - if (m_buffer.isValid() && alIsBuffer(m_buffer)) { - Logger::debug("[Sound] Destroying buffer (ID: " + std::to_string(m_buffer) + ")..."); + if (m_bufferIndex.isValid() && alIsBuffer(m_bufferIndex)) { + Logger::debug("[Sound] Destroying buffer (ID: " + std::to_string(m_bufferIndex) + ")..."); - alDeleteBuffers(1, &m_buffer.get()); + alDeleteBuffers(1, &m_bufferIndex.get()); checkError("Failed to delete buffer"); Logger::debug("[Sound] Destroyed buffer"); } - m_buffer.reset(); + m_bufferIndex.reset(); Logger::debug("[Sound] Destroyed"); } +void Sound::load() { + ZoneScopedN("Sound::load"); + + stop(); // Making sure the sound isn't paused or currently playing + alSourcei(m_sourceIndex, AL_BUFFER, 0); // Detaching the previous buffer (if any) from the source + + if ((m_data.format == AudioFormat::MONO_F32 || m_data.format == AudioFormat::STEREO_F32) && !alIsExtensionPresent("AL_EXT_float32")) { + Logger::error("[Sound] Float audio format is not supported by the audio driver."); + return; + } + + if ((m_data.format == AudioFormat::MONO_F64 || m_data.format == AudioFormat::STEREO_F64) && !alIsExtensionPresent("AL_EXT_double")) { + Logger::error("[Sound] Double audio format is not supported by the audio driver."); + return; + } + + alBufferData(m_bufferIndex, static_cast(m_data.format), m_data.buffer.data(), + static_cast(m_data.buffer.size()), static_cast(m_data.frequency)); + checkError("Failed to send audio data to the buffer"); + + alSourcei(m_sourceIndex, AL_BUFFER, static_cast(m_bufferIndex)); + checkError("Failed to map the sound buffer to the source"); +} + } // namespace Raz diff --git a/src/RaZ/Audio/SoundEffect.cpp b/src/RaZ/Audio/SoundEffect.cpp index d5a2a321..c16e9526 100644 --- a/src/RaZ/Audio/SoundEffect.cpp +++ b/src/RaZ/Audio/SoundEffect.cpp @@ -211,7 +211,7 @@ void SoundEffect::destroy() { Logger::debug("[SoundEffect] Destroying (ID: " + std::to_string(m_index) + ")..."); - if (m_index.isValid() && alIsEffect(m_index)) { + if (alIsEffect(m_index)) { alDeleteEffects(1, &m_index.get()); checkError("Failed to delete sound effect"); } diff --git a/src/RaZ/Audio/SoundEffectSlot.cpp b/src/RaZ/Audio/SoundEffectSlot.cpp index 0a42bd22..2e3bbe1b 100644 --- a/src/RaZ/Audio/SoundEffectSlot.cpp +++ b/src/RaZ/Audio/SoundEffectSlot.cpp @@ -106,7 +106,7 @@ void SoundEffectSlot::destroy() { Logger::debug("[SoundEffectSlot] Destroying (ID: " + std::to_string(m_index) + ")..."); - if (m_index.isValid() && alIsAuxiliaryEffectSlot(m_index)) { + if (alIsAuxiliaryEffectSlot(m_index)) { alDeleteAuxiliaryEffectSlots(1, &m_index.get()); checkError("Failed to delete sound effect slot"); } diff --git a/src/RaZ/Data/WavLoad.cpp b/src/RaZ/Data/WavLoad.cpp index 5615f453..94b3b0d4 100644 --- a/src/RaZ/Data/WavLoad.cpp +++ b/src/RaZ/Data/WavLoad.cpp @@ -1,4 +1,4 @@ -#include "RaZ/Audio/Sound.hpp" +#include "RaZ/Audio/AudioData.hpp" #include "RaZ/Data/WavFormat.hpp" #include "RaZ/Utils/FilePath.hpp" #include "RaZ/Utils/Logger.hpp" @@ -133,7 +133,7 @@ inline WavInfo validateWav(std::ifstream& file) { } // namespace -Sound load(const FilePath& filePath) { +AudioData load(const FilePath& filePath) { ZoneScopedN("WavFormat::load"); Logger::debug("[WavLoad] Loading WAV file ('" + filePath + "')..."); @@ -141,65 +141,62 @@ Sound load(const FilePath& filePath) { std::ifstream file(filePath, std::ios_base::in | std::ios_base::binary); if (!file) - throw std::invalid_argument("Error: Could not open the WAV file '" + filePath + "'"); + throw std::invalid_argument("[WavLoad] Could not open the WAV file '" + filePath + "'"); const WavInfo info = validateWav(file); if (!info.isValid) - throw std::runtime_error("Error: '" + filePath + "' is not a valid WAV audio file"); + throw std::runtime_error("[WavLoad] '" + filePath + "' is not a valid WAV audio file"); - Sound sound; + AudioData audioData {}; // Determining the right audio format switch (info.bitsPerSample) { case 8: if (info.channelCount == 1) - Internal::SoundAccess::getFormat(sound) = AudioFormat::MONO_U8; + audioData.format = AudioFormat::MONO_U8; else if (info.channelCount == 2) - Internal::SoundAccess::getFormat(sound) = AudioFormat::STEREO_U8; + audioData.format = AudioFormat::STEREO_U8; break; case 16: if (info.channelCount == 1) - Internal::SoundAccess::getFormat(sound) = AudioFormat::MONO_I16; + audioData.format = AudioFormat::MONO_I16; else if (info.channelCount == 2) - Internal::SoundAccess::getFormat(sound) = AudioFormat::STEREO_I16; + audioData.format = AudioFormat::STEREO_I16; break; case 32: if (info.channelCount == 1) - Internal::SoundAccess::getFormat(sound) = AudioFormat::MONO_F32; + audioData.format = AudioFormat::MONO_F32; else if (info.channelCount == 2) - Internal::SoundAccess::getFormat(sound) = AudioFormat::STEREO_F32; + audioData.format = AudioFormat::STEREO_F32; break; case 64: if (info.channelCount == 1) - Internal::SoundAccess::getFormat(sound) = AudioFormat::MONO_F64; + audioData.format = AudioFormat::MONO_F64; else if (info.channelCount == 2) - Internal::SoundAccess::getFormat(sound) = AudioFormat::STEREO_F64; + audioData.format = AudioFormat::STEREO_F64; break; default: - throw std::runtime_error("Error: " + std::to_string(info.bitsPerSample) + " bits WAV files are unsupported"); + throw std::runtime_error("[WavLoad] " + std::to_string(info.bitsPerSample) + " bits WAV files are unsupported"); } // If the format is still unassigned, it is invalid - if (static_cast(Internal::SoundAccess::getFormat(sound)) == 0) - throw std::runtime_error("Error: Unsupported WAV channel count"); + if (static_cast(audioData.format) == 0) + throw std::runtime_error("[WavLoad] Unsupported WAV channel count"); - Internal::SoundAccess::getFrequency(sound) = static_cast(info.frequency); + audioData.frequency = static_cast(info.frequency); // Reading the actual audio data from the file - std::vector& soundData = Internal::SoundAccess::getData(sound); - soundData.resize(info.dataSize); - file.read(reinterpret_cast(soundData.data()), static_cast(soundData.size())); - - sound.load(); + audioData.buffer.resize(info.dataSize); + file.read(reinterpret_cast(audioData.buffer.data()), static_cast(audioData.buffer.size())); Logger::debug("[WavLoad] Loaded WAV file"); - return sound; + return audioData; } } // namespace Raz::WavFormat diff --git a/src/RaZ/Data/WavSave.cpp b/src/RaZ/Data/WavSave.cpp index 0ecbc603..41098439 100644 --- a/src/RaZ/Data/WavSave.cpp +++ b/src/RaZ/Data/WavSave.cpp @@ -20,20 +20,18 @@ constexpr std::array toLittleEndian16(uint16_t val) { } // namespace -void save(const FilePath& filePath, const Sound& sound) { +void save(const FilePath& filePath, const AudioData& data) { std::ofstream file(filePath, std::ios_base::out | std::ios_base::binary); if (!file) - throw std::invalid_argument("Error: Unable to create a WAV file as '" + filePath + "'; path to file must exist"); - - const std::vector& soundData = Internal::SoundAccess::getData(sound); + throw std::invalid_argument("[WavSave] Unable to create a WAV file as '" + filePath + "'; path to file must exist"); //////////// // Header // //////////// file << "RIFF"; - file.write(toLittleEndian32(static_cast(soundData.size()) + 36).data(), 4); // File size - 8 + file.write(toLittleEndian32(static_cast(data.buffer.size()) + 36).data(), 4); // File size - 8 file << "WAVE"; ////////////////// @@ -45,7 +43,7 @@ void save(const FilePath& filePath, const Sound& sound) { uint8_t bitCount {}; - switch (sound.getFormat()) { + switch (data.format) { case AudioFormat::MONO_U8: case AudioFormat::STEREO_U8: bitCount = 8; @@ -67,14 +65,14 @@ void save(const FilePath& filePath, const Sound& sound) { break; default: - throw std::invalid_argument("Error: Unhandled audio format"); + throw std::invalid_argument("[WavSave] Unhandled audio format"); } file.write(toLittleEndian16((bitCount >= 32 ? 3 : 1)).data(), 2); // Writing 1 if integer, 3 if floating-point uint16_t channelCount {}; - switch (sound.getFormat()) { + switch (data.format) { case AudioFormat::MONO_U8: case AudioFormat::MONO_I16: case AudioFormat::MONO_F32: @@ -90,15 +88,15 @@ void save(const FilePath& filePath, const Sound& sound) { break; default: - throw std::invalid_argument("Error: Unhandled audio format"); + throw std::invalid_argument("[WavSave] Unhandled audio format"); } file.write(toLittleEndian16(channelCount).data(), 2); - file.write(toLittleEndian32(static_cast(sound.getFrequency())).data(), 4); + file.write(toLittleEndian32(static_cast(data.frequency)).data(), 4); const auto frameSize = static_cast(bitCount / 8 * channelCount); - file.write(toLittleEndian32(static_cast(sound.getFrequency()) * frameSize).data(), 4); // Bytes per second + file.write(toLittleEndian32(static_cast(data.frequency) * frameSize).data(), 4); // Bytes per second file.write(toLittleEndian16(frameSize).data(), 2); // Bytes per block (bits per sample / 8 * channel count) file.write(toLittleEndian16(bitCount).data(), 2); // Bits per sample (bit depth) @@ -107,8 +105,8 @@ void save(const FilePath& filePath, const Sound& sound) { //////////////// file << "data"; - file.write(toLittleEndian32(static_cast(soundData.size())).data(), 4); - file.write(reinterpret_cast(soundData.data()), static_cast(soundData.size())); + file.write(toLittleEndian32(static_cast(data.buffer.size())).data(), 4); + file.write(reinterpret_cast(data.buffer.data()), static_cast(data.buffer.size())); } } // namespace Raz::WavFormat diff --git a/src/RaZ/Script/LuaAudio.cpp b/src/RaZ/Script/LuaAudio.cpp index 017f9fbc..6d9b9237 100644 --- a/src/RaZ/Script/LuaAudio.cpp +++ b/src/RaZ/Script/LuaAudio.cpp @@ -19,6 +19,25 @@ using namespace TypeUtils; void LuaWrapper::registerAudioTypes() { sol::state& state = getState(); + { + sol::usertype audioData = state.new_usertype("AudioData", + sol::constructors()); + audioData["format"] = &AudioData::format; + audioData["frequency"] = &AudioData::frequency; + audioData["buffer"] = &AudioData::buffer; + + state.new_enum("AudioFormat", { + { "MONO_U8", AudioFormat::MONO_U8 }, + { "STEREO_U8", AudioFormat::STEREO_U8 }, + { "MONO_I16", AudioFormat::MONO_I16 }, + { "STEREO_I16", AudioFormat::STEREO_I16 }, + { "MONO_F32", AudioFormat::MONO_F32 }, + { "STEREO_F32", AudioFormat::STEREO_F32 }, + { "MONO_F64", AudioFormat::MONO_F64 }, + { "STEREO_F64", AudioFormat::STEREO_F64 } + }); + } + { sol::usertype audioSystem = state.new_usertype("AudioSystem", sol::constructors(&AudioSystem::openDevice)); audioSystem["recoverCurrentDevice"] = &AudioSystem::recoverCurrentDevice; - - state.new_enum("AudioFormat", { - { "MONO_U8", AudioFormat::MONO_U8 }, - { "STEREO_U8", AudioFormat::STEREO_U8 }, - { "MONO_I16", AudioFormat::MONO_I16 }, - { "STEREO_I16", AudioFormat::STEREO_I16 }, - { "MONO_F32", AudioFormat::MONO_F32 }, - { "STEREO_F32", AudioFormat::STEREO_F32 }, - { "MONO_F64", AudioFormat::MONO_F64 }, - { "STEREO_F64", AudioFormat::STEREO_F64 } - }); } { @@ -72,21 +80,19 @@ void LuaWrapper::registerAudioTypes() { microphone["stop"] = &Microphone::stop; microphone["recoverAvailableSampleCount"] = &Microphone::recoverAvailableSampleCount; microphone["recoverAvailableDuration"] = &Microphone::recoverAvailableDuration; - microphone["recoverData"] = sol::overload([] (Microphone& m) { return m.recoverData(); }, + microphone["recoverData"] = sol::overload([] (const Microphone& m) { return m.recoverData(); }, PickOverload(&Microphone::recoverData)); - microphone["recoverSound"] = sol::overload([] (Microphone& m) { return m.recoverSound(); }, - PickOverload(&Microphone::recoverSound)); } { sol::usertype sound = state.new_usertype("Sound", - sol::constructors(), + sol::constructors(), sol::base_classes, sol::bases()); sound["getBufferIndex"] = &Sound::getBufferIndex; - sound["getFormat"] = &Sound::getFormat; - sound["getFrequency"] = &Sound::getFrequency; + sound["getData"] = &Sound::getData; sound["init"] = &Sound::init; - sound["load"] = &Sound::load; + sound["load"] = PickOverload(&Sound::load); sound["pitch"] = sol::property(&Sound::recoverPitch, &Sound::setPitch); sound["gain"] = sol::property(&Sound::recoverGain, &Sound::setGain); sound["position"] = sol::property(&Sound::recoverPosition, PickOverload(&Sound::setPosition)); diff --git a/tests/src/RaZ/Data/WavFormat.cpp b/tests/src/RaZ/Data/WavFormat.cpp index cdb116bd..273e25c3 100644 --- a/tests/src/RaZ/Data/WavFormat.cpp +++ b/tests/src/RaZ/Data/WavFormat.cpp @@ -1,24 +1,19 @@ -#include "RaZ/Audio/Sound.hpp" +#include "RaZ/Audio/AudioData.hpp" #include "RaZ/Data/WavFormat.hpp" #include "RaZ/Utils/FilePath.hpp" #include TEST_CASE("WavFormat load/save", "[data]") { - const Raz::Sound origSound = Raz::WavFormat::load(RAZ_TESTS_ROOT "assets/sounds/notif_ting.wav"); - // Since no AudioSystem has been created, the Sound's buffer index cannot be valid. This is not the purpose of this test anyway + const Raz::AudioData origAudioData = Raz::WavFormat::load(RAZ_TESTS_ROOT "assets/sounds/notif_ting.wav"); + CHECK(origAudioData.format == Raz::AudioFormat::MONO_I16); + CHECK(origAudioData.frequency == 48000); + CHECK(origAudioData.buffer.size() == 608894); - CHECK(origSound.getFormat() == Raz::AudioFormat::MONO_I16); - CHECK(origSound.getFrequency() == 48000); + Raz::WavFormat::save("téstÊxpørt.wav", origAudioData); - Raz::WavFormat::save("téstÊxpørt.wav", origSound); - - const Raz::Sound savedSound = Raz::WavFormat::load("téstÊxpørt.wav"); - CHECK(savedSound.getFormat() == origSound.getFormat()); - CHECK(savedSound.getFrequency() == origSound.getFrequency()); - - const std::vector& origSoundData = Raz::Internal::SoundAccess::getData(origSound); - const std::vector& savedSoundData = Raz::Internal::SoundAccess::getData(savedSound); - REQUIRE(savedSoundData.size() == origSoundData.size()); - CHECK(std::equal(savedSoundData.cbegin(), savedSoundData.cend(), origSoundData.cbegin())); + const Raz::AudioData savedAudioData = Raz::WavFormat::load("téstÊxpørt.wav"); + CHECK(savedAudioData.format == origAudioData.format); + CHECK(savedAudioData.frequency == origAudioData.frequency); + CHECK(savedAudioData.buffer == origAudioData.buffer); } diff --git a/tests/src/RaZ/Script/LuaAudio.cpp b/tests/src/RaZ/Script/LuaAudio.cpp index 5e3317e3..aae2c15d 100644 --- a/tests/src/RaZ/Script/LuaAudio.cpp +++ b/tests/src/RaZ/Script/LuaAudio.cpp @@ -62,8 +62,7 @@ TEST_CASE("LuaAudio Microphone", "[script][lua][audio]") { microphone:stop() assert(microphone:recoverAvailableSampleCount() ~= nil) assert(microphone:recoverAvailableDuration() ~= nil) - assert(#microphone:recoverData() == #microphone:recoverData(0)) - assert(microphone:recoverSound() ~= microphone:recoverSound(0)) + assert(#microphone:recoverData().buffer == 0) )")); } @@ -71,15 +70,18 @@ TEST_CASE("LuaAudio Sound", "[script][lua][audio]") { CHECK(TestUtils::executeLuaScript(R"( local audioSystem = AudioSystem.new() -- Initializing the audio device & context, needed before all audio action + local audioData = WavFormat.load(FilePath.new(RAZ_TESTS_ROOT .. "assets/sounds/notif_ting.wav")) + local sound = Sound.new() - sound = WavFormat.load(FilePath.new(RAZ_TESTS_ROOT .. "assets/sounds/notif_ting.wav")) + sound = Sound.new(audioData) assert(sound:getBufferIndex() >= 0) - assert(sound:getFormat() == AudioFormat.MONO_I16) - assert(sound:getFrequency() == 48000) + assert(sound:getData().format == AudioFormat.MONO_I16) + assert(sound:getData().frequency == 48000) + assert(#sound:getData().buffer == 608894) sound:init() - sound:load() + sound:load(audioData) sound.pitch = 0.5 sound.gain = 0