Compare commits

...

7 Commits

7 changed files with 142 additions and 55 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "lib/miniaudio"]
path = lib/miniaudio
url = https://github.com/mackron/miniaudio
[submodule "lib/magic_enum"]
path = lib/magic_enum
url = https://github.com/Neargye/magic_enum

View File

@@ -9,7 +9,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-subobject-linkage")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address")
endif()
add_subdirectory(lib/miniaudio)
add_subdirectory(lib/magic_enum)
find_package(Corrade REQUIRED Main)
find_package(Magnum REQUIRED)
@@ -20,8 +22,9 @@ pkg_check_modules(AVUTIL REQUIRED libavutil)
pkg_check_modules(SWRESAMPLE REQUIRED libswresample)
add_library(
ChargeAudio SHARED "src/ChargeAudio.hpp" "src/Engine.cpp" "src/Sound.cpp"
"src/Listener.cpp" "lib/miniaudio/miniaudio.c")
ChargeAudio SHARED
"src/ChargeAudio.hpp" "src/Engine.cpp" "src/Sound.cpp" "src/Listener.cpp"
"src/ErrorHandling.cpp" "lib/miniaudio/miniaudio.c")
target_link_libraries(
ChargeAudio

1
lib/magic_enum Submodule

Submodule lib/magic_enum added at ec5734e491

View File

@@ -16,8 +16,8 @@ typedef Containers::Pointer<class Sound> SoundContainer;
typedef Containers::Pointer<class Listener> ListenerContainer;
class Sound {
public:
enum class SoundState { Idle, Playing, Paused, Finished };
enum class SoundType { FromFile, StreamedRawPCM, RawPCM };
enum class State { Idle, Playing, Paused, Finished };
enum class Type { FromFile, StreamedRawPCM, RawPCM };
// No copying
Sound(const Sound &) = delete;
Sound &operator=(const Sound &) = delete;
@@ -31,8 +31,9 @@ public:
void Pause();
void Reset();
const SoundState GetState();
const SoundType GetSoundType();
const State GetState();
const Type GetSoundType();
const size_t GetPlayedSampleCount();
const float GetPlaybackTime();
bool SetPlaybackTime(float time);
@@ -42,9 +43,12 @@ public:
void SetVolume(float value);
const float GetVolume();
// For StreamedRawPCM
void WriteToRingBuffer(uint8_t *data, uint32_t length);
private:
Sound(class Engine *engine, std::function<void(Sound *)> setupFunction,
SoundType type, std::string additionalErrorMessage = "");
Type type, std::string additionalErrorMessage = "");
static void onSoundFinish(void *customData, ma_sound *);
class Engine *baseEngine;
@@ -52,8 +56,8 @@ private:
ma_sound_config maConfig;
ma_pcm_rb maRingBuffer;
ma_audio_buffer maAudioBuffer;
SoundState state = SoundState::Idle;
SoundType type;
State state = State::Idle;
Type type;
friend class Engine;
};
@@ -110,9 +114,19 @@ private:
ma_result maResponse;
ma_decoder maStero;
ma_uint64 listenerCounter = 0;
ma_format PCMFormat = ma_format_f32;
ma_uint8 frameSize;
friend class Listener;
friend class Sound;
};
// For MA
void ThrowOnRuntimeError(std::string message,
ma_result errorType = ma_result::MA_ERROR);
// Generalised
void ThrowOnRuntimeError(std::string message, bool comparison);
void WarnIfTrue(std::string message, bool comparison = true);
} // namespace ChargeAudio
#endif

View File

