diff --git a/CMakeLists.txt b/CMakeLists.txt index f2f2b7c..cfa8d38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,8 @@ endif() include_directories("include" ${uuid_SOURCE_DIR}/include) add_library(ReMixer ${SOURCES} - src/linux/PulseSubsystem.cpp) + src/linux/PulseSubsystem.cpp + src/linux/SoundSubsystem.cpp) add_executable(ReMixer-Test main.cpp) set_target_properties(ReMixer PROPERTIES LINKER_LANGUAGE CXX) diff --git a/cmake-build-debug/blm.ogg b/cmake-build-debug/blm.ogg deleted file mode 100644 index 61732f6..0000000 Binary files a/cmake-build-debug/blm.ogg and /dev/null differ diff --git a/cmake-build-debug/click.ogg b/cmake-build-debug/click.ogg new file mode 100644 index 0000000..ae5e2a0 Binary files /dev/null and b/cmake-build-debug/click.ogg differ diff --git a/cmake-build-debug/output.raw b/cmake-build-debug/output.raw new file mode 100644 index 0000000..5f08b04 Binary files /dev/null and b/cmake-build-debug/output.raw differ diff --git a/cmake-build-debug/wind.ogg b/cmake-build-debug/wind.ogg new file mode 100644 index 0000000..df901a3 Binary files /dev/null and b/cmake-build-debug/wind.ogg differ diff --git a/cmake-build-debug/wind.raw b/cmake-build-debug/wind.raw new file mode 100644 index 0000000..7781be0 Binary files /dev/null and b/cmake-build-debug/wind.raw differ diff --git a/include/ReMixer/PulseSubsystem.h b/include/ReMixer/PulseSubsystem.h index 5b7f3b7..d26a219 100644 --- a/include/ReMixer/PulseSubsystem.h +++ b/include/ReMixer/PulseSubsystem.h @@ -13,194 +13,220 @@ #include #include - - -/// A PulseAudio implementation of the SoundSubsystem. -class PulseAudioSubsystem : public SoundSubsystem +namespace ReMixer { - struct pa_impl + /// A PulseAudio implementation of the SoundSubsystem. + class PulseAudioSubsystem : public SoundSubsystem { - pa_threaded_mainloop* mainloop; - pa_context* context; - pa_stream_direction_t* direction; - pa_mainloop_api* api; - - const void* read_data; - size_t read_index, read_length; - - int operation_success; - }; - std::map streams; -public: - /// Constructs a new PulseAudio context with the given name. - /// @param name The context name. All streams created with this context will be grouped together by this name. - PulseAudioSubsystem(const std::string& context_name) : SoundSubsystem() - { - const char *server = NULL; - pa = pa_xnew(pa_impl, 1); - - int error = PA_ERR_INTERNAL; - - if (!(pa->mainloop = pa_threaded_mainloop_new())) + struct pa_impl { - throw std::runtime_error("Failed to create a PulseAudio mainloop."); - } + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_stream_direction_t* direction; + pa_mainloop_api* api; - pa->api = pa_threaded_mainloop_get_api(pa->mainloop); + const void* read_data; + size_t read_index, read_length; - if (!(pa->context = pa_context_new(pa->api, context_name.c_str()))) + int operation_success; + }; + std::map streams; + public: + /// Constructs a new PulseAudio context with the given name. + /// @param name The context name. All streams created with this context will be grouped together by this name. + PulseAudioSubsystem(const std::string& context_name) : SoundSubsystem() { - throw std::runtime_error("Failed to create a PulseAudio context."); - } + const char *server = NULL; + pa = pa_xnew(pa_impl, 1); - pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN; + int error = PA_ERR_INTERNAL; - pa_context_set_state_callback(pa->context, OnCtxStateChange, pa); - - - if (pa_context_connect(pa->context, server, flags, NULL) < 0) - { - error = pa_context_errno(pa->context); - throw std::runtime_error(std::format("Failed to connect to context {}", error)); - } else{ - ctx_connected = true; - } - - pa_threaded_mainloop_lock(pa->mainloop); - - if (pa_threaded_mainloop_start(pa->mainloop) < 0) { - throw std::runtime_error("Failed to start thread mainloop."); - } - - for (;;) { - pa_context_state_t state; - state = pa_context_get_state(pa->context); - - if (state == PA_CONTEXT_READY) - break; - - if (!PA_CONTEXT_IS_GOOD(state)) { - error = pa_context_errno(pa->context); - - throw std::runtime_error(std::format("Context was not good: {}", error)); + if (!(pa->mainloop = pa_threaded_mainloop_new())) + { + throw std::runtime_error("Failed to create a PulseAudio mainloop."); } - // Wait until the context is ready. - pa_threaded_mainloop_wait(pa->mainloop); - } - pa_threaded_mainloop_unlock(pa->mainloop); - std::cout << "PulseAudio context is ready." << std::endl; - } + pa->api = pa_threaded_mainloop_get_api(pa->mainloop); - bool Connected() - { - return ctx_connected; - } + if (!(pa->context = pa_context_new(pa->api, context_name.c_str()))) + { + throw std::runtime_error("Failed to create a PulseAudio context."); + } + + pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN; + + pa_context_set_state_callback(pa->context, OnCtxStateChange, pa); - PulseStream CreateStream(const std::string& stream_name) - { + if (pa_context_connect(pa->context, server, flags, NULL) < 0) + { + error = pa_context_errno(pa->context); + throw std::runtime_error(std::format("Failed to connect to context {}", error)); + } else{ + ctx_connected = true; + } - // Assumed defaults - // TODO: Expose to the API later - static const pa_sample_spec ss = { - .format = PA_SAMPLE_S16LE, - .rate = 44100, - .channels = 2 - }; + pa_threaded_mainloop_lock(pa->mainloop); - const pa_cvolume* volume = NULL; - pa_channel_map* map = NULL; - const char* dev = NULL; - pa_buffer_attr* attr = NULL; + if (pa_threaded_mainloop_start(pa->mainloop) < 0) { + throw std::runtime_error("Failed to start thread mainloop."); + } - PulseStream new_stream = PulseStream(); - pa_threaded_mainloop_lock(pa->mainloop); + for (;;) { + pa_context_state_t state; + state = pa_context_get_state(pa->context); - if (!(new_stream.stream = pa_stream_new(pa->context, stream_name.c_str(), &ss, map))) - { - throw std::runtime_error("Failed to create a new PulseAudio stream."); + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) { + error = pa_context_errno(pa->context); + + throw std::runtime_error(std::format("Context was not good: {}", error)); + } + + // Wait until the context is ready. + pa_threaded_mainloop_wait(pa->mainloop); + } + pa_threaded_mainloop_unlock(pa->mainloop); + std::cout << "PulseAudio context is ready." << std::endl; } - pa_stream_set_state_callback(new_stream.stream, PulseStream::OnStreamStateChanged, pa->mainloop); - pa_stream_set_read_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop); - pa_stream_set_write_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop); - pa_stream_set_latency_update_callback(new_stream.stream, PulseStream::OnLatencyUpdate, pa->mainloop); - - pa_stream_flags flags = static_cast(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | - PA_STREAM_AUTO_TIMING_UPDATE); - - // TODO: Support Playback and Recording streams - int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL); - if (r < 0) + void Play(const PulseStream& stream, const Sound& sound) { - int error = pa_context_errno(pa->context); - throw std::runtime_error(std::format("Failed to connect the stream: {}", error)); + pa_threaded_mainloop_lock(pa->mainloop); + + size_t length = sound.DataSize(); + + const void* data = sound.ptr(); + + while(length > 0) + { + size_t l; + int r; + while(!(l = pa_stream_writable_size(stream.stream))) { + pa_threaded_mainloop_wait(pa->mainloop); + } + + if (l > length) + l = length; + + r = pa_stream_write(stream.stream, data, l, nullptr, 0LL, PA_SEEK_RELATIVE); + data = (const char*) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock(pa->mainloop); } - for (;;) + bool Connected() { - pa_stream_state_t state; - state = pa_stream_get_state(new_stream.stream); + return ctx_connected; + } - if (state == PA_STREAM_READY) - break; - if (!PA_STREAM_IS_GOOD(state)) + PulseStream CreateStream(const std::string& stream_name) + { + + // Assumed defaults + // TODO: Expose to the API later + static const pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 44100, + .channels = 2 + }; + + const pa_cvolume* volume = NULL; + pa_channel_map* map = NULL; + const char* dev = NULL; + pa_buffer_attr* attr = NULL; + + PulseStream new_stream = PulseStream(); + pa_threaded_mainloop_lock(pa->mainloop); + + if (!(new_stream.stream = pa_stream_new(pa->context, stream_name.c_str(), &ss, map))) + { + throw std::runtime_error("Failed to create a new PulseAudio stream."); + } + + pa_stream_set_state_callback(new_stream.stream, PulseStream::OnStreamStateChanged, pa->mainloop); + pa_stream_set_read_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop); + pa_stream_set_write_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop); + pa_stream_set_latency_update_callback(new_stream.stream, PulseStream::OnLatencyUpdate, pa->mainloop); + + pa_stream_flags flags = static_cast(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | + PA_STREAM_AUTO_TIMING_UPDATE); + + // TODO: Support Playback and Recording streams + int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL); + if (r < 0) { int error = pa_context_errno(pa->context); - throw std::runtime_error(std::format("Stream state is bad: {}", error)); + throw std::runtime_error(std::format("Failed to connect the stream: {}", error)); } - pa_threaded_mainloop_wait(pa->mainloop); + for (;;) + { + pa_stream_state_t state; + state = pa_stream_get_state(new_stream.stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + int error = pa_context_errno(pa->context); + throw std::runtime_error(std::format("Stream state is bad: {}", error)); + } + + pa_threaded_mainloop_wait(pa->mainloop); + } + + pa_threaded_mainloop_unlock(pa->mainloop); + + streams.insert({stream_name, new_stream}); + + return new_stream; } + protected: + private: + static void OnCtxStateChange(pa_context* c, void* userdata) + { + std::cout << "context state callback: "; - pa_threaded_mainloop_unlock(pa->mainloop); - - streams.insert({stream_name, new_stream}); - - return new_stream; - } -protected: -private: - static void OnCtxStateChange(pa_context* c, void* userdata) - { - std::cout << "context state callback: "; - - auto *p = static_cast(userdata); - switch(pa_context_get_state(c)) { - case PA_CONTEXT_READY: - std::cout << "Context Ready" << std::endl; - pa_threaded_mainloop_signal(p->mainloop, 0); - break; - case PA_CONTEXT_TERMINATED: - std::cout << "Context Terminated" << std::endl; - pa_threaded_mainloop_signal(p->mainloop, 0); - break; - case PA_CONTEXT_FAILED: - std::cout << "Context Failed" << std::endl; - pa_threaded_mainloop_signal(p->mainloop, 0); - break; - case PA_CONTEXT_UNCONNECTED: - std::cout << "Context Unconnected" << std::endl; - break; - case PA_CONTEXT_CONNECTING: - std::cout << "Context Connecting" << std::endl; - break; - case PA_CONTEXT_AUTHORIZING: - std::cout << "Context Authorizing" << std::endl; - break; - case PA_CONTEXT_SETTING_NAME: - std::cout << "Context Setting Name" << std::endl; - break; - default: - std::cerr << "The fuck?" << std::endl; - break; + auto *p = static_cast(userdata); + switch(pa_context_get_state(c)) { + case PA_CONTEXT_READY: + std::cout << "Context Ready" << std::endl; + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + case PA_CONTEXT_TERMINATED: + std::cout << "Context Terminated" << std::endl; + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + case PA_CONTEXT_FAILED: + std::cout << "Context Failed" << std::endl; + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + case PA_CONTEXT_UNCONNECTED: + std::cout << "Context Unconnected" << std::endl; + break; + case PA_CONTEXT_CONNECTING: + std::cout << "Context Connecting" << std::endl; + break; + case PA_CONTEXT_AUTHORIZING: + std::cout << "Context Authorizing" << std::endl; + break; + case PA_CONTEXT_SETTING_NAME: + std::cout << "Context Setting Name" << std::endl; + break; + default: + std::cerr << "The fuck?" << std::endl; + break; + } } - } - pa_impl* pa; - bool ctx_connected = false; - -}; - + pa_impl* pa; + bool ctx_connected = false; + }; +} \ No newline at end of file diff --git a/include/ReMixer/ReMixer.h b/include/ReMixer/ReMixer.h index 56280d5..239adad 100644 --- a/include/ReMixer/ReMixer.h +++ b/include/ReMixer/ReMixer.h @@ -1,11 +1,18 @@ #pragma once -#include -#include - namespace ReMixer { + enum class StreamDirection { + PLAYBACK, + RECORD, + }; + + enum class StreamMode : bool { + MONO = false, + STEREO = true + }; + unsigned operator ""_Hz(unsigned long long int frequency); + unsigned operator ""_kHz(long double frequency); + - uint16_t createStream(const std::string& application_name, const std::string& stream_name, SampleRate sample_rate, StreamMode mode); - SFX* loadSFX(const std::string& file); } diff --git a/include/ReMixer/SoundSubsystem.h b/include/ReMixer/SoundSubsystem.h index 7849b84..024234a 100644 --- a/include/ReMixer/SoundSubsystem.h +++ b/include/ReMixer/SoundSubsystem.h @@ -2,110 +2,114 @@ #include -/// A managed sound handler object. -class NuSound +namespace ReMixer { -public: - virtual bool IsPlaying() = 0; - virtual bool IsPaused() = 0; - virtual bool IsFinished() = 0; - float PlaybackLoudness(); - int ChannelCount(); - bool Looped(); - float PlaybackSpeed(); - float TimeLength(); - float TimePosition(); - float Volume(); + /// A managed sound handler object. + class NuSound + { + public: + virtual bool IsPlaying() = 0; + virtual bool IsPaused() = 0; + virtual bool IsFinished() = 0; + float PlaybackLoudness(); + int ChannelCount(); + bool Looped(); + float PlaybackSpeed(); + float TimeLength(); + float TimePosition(); + float Volume(); - void SetTimePosition(float timePos); - void SetVolume(float vol); - void SetPlaybackSpeed(float speed); + void SetTimePosition(float timePos); + void SetVolume(float vol); + void SetPlaybackSpeed(float speed); - virtual void Pause() = 0; - virtual void Play() = 0; - void Resume(); - void Stop(); + virtual void Pause() = 0; + virtual void Play() = 0; + void Resume(); + void Stop(); - Event DidLoop; - Event<> Ended; - Event<> Paused; - Event<> Played; - Event<> Resumed; -protected: -private: -}; + Event DidLoop; + Event<> Ended; + Event<> Paused; + Event<> Played; + Event<> Resumed; + protected: + private: + }; -/// TODO: Equalizer Effect -/// TODO: Compressor Effect -/// TODO: Reverb Effect -/// TODO: Chorus Effect -/// TODO: Distortion Effect -/// TODO: Echo Effect -/// TODO: Flange Effect -/// TODO: Pitch Shift Effect -/// TODO: Tremolo Effect -/// TODO: Positional Sound Object? + /// TODO: Equalizer Effect + /// TODO: Compressor Effect + /// TODO: Reverb Effect + /// TODO: Chorus Effect + /// TODO: Distortion Effect + /// TODO: Echo Effect + /// TODO: Flange Effect + /// TODO: Pitch Shift Effect + /// TODO: Tremolo Effect + /// TODO: Positional Sound Object? -/// Physical representation of Pulse-Code-Modulated sound data. -class PCM -{ + /// Physical representation of Pulse-Code-Modulated sound data. + class PCM + { -}; + }; -class Decibels -{ + class Decibels + { -}; + }; -enum class SoundApi -{ - PulseAudio, /// Currently supported. - ALSA, /// Not yet supported. - PipeWire, /// Not yet supported. - WASAPI, /// Work-in-progress support. - OSS, /// Not yet supported. - CoreAudio /// Support not planned. -}; + enum class SoundApi + { + PulseAudio, /// Currently supported. + ALSA, /// Not yet supported. + PipeWire, /// Not yet supported. + WASAPI, /// Work-in-progress support. + OSS, /// Not yet supported. + CoreAudio /// Support not planned. + }; -class AudioDevice {}; -class PlaybackDevice : public AudioDevice {}; -class RecordingDevice : public AudioDevice {}; + class AudioDevice {}; + class PlaybackDevice : public AudioDevice {}; + class RecordingDevice : public AudioDevice {}; -/// An abstract class that defines the SoundSubsystem which will be used by the higher-level ReMixer API. -/// Since it is common for sound APIs to operate on a separate (or even multiple) threads, -/// The subsystem also functions as a "controller" that manages these separate threads. -/// Therefore, it is safe to use in a single-threaded application. -class SoundSubsystem -{ -public: - std::vector GetAudioDevices(); - std::vector GetPlaybackDevices(); - std::vector GetRecordingDevices(); + /// An abstract class that defines the SoundSubsystem which will be used by the higher-level ReMixer API. + /// Since it is common for sound APIs to operate on a separate (or even multiple) threads, + /// The subsystem also functions as a "controller" that manages these separate threads. + /// Therefore, it is safe to use in a single-threaded application. + class SoundSubsystem + { + public: + std::vector GetAudioDevices(); + std::vector GetPlaybackDevices(); + std::vector GetRecordingDevices(); - std::string GetPlaybackDevice(); - std::string GetRecordingDevice(); - void SetPlaybackDevice(); - void SetRecordingDevice(); + std::string GetPlaybackDevice(); + std::string GetRecordingDevice(); + void SetPlaybackDevice(); + void SetRecordingDevice(); - void SetMasterVolume(Decibels db); + void SetMasterVolume(Decibels db); -protected: -private: + protected: + private: -}; + }; -/// A Windows Sound API implementation of the SoundSubsystem. -class WASAPISubsystem : public SoundSubsystem -{ -public: -protected: -private: -}; + /// A Windows Sound API implementation of the SoundSubsystem. + class WASAPISubsystem : public SoundSubsystem + { + public: + protected: + private: + }; + +} diff --git a/include/ReMixer/StreamManager.h b/include/ReMixer/StreamManager.h index e4d2b76..d0d7a66 100644 --- a/include/ReMixer/StreamManager.h +++ b/include/ReMixer/StreamManager.h @@ -17,20 +17,20 @@ class TakenStreamIDException : public std::exception }; -class StreamManager { -public: - static std::map streamList; +//class StreamManager { +//public: + //static std::map streamList; //StreamManager(); //StreamManager(const StreamManager&); // Get stream using handle - static Stream *GetStream(uint16_t handle) - { - return streamList.at(handle); - } + //static Stream *GetStream(uint16_t handle) + //{ + // return streamList.at(handle); + //} // Return Stream handle // TODO: - static Stream* CreateStream() + /*static Stream* CreateStream() { // Can we create the stream here and assign the handle on our side? @@ -45,4 +45,4 @@ public: auto begin() { return streamList.begin(); }; auto end() { return streamList.end(); }; -}; \ No newline at end of file +};*/ \ No newline at end of file diff --git a/include/ReMixer/sound.h b/include/ReMixer/sound.h index 6a4f660..cae606e 100644 --- a/include/ReMixer/sound.h +++ b/include/ReMixer/sound.h @@ -4,39 +4,42 @@ #include #include #include +#include +#include +#include + +namespace ReMixer +{ + class Sound { + private: + std::vector audio_data; + //SampleRate sample_rate; + StreamMode num_channels; + public: + + static Sound FromOGGVorbisFile(const std::filesystem::path& file_name); + static Sound FromPCMFile(const std::filesystem::path& file_name); + static Sound FromPCMBuffer(std::vector buffer); + void LoadOGGVorbisFile(const std::filesystem::path& file_name); + void LoadPCMFile(const std::filesystem::path& file_name); + void LoadPCMBuffer(std::vector buffer); - -class Sound { -private: - std::vector audio_data; - SampleRate sample_rate; - StreamMode num_channels; -public: - - static Sound FromOGGVorbisFile(const std::filesystem::path& file_name) - { - return Sound{}; - } - - std::vector getAudioData(); - void setAudioData(const std::vector& audioData); - void setNumberOfChannels(StreamMode mode); - void setSampleRate(SampleRate rate); - SampleRate getSampleRate(); - [[nodiscard]] unsigned int getNumberOfChannels() const; - char* ptr() { return audio_data.data();} - [[nodiscard]] const char* ptr() const { return audio_data.data();} - Sound() = default; -}; + unsigned int DataSize() const { return audio_data.size();}; + char* ptr() { return audio_data.data();} + [[nodiscard]] const char* ptr() const { return audio_data.data();} + Sound() = default; + }; /// Class that holds a short sound that can fit in memory and requires no latency. (i.e. footsteps or gun shots). -class SFX { + class SFX { -}; + }; /// Music class is for longer sounds that should be streamed in. -class Music { + class Music { + + }; +} -}; diff --git a/include/ReMixer/stream.h b/include/ReMixer/stream.h index 5c77768..22d6a31 100644 --- a/include/ReMixer/stream.h +++ b/include/ReMixer/stream.h @@ -1,6 +1,6 @@ /// ReMixer /// A Public Domain C++ Audio Playback Library -//By william @ RedactedSoftware. Thanks to Dawsh & Maxine. +/// By william @ RedactedSoftware. Thanks to Dawsh & Maxine. /// (c) 2024 Redacted Software /// This work is explicitly dedicated to the public domain, for the hopeful betterment of the software industry. @@ -14,107 +14,87 @@ #include #include #include +#include +#include "sound.h" +#include -enum class StreamMode : bool { - MONO = false, - STEREO = true -}; -unsigned operator ""_Hz(unsigned long long int frequency) +namespace ReMixer { - return frequency; -} -unsigned operator ""_kHz(long double frequency) -{ - return frequency * 1000; -} + class Stream { + private: + std::string name; + std::string parent_name; + unsigned int channel_count; + unsigned int sample_rate; + StreamDirection dir; -enum class StreamDirection { - PLAYBACK, - RECORD, -}; - -class Stream { -private: - - std::string name; - std::string parent_name; - unsigned int channel_count; - unsigned int sample_rate; - StreamDirection dir; - - std::vector buffer; -#ifdef __linux__ - pa_stream* stream = nullptr; -#endif - -#ifdef _WIN32 -#endif -public: - size_t bufferSize; - uint16_t handle; - //Stream(const std::string& application_name, const std::string& stream_name, SampleRate sample_rate, StreamMode mode); - unsigned int numChannels(); + std::vector buffer; - void test_play(const void *data, size_t size, int *err); -}; + public: + size_t bufferSize; + uint16_t handle; + //Stream(const std::string& application_name, const std::string& stream_name, SampleRate sample_rate, StreamMode mode); + }; -class PulseStream : public Stream -{ -public: - - - pa_stream* stream; - - static void OnStreamStateChanged(pa_stream* s, void* userdata) + class PulseStream : public Stream { - std::cout << "stream state callback: "; + public: + pa_stream* stream; - auto *p = static_cast(userdata); - auto state = pa_stream_get_state(s); - switch(state) { - case PA_STREAM_READY: - std::cout << "Stream Ready" << std::endl; - pa_threaded_mainloop_signal(p, 0); - break; - case PA_STREAM_FAILED: - std::cout << "Stream Failed" << std::endl; - pa_threaded_mainloop_signal(p, 0); - break; - case PA_STREAM_TERMINATED: - std::cout << "Stream Terminated" << std::endl; - pa_threaded_mainloop_signal(p, 0); - break; - case PA_STREAM_UNCONNECTED: - std::cout << "Stream Unconnected" << std::endl; - break; - case PA_STREAM_CREATING: - std::cout << "Stream Creating" << std::endl; - break; + + static void OnStreamStateChanged(pa_stream* s, void* userdata) + { + std::cout << "stream state callback: "; + + auto *p = static_cast(userdata); + + auto state = pa_stream_get_state(s); + switch(state) { + case PA_STREAM_READY: + std::cout << "Stream Ready" << std::endl; + pa_threaded_mainloop_signal(p, 0); + break; + case PA_STREAM_FAILED: + std::cout << "Stream Failed" << std::endl; + pa_threaded_mainloop_signal(p, 0); + break; + case PA_STREAM_TERMINATED: + std::cout << "Stream Terminated" << std::endl; + pa_threaded_mainloop_signal(p, 0); + break; + case PA_STREAM_UNCONNECTED: + std::cout << "Stream Unconnected" << std::endl; + break; + case PA_STREAM_CREATING: + std::cout << "Stream Creating" << std::endl; + break; + } } - } - static void OnStreamRequest(pa_stream* s, size_t length, void* userdata) - { - std::cout << "Received stream request callback: " << length << std::endl; + static void OnStreamRequest(pa_stream* s, size_t length, void* userdata) + { + std::cout << "Received stream request callback: " << length << std::endl; - auto *p = static_cast(userdata); + auto *p = static_cast(userdata); - pa_threaded_mainloop_signal(p, 0); - } + pa_threaded_mainloop_signal(p, 0); + } - static void OnLatencyUpdate(pa_stream* s, void* userdata) - { - std::cout << "Received stream latency update callback" << std::endl; - auto *p = static_cast(userdata); + static void OnLatencyUpdate(pa_stream* s, void* userdata) + { + std::cout << "Received stream latency update callback" << std::endl; + auto *p = static_cast(userdata); - pa_threaded_mainloop_signal(p, 0); - } -protected: -private: + pa_threaded_mainloop_signal(p, 0); + } + protected: + private: + + }; +} -}; diff --git a/main.cpp b/main.cpp index 1fa01d0..98b2aad 100644 --- a/main.cpp +++ b/main.cpp @@ -9,27 +9,36 @@ /// @edit 2024-08-06 #include -#include -#include -#include - #include #include #include #include #include +#include +#include +#include +#include + [[noreturn]] int main() { + using namespace ReMixer; using namespace std::chrono_literals; PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test"); PulseStream test_stream = test.CreateStream("Test Stream"); PulseStream test_stream2 = test.CreateStream("Another Test Stream"); + + Sound test_sound = Sound::FromPCMFile("output.raw"); + Sound ogg_test_sound = Sound::FromOGGVorbisFile("song.ogg"); + + test.Play(test_stream, test_sound); + //test.Play(test_stream2, ogg_test_sound); + while(true) { - std::this_thread::sleep_for(25ms); + //std::this_thread::sleep_for(25ms); } return 0; diff --git a/src/linux/ReMixer.cpp b/src/linux/ReMixer.cpp index 92a0b33..3b8eb81 100644 --- a/src/linux/ReMixer.cpp +++ b/src/linux/ReMixer.cpp @@ -1,11 +1,10 @@ #include -uint16_t ReMixer::createStream(const std::string& application_name, const std::string& stream_name, SampleRate sample_rate, StreamMode mode) { - auto s = Stream(application_name, stream_name, sample_rate, mode); - return s.handle; +unsigned ReMixer::operator ""_Hz(unsigned long long int frequency) { + return frequency; } -SFX* ReMixer::loadSFX(const std::string& file) { - //return new SFX(file); +unsigned ReMixer::operator ""_kHz(long double frequency) { + return frequency * 1000; } diff --git a/src/linux/SoundSubsystem.cpp b/src/linux/SoundSubsystem.cpp new file mode 100644 index 0000000..caee316 --- /dev/null +++ b/src/linux/SoundSubsystem.cpp @@ -0,0 +1,3 @@ +// +// Created by dawsh on 8/28/24. +// diff --git a/src/linux/sound.cpp b/src/linux/sound.cpp index fa92429..2a84958 100644 --- a/src/linux/sound.cpp +++ b/src/linux/sound.cpp @@ -1,64 +1,69 @@ #include #include #include +#include -std::vector Sound::getAudioData() { - return audio_data; + + + +ReMixer::Sound ReMixer::Sound::FromOGGVorbisFile(const std::filesystem::path &file_name) { + Sound sound; + sound.LoadOGGVorbisFile(file_name); + return sound; } -void Sound::setAudioData(const std::vector& audioData) { - audio_data = audioData; +ReMixer::Sound ReMixer::Sound::FromPCMFile(const std::filesystem::path &file_name) { + Sound sound; + sound.LoadPCMFile(file_name); + return sound; } -unsigned int Sound::getNumberOfChannels() const { - return (unsigned int) num_channels + 1; +ReMixer::Sound ReMixer::Sound::FromPCMBuffer(std::vector buffer) { + Sound sound; + sound.LoadPCMBuffer(buffer); + return sound; } -SampleRate Sound::getSampleRate() { - return sample_rate; +void ReMixer::Sound::LoadPCMFile(const std::filesystem::path &file_name) { + std::ifstream file(file_name, std::ios::binary | std::ios::ate); + file.unsetf(std::ios::skipws); + + file.seekg(0, std::ios::end); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + audio_data.reserve(size); + + audio_data.insert(audio_data.begin(), + std::istream_iterator(file), + std::istream_iterator()); } -void Sound::setNumberOfChannels(StreamMode mode) { - num_channels = mode; - +void ReMixer::Sound::LoadPCMBuffer(std::vector buffer) { + audio_data = buffer; } -void Sound::setSampleRate(SampleRate rate) { - sample_rate = rate; -} - -/* - * SFX::SFX(const std::string& sfx_file_path) { +void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) { OggVorbis_File vf; - FILE* inFile = fopen(sfx_file_path.c_str(), "rb"); + FILE* inFile = fopen(file_name.c_str(), "rb"); + if (!inFile) { + std::cerr << "Error opening input file." << std::endl; + return; + } - if (!inFile) - throw std::runtime_error(std::string("Couldn't open the input file " + sfx_file_path)); - - if (ov_open(inFile, &vf, nullptr, 0) < 0) - throw std::runtime_error(std::string("libVorbis couldn't open the input file " + sfx_file_path)); + if (ov_open(inFile, &vf, NULL, 0) < 0) { + std::cerr << "Error opening Ogg Vorbis file." << std::endl; + fclose(inFile); + return; + } vorbis_info* vi = ov_info(&vf, -1); char pcmout[4096]; - std::vector raw_audio; int current_section; long bytes; - while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0) - raw_audio.insert(raw_audio.end(), pcmout, pcmout + bytes); - - - if (vi->channels == 1) - setNumberOfChannels(StreamMode::MONO); - else if (vi->channels == 2) - setNumberOfChannels(StreamMode::STEREO); - - if (vi->rate == 44100) - setSampleRate(SampleRate::STANDARD); - else if (vi->rate == 48000) - setSampleRate(SampleRate::HIGH); - - ov_clear(&vf); -} -*/ + while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0) { + audio_data.insert(audio_data.end(), pcmout, pcmout + bytes); + } +} \ No newline at end of file diff --git a/test-sounds/output.raw b/test-sounds/output.raw new file mode 100644 index 0000000..5f08b04 Binary files /dev/null and b/test-sounds/output.raw differ diff --git a/test-sounds/wind.raw b/test-sounds/wind.raw new file mode 100644 index 0000000..7781be0 Binary files /dev/null and b/test-sounds/wind.raw differ