diff --git a/cmakeaudioplayer/CMakeLists.txt b/cmakeaudioplayer/CMakeLists.txt new file mode 100644 index 0000000..4e467b4 --- /dev/null +++ b/cmakeaudioplayer/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.10) +project(AudioPlayer) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Use pkg-config to find Vorbis +find_package(PkgConfig REQUIRED) +pkg_check_modules(VORBIS REQUIRED vorbis vorbisfile) +pkg_check_modules(OGG REQUIRED ogg) +find_package(OpenAL REQUIRED) + +add_library(audioplayer + src/AudioPlayer.cpp + include/AudioPlayer.hpp +) + +target_include_directories(audioplayer + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${OPENAL_INCLUDE_DIR} + ${VORBIS_INCLUDE_DIRS} + ${OGG_INCLUDE_DIRS} +) + +target_link_libraries(audioplayer + PUBLIC + ${OPENAL_LIBRARY} + ${VORBIS_LIBRARIES} + ${OGG_LIBRARIES} +) + +# Test executable +add_executable(test_audio examples/test_audio.cpp) +target_link_libraries(test_audio PRIVATE audioplayer stdc++fs) diff --git a/cmakeaudioplayer/examples/test_audio.cpp b/cmakeaudioplayer/examples/test_audio.cpp new file mode 100644 index 0000000..06635d6 --- /dev/null +++ b/cmakeaudioplayer/examples/test_audio.cpp @@ -0,0 +1,32 @@ +#include "AudioPlayer.hpp" +#include +#include +#include +#include + +int main() { + try { + AudioPlayer player; + const std::string filename = "Symphony No.6 (1st movement).ogg"; + + std::cout << "🔍 Looking for file: " << filename << " in sounds directory...\n"; + + if (!player.playFromSoundsDir(filename)) { + std::cout << "❌ Failed to play audio file\n"; + return 1; + } + + std::cout << "✅ Playing symphony...\n"; + + // Check status for 30 seconds + for (int i = 0; i < 30; ++i) { + std::cout << "📊 Status: " << (player.isPlaying() ? "Playing â–ļī¸" : "Stopped âšī¸") << "\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + return 0; + } catch (const std::exception& e) { + std::cerr << "❌ Error: " << e.what() << "\n"; + return 1; + } +} diff --git a/cmakeaudioplayer/include/AudioPlayer.hpp b/cmakeaudioplayer/include/AudioPlayer.hpp new file mode 100644 index 0000000..c8e2d12 --- /dev/null +++ b/cmakeaudioplayer/include/AudioPlayer.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class AudioPlayer { +public: + AudioPlayer(); + ~AudioPlayer(); + + bool playFromSoundsDir(const std::string& filename); + void stop(); + bool isPlaying() const; + +private: + ALCdevice* device; + ALCcontext* context; + ALuint source; + ALuint buffer; + bool playing; + + std::vector loadOgg(const std::string& filename); + std::string findFileInSounds(const std::string& filename); + bool isOggFile(const std::string& filename) const; +}; diff --git a/cmakeaudioplayer/src/AudioPlayer.cpp b/cmakeaudioplayer/src/AudioPlayer.cpp new file mode 100644 index 0000000..b31d295 --- /dev/null +++ b/cmakeaudioplayer/src/AudioPlayer.cpp @@ -0,0 +1,145 @@ +#include "AudioPlayer.hpp" +#include +#include +#include +#include +#include +#include + +AudioPlayer::AudioPlayer() : device(nullptr), context(nullptr), source(0), buffer(0), playing(false) { + device = alcOpenDevice(nullptr); + if (!device) { + throw std::runtime_error("Failed to open audio device"); + } + + context = alcCreateContext(device, nullptr); + if (!context) { + alcCloseDevice(device); + throw std::runtime_error("Failed to create audio context"); + } + + alcMakeContextCurrent(context); + alGenSources(1, &source); + alGenBuffers(1, &buffer); +} + +AudioPlayer::~AudioPlayer() { + if (source) + alDeleteSources(1, &source); + if (buffer) + alDeleteBuffers(1, &buffer); + + if (context) { + alcMakeContextCurrent(nullptr); + alcDestroyContext(context); + } + if (device) + alcCloseDevice(device); +} + +bool AudioPlayer::isOggFile(const std::string& filename) const { + std::string ext = std::filesystem::path(filename).extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return ext == ".ogg"; +} + +std::string AudioPlayer::findFileInSounds(const std::string& filename) { + // Check relative to executable location first (../../sounds) + std::filesystem::path soundsDir = std::filesystem::current_path() / ".." / ".." / "sounds"; + + // Fallback to ../sounds if not found + std::filesystem::path altSoundsDir = std::filesystem::current_path() / ".." / "sounds"; + + std::cout << "🔍 Searching for \"" << filename << "\" in:\n"; + std::cout << " " << soundsDir << "\n"; + std::cout << " " << altSoundsDir << "\n"; + + if (std::filesystem::exists(soundsDir / filename)) { + return (soundsDir / filename).string(); + } + + if (std::filesystem::exists(altSoundsDir / filename)) { + return (altSoundsDir / filename).string(); + } + + throw std::runtime_error("❌ File not found: " + filename); +} + +std::vector AudioPlayer::loadOgg(const std::string& filename) { + FILE* file = fopen(filename.c_str(), "rb"); + if (!file) { + throw std::runtime_error("Cannot open file: " + filename); + } + + OggVorbis_File vf; + if (ov_open_callbacks(file, &vf, nullptr, 0, OV_CALLBACKS_DEFAULT) < 0) { + fclose(file); + throw std::runtime_error("Input not an Ogg file: " + filename); + } + + vorbis_info* vi = ov_info(&vf, -1); + std::vector audioData; + char data[4096]; + int bitstream; + long bytes; + + do { + bytes = ov_read(&vf, data, sizeof(data), 0, 2, 1, &bitstream); + if (bytes > 0) { + audioData.insert(audioData.end(), data, data + bytes); + } + } while (bytes > 0); + + // Setup the buffer with the audio data + alBufferData(buffer, + (vi->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, + audioData.data(), + audioData.size(), + vi->rate); + + ov_clear(&vf); + return audioData; +} + +bool AudioPlayer::playFromSoundsDir(const std::string& filename) { + try { + if (!isOggFile(filename)) { + std::cerr << "❌ Error: File must be an .ogg file\n"; + return false; + } + + std::string fullPath = findFileInSounds(filename); + std::cout << "✅ Found file: " << fullPath << "\n"; + + auto audioData = loadOgg(fullPath); + alSourcei(source, AL_BUFFER, buffer); + + alGetError(); // Clear any previous errors + + std::cout << "â–ļī¸ Starting playback...\n"; + alSourcePlay(source); + + ALenum error = alGetError(); + if (error != AL_NO_ERROR) { + std::cerr << "❌ OpenAL error: " << error << std::endl; + return false; + } + + playing = true; + return true; + } catch (const std::exception& e) { + std::cerr << "❌ Error: " << e.what() << std::endl; + return false; + } +} + +void AudioPlayer::stop() { + alSourceStop(source); + playing = false; +} + +bool AudioPlayer::isPlaying() const { + ALint state; + alGetSourcei(source, AL_SOURCE_STATE, &state); + return state == AL_PLAYING; +} diff --git a/sounds/Symphony No.6 (1st movement).ogg b/sounds/Symphony No.6 (1st movement).ogg new file mode 100644 index 0000000..d527c9c Binary files /dev/null and b/sounds/Symphony No.6 (1st movement).ogg differ