@@ -1,9 +1,7 @@
#include "ChargeAudio.hpp"
#include "miniaudio/miniaudio.h"
#include <Corrade/Utility/Debug.h>
#include <cstdint>
#include <stdexcept>
using namespace ChargeAudio;
using namespace Corrade;
@@ -12,12 +10,10 @@ Engine::Engine(uint32_t sampleRate, uint32_t channels) {
maConfig = ma_engine_config_init();
maConfig.sampleRate = sampleRate;
maConfig.channels = channels;
frameSize = ma_get_bytes_per_sample(PCMFormat) * channels;
if ((maResponse = ma_engine_init(&maConfig, &maEngine)) != MA_SUCCESS) {
Utility::Error{} << "Could not init miniaudio (" << maResponse << ")";
throw new std::runtime_error(
"Failed to init miniaudio engine. Check STDERR for more info.");
}
ThrowOnRuntimeError("Failed to init miniaudio engine",
ma_engine_init(&maConfig, &maEngine));
}
Engine::~Engine() { ma_engine_uninit(&maEngine); }
@@ -32,16 +28,12 @@ SoundContainer Engine::CreateSound(int bufferLengthInSeconds) {
[length = bufferLengthInSeconds, channels = GetChannelCount(),
sampleRate = GetSampleRate()](Sound *sound) {
ma_result result = ma_pcm_rb_init(
ma_format_s32, channels, sampleRate * channels * length, nullptr,
nullptr, &sound->maRingBuffer);
if (result != MA_SUCCESS) {
Utility::Error{} << "Failed to create a new ring buffer!" << " ("
<< result << ")";
throw new std::runtime_error("Failed to create a new ring buffer! "
"Check STDERR for more info.");
}
sound->baseEngine->PCMFormat, channels, sampleRate * length,
nullptr, nullptr, &sound->maRingBuffer);
ThrowOnRuntimeError("Failed to create a new ring buffer!", result);
sound->maConfig.pDataSource = &sound->maRingBuffer;
},
Sound::SoundType::StreamedRawPCM,
Sound::Type::StreamedRawPCM,
"Failed to create the sound from ring buffer: "));
}
@@ -52,21 +44,15 @@ SoundContainer Engine::CreateSound(uint8_t *data, int length) {
[data, length, channels = GetChannelCount(),
sampleRate = GetSampleRate()](Sound *sound) {
ma_audio_buffer_config config = ma_audio_buffer_config_init(
ma_format_s32, channels, length / (4 * channels), data, nullptr);
sound->baseEngine->PCMFormat, channels,
length / sound->baseEngine->frameSize, data, nullptr);
ma_result result =
ma_audio_buffer_init_copy(&config, &sound->maAudioBuffer);
if (result != MA_SUCCESS) {
Utility::Error{} << "Failed to create a new audio buffer!" << " ("
<< result << ")";
throw new std::runtime_error("Failed to create a new audio buffer! "
"Check STDERR for more info.");
}
ThrowOnRuntimeError("Failed to create a new audio buffer!", result);
sound->maConfig.pDataSource = &sound->maAudioBuffer;
},
Sound::SoundType::RawPCM,
"Failed to create the sound from the PCM data: "));
Sound::Type::RawPCM, "Failed to create the sound from the PCM data: "));
}
SoundContainer Engine::CreateSound(const std::string filepath,
@@ -77,7 +63,7 @@ SoundContainer Engine::CreateSound(const std::string filepath,
sound->maConfig.pFilePath = filepath.c_str();
sound->maConfig.flags = (streamFile ? MA_SOUND_FLAG_STREAM : 0);
},
Sound::SoundType::FromFile,
Sound::Type::FromFile,
"Failed to create the sound from the file: " + filepath));
}

32
src/ErrorHandling.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "ChargeAudio.hpp"
#include "magic_enum/include/magic_enum/magic_enum.hpp"
#include <Corrade/Utility/Debug.h>
#include <stdexcept>
void ChargeAudio::ThrowOnRuntimeError(std::string message,
ma_result errorType) {
if (errorType == MA_SUCCESS) {
return;
}
Utility::Error{} << Utility::Debug::color(Utility::Debug::Color::Red)
<< "ERROR:" << Utility::Debug::resetColor << message.c_str()
<< magic_enum::enum_name(errorType).data()
<< "Execution will be stopped!\n";
throw new std::runtime_error(message + " Check STDERR for more info.\n");
}
void ChargeAudio::ThrowOnRuntimeError(std::string message, bool comparison) {
if (!comparison) {
return;
}
ThrowOnRuntimeError(message);
}
void ChargeAudio::WarnIfTrue(std::string message, bool comparison) {
if (comparison) {
Utility::Debug{} << Utility::Debug::color(Utility::Debug::Color::Yellow)
<< "WARNING:" << Utility::Debug::resetColor
<< message.c_str() << "Execution will continue.";
}
}

View File

