Skip to content

Commit

Permalink
[Audio/AudioData] Added a type dedicated to just audio data
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Razakhel committed Jan 10, 2025
1 parent 7a32866 commit 5ada8a3
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 232 deletions.
28 changes: 15 additions & 13 deletions examples/audioDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ 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<Raz::AudioSystem>();

// The Listener entity requires a Transform component
auto& listener = world.addEntityWithComponent<Raz::Transform>().addComponent<Raz::Listener>();

Raz::Entity& sound = world.addEntity();
auto& soundTrans = sound.addComponent<Raz::Transform>();
auto& soundComp = sound.addComponent<Raz::Sound>(Raz::WavFormat::load(RAZ_ROOT "assets/sounds/knock.wav"));
auto& soundComp = sound.addComponent<Raz::Sound>(knockAudio);

Raz::Microphone microphone(Raz::AudioFormat::MONO_U8, 16000, 1.f);

Expand Down Expand Up @@ -109,7 +112,6 @@ int main() {
listener.setGain(listenerGain);

soundComp.init();
soundComp.load();
soundComp.setRepeat(isRepeating);
soundComp.setGain(soundGain);
soundComp.setPitch(soundPitch);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -605,7 +607,7 @@ int main() {
// Starting application //
//////////////////////////

std::vector<uint8_t> 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)));
Expand All @@ -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<float>(captureData[i]) * factorU8 - 1.f);
rightCapturePlot.push(static_cast<float>(captureData[i + 1]) * factorU8 - 1.f);
leftCapturePlot.push(static_cast<float>(captureData.buffer[i]) * factorU8 - 1.f);
rightCapturePlot.push(static_cast<float>(captureData.buffer[i + 1]) * factorU8 - 1.f);
++i;
} else { // Mono 8
monoCapturePlot.push(static_cast<float>(captureData[i]) * factorU8 - 1.f);
monoCapturePlot.push(static_cast<float>(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<float>(static_cast<int16_t>((captureData[i] << 0u) | (captureData[i + 1] << 8u))) * factorI16);
rightCapturePlot.push(static_cast<float>(static_cast<int16_t>((captureData[i + 2] << 0u) | (captureData[i + 3] << 8u))) * factorI16);
leftCapturePlot.push(static_cast<float>(static_cast<int16_t>(captureData.buffer[i] | (captureData.buffer[i + 1] << 8u))) * factorI16);
rightCapturePlot.push(static_cast<float>(static_cast<int16_t>(captureData.buffer[i + 2] | (captureData.buffer[i + 3] << 8u))) * factorI16);
i += 2;
} else { // Mono 16
monoCapturePlot.push(static_cast<float>(static_cast<int16_t>((captureData[i] << 0u) | (captureData[i + 1] << 8u))) * factorI16);
monoCapturePlot.push(static_cast<float>(static_cast<int16_t>(captureData.buffer[i] | (captureData.buffer[i + 1] << 8u))) * factorI16);
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions include/RaZ/Audio/AudioData.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#ifndef RAZ_AUDIODATA_HPP
#define RAZ_AUDIODATA_HPP

#include <cstdint>
#include <vector>

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<uint8_t> buffer {};
};

} // namespace Raz

#endif // RAZ_AUDIODATA_HPP
11 changes: 0 additions & 11 deletions include/RaZ/Audio/AudioSystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 11 additions & 16 deletions include/RaZ/Audio/Microphone.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string>

class Sound;
namespace Raz {

class Microphone {
public:
Expand Down Expand Up @@ -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<uint8_t> 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<uint8_t>& 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;
Expand Down
50 changes: 16 additions & 34 deletions include/RaZ/Audio/Sound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cstddef>
#include <limits>
#include <vector>

namespace Raz {

namespace Internal { class SoundAccess; }

class SoundEffectSlot;

template <typename T, std::size_t Size>
Expand All @@ -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;
Expand Down Expand Up @@ -128,30 +125,15 @@ class Sound final : public Component {
~Sound() override { destroy(); }

private:
OwnerValue<unsigned int, std::numeric_limits<unsigned int>::max()> m_buffer {};
OwnerValue<unsigned int, std::numeric_limits<unsigned int>::max()> m_source {};

AudioFormat m_format {};
int m_frequency {};
std::vector<uint8_t> 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<unsigned int, std::numeric_limits<unsigned int>::max()> m_bufferIndex {};
OwnerValue<unsigned int, std::numeric_limits<unsigned int>::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<uint8_t>& getData(const Sound& sound) { return sound.m_data; }
static std::vector<uint8_t>& getData(Sound& sound) { return sound.m_data; }
AudioData m_data {};
};

} // namespace Internal

} // namespace Raz

#endif // RAZ_SOUND_HPP
14 changes: 7 additions & 7 deletions include/RaZ/Data/WavFormat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions include/RaZ/RaZ.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
32 changes: 11 additions & 21 deletions src/RaZ/Audio/Microphone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <AL/al.h>
#include <AL/alc.h>

#include <stdexcept>
#include <string>

namespace Raz {
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -170,17 +171,19 @@ float Microphone::recoverAvailableDuration() const noexcept {
return (static_cast<float>(recoverAvailableSampleCount()) / static_cast<float>(m_frequency));
}

std::vector<uint8_t> Microphone::recoverData(float maxDuration) const {
std::vector<uint8_t> data;
AudioData Microphone::recoverData(float maxDuration) const {
AudioData data {};
recoverData(data, maxDuration);

return data;
}

void Microphone::recoverData(std::vector<uint8_t>& 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;
Expand All @@ -193,25 +196,12 @@ void Microphone::recoverData(std::vector<uint8_t>& data, float maxDuration) cons
if (maxDuration > 0.f)
sampleCount = std::min(sampleCount, static_cast<int>(maxDuration * static_cast<float>(m_frequency)));

data.resize(recoverFrameSize(m_format) * sampleCount);
data.buffer.resize(recoverFrameSize(m_format) * sampleCount);

alcCaptureSamples(static_cast<ALCdevice*>(m_device), data.data(), sampleCount);
alcCaptureSamples(static_cast<ALCdevice*>(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<int>(m_frequency);
sound.m_data = recoverData(maxDuration);
sound.load();

return sound;
}

void Microphone::destroy() {
ZoneScopedN("Microphone::destroy");

Expand Down
Loading

0 comments on commit 5ada8a3

Please sign in to comment.