Audio Abstrakcija - WIP
This commit is contained in:
@@ -28,6 +28,13 @@ FetchContent_Declare(
|
||||
GIT_TAG release-3.2.2
|
||||
)
|
||||
|
||||
# Download SDL3_mixer from source
|
||||
FetchContent_Declare(
|
||||
SDL3_mixer
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL_mixer.git
|
||||
GIT_TAG release-3.2.0
|
||||
)
|
||||
|
||||
# Work around PipeWire API mismatch on some Linux distributions.
|
||||
# Disable PipeWire backend in SDL; PulseAudio/ALSA backends remain available.
|
||||
set(SDL_PIPEWIRE OFF CACHE BOOL "Disable SDL PipeWire backend" FORCE)
|
||||
@@ -37,6 +44,7 @@ set(SDL_PIPEWIRE_SHARED OFF CACHE BOOL "Disable dynamic PipeWire loading in SDL"
|
||||
FetchContent_MakeAvailable(SDL3)
|
||||
FetchContent_MakeAvailable(SDL3_image)
|
||||
FetchContent_MakeAvailable(SDL3_ttf)
|
||||
FetchContent_MakeAvailable(SDL3_mixer)
|
||||
|
||||
# Collect all source files from src/ and nested directories
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
@@ -51,4 +59,4 @@ add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||
|
||||
# Link SDL3 and SDL3_image to the executable
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3 SDL3_image::SDL3_image SDL3_ttf::SDL3_ttf)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3 SDL3_image::SDL3_image SDL3_ttf::SDL3_ttf SDL3_mixer::SDL3_mixer)
|
||||
|
||||
21
include/audio/audio.hpp
Normal file
21
include/audio/audio.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <utils.hpp>
|
||||
|
||||
namespace Game::Audio {
|
||||
class Audio {
|
||||
public:
|
||||
Audio() = default;
|
||||
DISABLE_COPY_AND_MOVE(Audio)
|
||||
~Audio();
|
||||
|
||||
static Audio& getInstance();
|
||||
bool init();
|
||||
SDL_AudioDeviceID getAudioDevice() const { return mDevice; }
|
||||
|
||||
private:
|
||||
SDL_AudioSpec mAudioSpec;
|
||||
SDL_AudioDeviceID mDevice;
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <object/entity.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
#include <renderer/font.hpp>
|
||||
#include <object/sound.hpp>
|
||||
|
||||
namespace Game::AGame {
|
||||
class Player : public Object::Entity {
|
||||
@@ -12,5 +13,8 @@ namespace Game::AGame {
|
||||
~Player() override = default;
|
||||
void start() override;
|
||||
void update(float deltaTime) override;
|
||||
|
||||
private:
|
||||
Object::Sound mSound;
|
||||
};
|
||||
}
|
||||
37
include/object/sound.hpp
Normal file
37
include/object/sound.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include <utils.hpp>
|
||||
#include <audio/audio.hpp>
|
||||
|
||||
namespace Game::Object {
|
||||
enum class Format {
|
||||
WAV,
|
||||
OGG,
|
||||
MP3,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class Sound {
|
||||
public:
|
||||
Sound() = default;
|
||||
Sound(std::string path, Format format, int volume = 128);
|
||||
Sound(const Sound&);
|
||||
Sound& operator=(const Sound&);
|
||||
Sound(Sound&&);
|
||||
Sound& operator=(Sound&&);
|
||||
~Sound();
|
||||
|
||||
void setVolume(int volume) { mVolume = volume; }
|
||||
int getVolume() const { return mVolume; }
|
||||
|
||||
void play();
|
||||
|
||||
private:
|
||||
Uint8* mAudioBuffer = nullptr;
|
||||
Uint32 mAudioLength = 0;
|
||||
SDL_AudioStream* mAudioStream = nullptr;
|
||||
int mVolume;
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,9 @@ namespace Game::Renderer {
|
||||
|
||||
SDL_Texture* getSDLTexture();
|
||||
std::string getId();
|
||||
float getWidth();
|
||||
float getHeight();
|
||||
|
||||
protected:
|
||||
SDL_Texture* mTex;
|
||||
std::string mId;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#define DISABLE_COPY(Class) \
|
||||
Class(const Class&) = delete; \
|
||||
@@ -29,3 +30,8 @@
|
||||
|
||||
#define END_GAME_ENTITY() \
|
||||
};
|
||||
|
||||
#define RUNNING_TIME() \
|
||||
SDL_GetTicks() / 1000.f
|
||||
|
||||
#define PI 3.14159265358979323846f
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <renderer/renderer.hpp>
|
||||
#include <state/gamestate.hpp>
|
||||
#include <game/gamemanager.hpp>
|
||||
#include <audio/audio.hpp>
|
||||
|
||||
namespace Game::Window {
|
||||
class Window {
|
||||
|
||||
BIN
resources/example.wav
Normal file
BIN
resources/example.wav
Normal file
Binary file not shown.
37
src/audio/audio.cpp
Normal file
37
src/audio/audio.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <audio/audio.hpp>
|
||||
|
||||
namespace Game::Audio {
|
||||
Audio::~Audio() {
|
||||
if (mDevice != 0) {
|
||||
SDL_CloseAudioDevice(mDevice);
|
||||
LOG("Audio device closed successfully");
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
LOG("SDL audio subsystem quit successfully");
|
||||
}
|
||||
|
||||
Audio& Audio::getInstance() {
|
||||
static Audio instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Audio::init() {
|
||||
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
ERROR("Failed to initialize SDL audio subsystem: " << SDL_GetError());
|
||||
} else {
|
||||
LOG("SDL audio subsystem initialized successfully");
|
||||
}
|
||||
|
||||
mAudioSpec.channels = 2; // Stereo
|
||||
mAudioSpec.freq = 44100; // CD quality
|
||||
mAudioSpec.format = SDL_AUDIO_F32; // 32-bit float
|
||||
|
||||
mDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &mAudioSpec);
|
||||
if (mDevice == 0) {
|
||||
ERROR("Failed to open audio device: " << SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,22 @@
|
||||
|
||||
namespace Game::AGame {
|
||||
void Player::start() {
|
||||
LOG("Created the Player");
|
||||
mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
|
||||
mSound.play();
|
||||
|
||||
if (mTex && mTex->getId() == "Roboto") {
|
||||
LOG("Player texture is a font");
|
||||
// Treat as Font and build it
|
||||
std::shared_ptr<Renderer::Font> font = std::dynamic_pointer_cast<Renderer::Font>(mTex);
|
||||
if (font) {
|
||||
font->build({255, 255, 255, 255}, "Hello, World!");
|
||||
} else {
|
||||
ERROR("Failed to cast texture to font");
|
||||
}
|
||||
}
|
||||
mTransform.x = 640.f - (mTex->getWidth() / 2.f * mTransform.scaleX); // Start in the middle of the screen
|
||||
mTransform.y = 360.f - (mTex->getHeight() / 2.f * mTransform.scaleY);
|
||||
mTransform.rotation = 0.f;
|
||||
}
|
||||
|
||||
void Player::update(float deltaTime) {
|
||||
if (!mIsActive) return;
|
||||
//LOG("Updated Player");
|
||||
mTransform.x += 1.f; // Move right at a constant speed for testing
|
||||
//mTransform.x += 1.f; // Move right at a constant speed for testing
|
||||
mTransform.rotation += 1.f; // Rotate clockwise for testing
|
||||
//LOG(mName << " position: " << mTransform.x << ' ' << mTransform.y);
|
||||
//LOG("DeltaTime: " << deltaTime);
|
||||
mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||
//mTransform.scaleY = 1.f + 1.f * std::sin(SDL_GetTicks() / 500.f);
|
||||
}
|
||||
}
|
||||
150
src/object/sound.cpp
Normal file
150
src/object/sound.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <object/sound.hpp>
|
||||
|
||||
namespace Game::Object {
|
||||
namespace {
|
||||
static SDL_AudioSpec makeFloatStereo44100() {
|
||||
SDL_AudioSpec spec{};
|
||||
spec.freq = 44100;
|
||||
spec.channels = 2;
|
||||
spec.format = SDL_AUDIO_F32;
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(std::string path, Format format, int volume)
|
||||
: mAudioBuffer(nullptr), mAudioLength(0), mAudioStream(nullptr), mVolume(volume) {
|
||||
if (format == Format::WAV) {
|
||||
SDL_AudioSpec wavSpec = makeFloatStereo44100();
|
||||
|
||||
if (!SDL_LoadWAV(path.c_str(), &wavSpec, &mAudioBuffer, &mAudioLength)) {
|
||||
ERROR("Failed to load WAV file: " << SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("WAV file loaded successfully");
|
||||
|
||||
SDL_AudioSpec spec = makeFloatStereo44100();
|
||||
mAudioStream = SDL_OpenAudioDeviceStream(
|
||||
Audio::Audio::getInstance().getAudioDevice(), &spec, nullptr, nullptr
|
||||
);
|
||||
if (!mAudioStream) {
|
||||
ERROR("Failed to create audio stream: " << SDL_GetError());
|
||||
SDL_free(mAudioBuffer);
|
||||
mAudioBuffer = nullptr;
|
||||
mAudioLength = 0;
|
||||
}
|
||||
} else {
|
||||
ERROR("Unsupported audio format");
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const Sound& other)
|
||||
: mAudioBuffer(nullptr), mAudioLength(0), mAudioStream(nullptr), mVolume(other.mVolume) {
|
||||
if (&other == this) return;
|
||||
|
||||
if (other.mAudioBuffer && other.mAudioLength > 0) {
|
||||
Uint8* newBuffer = static_cast<Uint8*>(SDL_malloc(other.mAudioLength));
|
||||
if (!newBuffer) {
|
||||
ERROR("Failed to allocate memory for audio buffer copy");
|
||||
return;
|
||||
}
|
||||
SDL_memcpy(newBuffer, other.mAudioBuffer, other.mAudioLength);
|
||||
mAudioBuffer = newBuffer;
|
||||
mAudioLength = other.mAudioLength;
|
||||
}
|
||||
|
||||
SDL_AudioSpec wavSpec = makeFloatStereo44100();
|
||||
mAudioStream = SDL_OpenAudioDeviceStream(
|
||||
Audio::Audio::getInstance().getAudioDevice(), &wavSpec, nullptr, nullptr
|
||||
);
|
||||
if (!mAudioStream) {
|
||||
ERROR("Failed to create audio stream for copied Sound: " << SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
Sound& Sound::operator=(const Sound& other) {
|
||||
if (&other == this) return *this;
|
||||
|
||||
Uint8* newBuffer = nullptr;
|
||||
Uint32 newLength = 0;
|
||||
|
||||
if (other.mAudioBuffer && other.mAudioLength > 0) {
|
||||
newBuffer = static_cast<Uint8*>(SDL_malloc(other.mAudioLength));
|
||||
if (!newBuffer) {
|
||||
ERROR("Failed to allocate memory for audio buffer copy");
|
||||
return *this;
|
||||
}
|
||||
SDL_memcpy(newBuffer, other.mAudioBuffer, other.mAudioLength);
|
||||
newLength = other.mAudioLength;
|
||||
}
|
||||
|
||||
SDL_AudioStream* newStream = nullptr;
|
||||
SDL_AudioSpec wavSpec = makeFloatStereo44100();
|
||||
newStream = SDL_OpenAudioDeviceStream(
|
||||
Audio::Audio::getInstance().getAudioDevice(), &wavSpec, nullptr, nullptr
|
||||
);
|
||||
if (!newStream) {
|
||||
ERROR("Failed to create audio stream for copied Sound: " << SDL_GetError());
|
||||
if (newBuffer) SDL_free(newBuffer);
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (mAudioBuffer) SDL_free(mAudioBuffer);
|
||||
if (mAudioStream && SDL_WasInit(SDL_INIT_AUDIO)) SDL_DestroyAudioStream(mAudioStream);
|
||||
|
||||
mAudioBuffer = newBuffer;
|
||||
mAudioLength = newLength;
|
||||
mAudioStream = newStream;
|
||||
mVolume = other.mVolume;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sound::Sound(Sound&& other)
|
||||
: mAudioBuffer(other.mAudioBuffer),
|
||||
mAudioLength(other.mAudioLength),
|
||||
mAudioStream(other.mAudioStream),
|
||||
mVolume(other.mVolume) {
|
||||
other.mAudioBuffer = nullptr;
|
||||
other.mAudioLength = 0;
|
||||
other.mAudioStream = nullptr;
|
||||
}
|
||||
|
||||
Sound& Sound::operator=(Sound&& other) {
|
||||
if (this != &other) {
|
||||
if (mAudioBuffer) SDL_free(mAudioBuffer);
|
||||
if (mAudioStream && SDL_WasInit(SDL_INIT_AUDIO)) SDL_DestroyAudioStream(mAudioStream);
|
||||
|
||||
mAudioBuffer = other.mAudioBuffer;
|
||||
mAudioLength = other.mAudioLength;
|
||||
mAudioStream = other.mAudioStream;
|
||||
mVolume = other.mVolume;
|
||||
|
||||
other.mAudioBuffer = nullptr;
|
||||
other.mAudioLength = 0;
|
||||
other.mAudioStream = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sound::~Sound() {
|
||||
if (mAudioBuffer) {
|
||||
SDL_free(mAudioBuffer);
|
||||
mAudioBuffer = nullptr;
|
||||
}
|
||||
if (mAudioStream) {
|
||||
if (SDL_WasInit(SDL_INIT_AUDIO)) {
|
||||
SDL_DestroyAudioStream(mAudioStream);
|
||||
}
|
||||
mAudioStream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::play() {
|
||||
if (mAudioStream && mAudioBuffer && mAudioLength > 0) {
|
||||
if (!SDL_PutAudioStreamData(mAudioStream, mAudioBuffer, mAudioLength)) {
|
||||
ERROR("Failed to queue audio data: " << SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <renderer/texture.hpp>
|
||||
#include <utility>
|
||||
|
||||
Game::Renderer::Texture::Texture(std::string id)
|
||||
namespace Game::Renderer {
|
||||
Texture::Texture(std::string id)
|
||||
: mTex(nullptr), mId(std::move(id)) {}
|
||||
|
||||
Game::Renderer::Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id)
|
||||
Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id)
|
||||
: mTex(nullptr), mId(id) {
|
||||
SDL_Surface* surf = IMG_Load(path.c_str());
|
||||
if (!surf) {
|
||||
@@ -14,29 +15,44 @@ Game::Renderer::Texture::Texture(const std::string& path, SDL_Renderer* renderer
|
||||
|
||||
mTex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||
SDL_DestroySurface(surf);
|
||||
}
|
||||
}
|
||||
|
||||
Game::Renderer::Texture::Texture(const Texture& other) {
|
||||
Texture::Texture(const Texture& other) {
|
||||
// Copy the references, since copying memory would require re-initing a bunch of things - for now
|
||||
this->mTex = other.mTex;
|
||||
}
|
||||
}
|
||||
|
||||
Game::Renderer::Texture& Game::Renderer::Texture::operator=(const Texture& other) {
|
||||
Texture& Texture::operator=(const Texture& other) {
|
||||
// Same reasoning
|
||||
this->mTex = other.mTex;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
Game::Renderer::Texture::~Texture() {
|
||||
Texture::~Texture() {
|
||||
if (mTex)
|
||||
SDL_DestroyTexture(mTex);
|
||||
LOG("Destroyed texture '" << mId << "'")
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Texture* Game::Renderer::Texture::getSDLTexture() {
|
||||
SDL_Texture* Texture::getSDLTexture() {
|
||||
return mTex;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Game::Renderer::Texture::getId() {
|
||||
std::string Texture::getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
float Texture::getWidth() {
|
||||
if (!mTex) return 0.f;
|
||||
float width;
|
||||
SDL_GetTextureSize(mTex, &width, nullptr);
|
||||
return width;
|
||||
}
|
||||
|
||||
float Texture::getHeight() {
|
||||
if (!mTex) return 0.f;
|
||||
float height;
|
||||
SDL_GetTextureSize(mTex, nullptr, &height);
|
||||
return height;
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ namespace Game::Window {
|
||||
return false;
|
||||
}
|
||||
|
||||
Audio::Audio::getInstance().init();
|
||||
|
||||
mWindow = SDL_CreateWindow(title.c_str(), width, height, SDL_WINDOW_RESIZABLE);
|
||||
if (!mWindow) {
|
||||
ERROR("Failed to create window: " << SDL_GetError());
|
||||
|
||||
Reference in New Issue
Block a user