@@ -1,34 +1,33 @@
#include "ChargeAudio.hpp"
#include <Corrade/Utility/Debug.h>
#include <cassert>
#include <cstring>
#include <functional>
#include <stdexcept>
using namespace ChargeAudio;
Sound::Sound(Engine *engine, std::function<void(Sound *)> setupFunction,
SoundType soundType, std::string additionalErrorMessage)
Type soundType, std::string additionalErrorMessage)
: baseEngine(engine) {
maConfig = ma_sound_config_init_2(&baseEngine->maEngine);
maConfig.endCallback = Sound::onSoundFinish;
maConfig.pEndCallbackUserData = this;
setupFunction(this);
ma_result maResponse =
ma_sound_init_ex(&baseEngine->maEngine, &maConfig, &maSound);
if (maResponse != MA_SUCCESS) {
Utility::Error{} << "Failed to create a new sound" << " (" << maResponse
<< ")";
throw new std::runtime_error(
"Failed to create a new sound. Check STDERR for more info.\n" +
additionalErrorMessage);
}
ThrowOnRuntimeError("Failed to create a new sound. " + additionalErrorMessage,
maResponse);
type = soundType;
}
Sound::~Sound() {
switch (type) {
case Sound::SoundType::RawPCM:
case Sound::Type::RawPCM:
ma_audio_buffer_uninit_and_free(&maAudioBuffer);
break;
case Sound::SoundType::StreamedRawPCM:
case Sound::Type::StreamedRawPCM:
ma_pcm_rb_uninit(&maRingBuffer);
break;
default:
@@ -37,16 +36,24 @@ Sound::~Sound() {
ma_sound_uninit(&maSound);
}
const Sound::SoundState Sound::GetState() { return state; }
const Sound::SoundType Sound::GetSoundType() { return type; }
const Sound::State Sound::GetState() { return state; }
const Sound::Type Sound::GetSoundType() { return type; }
const float Sound::GetDuration() {
WarnIfTrue("You are using StreamedRawPCM sound! GetDuration() will not work "
"since the PCM data is in a fixed ring buffer.",
type == Sound::Type::StreamedRawPCM);
float time;
ma_sound_get_length_in_seconds(&this->maSound, &time);
return time;
}
const float Sound::GetPlaybackTime() {
WarnIfTrue(
"You are using StreamedRawPCM sound! GetPlaybackTime() will not work "
"since the PCM data is in a fixed ring buffer. However you can do "
"GetPlayedSampleCount()/SAMPLE_RATE to get time elapsed.",
type == Sound::Type::StreamedRawPCM);
float time;
ma_sound_get_cursor_in_seconds(&this->maSound, &time);
return time;
@@ -54,6 +61,12 @@ const float Sound::GetPlaybackTime() {
// true or false depending on if the playback was set
bool Sound::SetPlaybackTime(float time) {
ThrowOnRuntimeError(
"You cannot set playback time on a StreamedRawPCM as it uses a ring "
"buffer. If you wanted to skip forward or backward. Empty the buffer and "
"then write what you want.",
type == Sound::Type::StreamedRawPCM);
// Better to just catch it from the start
if (time < 0) {
return false;
@@ -69,16 +82,27 @@ bool Sound::SetPlaybackTime(float time) {
// Controls
void Sound::Play() {
ma_sound_start(&maSound);
state = Sound::SoundState::Playing;
ThrowOnRuntimeError("Failed to start the sound.", ma_sound_start(&maSound));
state = Sound::State::Playing;
}
void Sound::Pause() {
ma_sound_stop(&maSound);
state = Sound::SoundState::Paused;
ThrowOnRuntimeError("Failed to pause the sound.", ma_sound_stop(&maSound));
state = Sound::State::Paused;
}
void Sound::Reset() { ma_sound_seek_to_pcm_frame(&maSound, 0); }
void Sound::Reset() {
ThrowOnRuntimeError(
"You cannot reset on Streamed RawPCM sounds! Since the buffer is a "
"ring buffer there isn't a \"start\" to return to.",
type == Type::StreamedRawPCM);
ma_sound_seek_to_pcm_frame(&maSound, 0);
}
// Used with Streamed PCM but works with anything
const size_t Sound::GetPlayedSampleCount() {
return ma_sound_get_time_in_pcm_frames(&maSound);
}
void Sound::SetPosition(Magnum::Vector3 position) {
ma_sound_set_position(&maSound, position.x(), position.y(), position.z());
@@ -90,8 +114,32 @@ const Magnum::Vector3 Sound::GetPosition() {
void Sound::SetVolume(float value) { ma_sound_set_volume(&maSound, value); }
const float Sound::GetVolume() { return ma_sound_get_volume(&maSound); }
// StreamedRawPCM
void Sound::WriteToRingBuffer(uint8_t *data, uint32_t length) {
uint8_t *dataPosition = data;
void *writePosition;
uint32_t framesToWrite = length / baseEngine->frameSize, framesWritten = 0,
framesAvailable = 0;
while (framesWritten < framesToWrite) {
framesAvailable = framesToWrite - framesWritten;
// Sometimes this can fail. Actually terrifying.
ThrowOnRuntimeError("PCM ring buffer could not allocate... Oh no",
ma_pcm_rb_acquire_write(&maRingBuffer, &framesAvailable,
&writePosition));
memcpy(writePosition, dataPosition,
framesAvailable * baseEngine->frameSize);
ma_pcm_rb_commit_write(&maRingBuffer, framesAvailable);
framesWritten += framesAvailable;
dataPosition += framesAvailable * baseEngine->frameSize;
}
}
// STATICs
void Sound::onSoundFinish(void *customData, ma_sound *) {
auto sound = static_cast<Sound *>(customData);
sound->state = SoundState::Finished;
sound->state = State::Finished;
}