Functioning High-level ReMixer API (First sound played!!)
This commit is contained in:
@@ -38,7 +38,8 @@ endif()
|
|||||||
include_directories("include" ${uuid_SOURCE_DIR}/include)
|
include_directories("include" ${uuid_SOURCE_DIR}/include)
|
||||||
|
|
||||||
add_library(ReMixer ${SOURCES}
|
add_library(ReMixer ${SOURCES}
|
||||||
src/linux/PulseSubsystem.cpp)
|
src/linux/PulseSubsystem.cpp
|
||||||
|
src/linux/SoundSubsystem.cpp)
|
||||||
add_executable(ReMixer-Test main.cpp)
|
add_executable(ReMixer-Test main.cpp)
|
||||||
|
|
||||||
set_target_properties(ReMixer PROPERTIES LINKER_LANGUAGE CXX)
|
set_target_properties(ReMixer PROPERTIES LINKER_LANGUAGE CXX)
|
||||||
|
Binary file not shown.
BIN
cmake-build-debug/click.ogg
Normal file
BIN
cmake-build-debug/click.ogg
Normal file
Binary file not shown.
BIN
cmake-build-debug/output.raw
Normal file
BIN
cmake-build-debug/output.raw
Normal file
Binary file not shown.
BIN
cmake-build-debug/wind.ogg
Normal file
BIN
cmake-build-debug/wind.ogg
Normal file
Binary file not shown.
BIN
cmake-build-debug/wind.raw
Normal file
BIN
cmake-build-debug/wind.raw
Normal file
Binary file not shown.
@@ -13,194 +13,220 @@
|
|||||||
#include <format>
|
#include <format>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
namespace ReMixer
|
||||||
|
|
||||||
/// A PulseAudio implementation of the SoundSubsystem.
|
|
||||||
class PulseAudioSubsystem : public SoundSubsystem
|
|
||||||
{
|
{
|
||||||
struct pa_impl
|
/// A PulseAudio implementation of the SoundSubsystem.
|
||||||
|
class PulseAudioSubsystem : public SoundSubsystem
|
||||||
{
|
{
|
||||||
pa_threaded_mainloop* mainloop;
|
struct pa_impl
|
||||||
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<std::string, PulseStream> 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()))
|
|
||||||
{
|
{
|
||||||
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<std::string, PulseStream> 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->mainloop = pa_threaded_mainloop_new()))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to create a PulseAudio mainloop.");
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the context is ready.
|
pa->api = pa_threaded_mainloop_get_api(pa->mainloop);
|
||||||
pa_threaded_mainloop_wait(pa->mainloop);
|
|
||||||
}
|
|
||||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
|
||||||
std::cout << "PulseAudio context is ready." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Connected()
|
if (!(pa->context = pa_context_new(pa->api, context_name.c_str())))
|
||||||
{
|
{
|
||||||
return ctx_connected;
|
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
|
pa_threaded_mainloop_lock(pa->mainloop);
|
||||||
// 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;
|
if (pa_threaded_mainloop_start(pa->mainloop) < 0) {
|
||||||
pa_channel_map* map = NULL;
|
throw std::runtime_error("Failed to start thread mainloop.");
|
||||||
const char* dev = NULL;
|
}
|
||||||
pa_buffer_attr* attr = NULL;
|
|
||||||
|
|
||||||
PulseStream new_stream = PulseStream();
|
for (;;) {
|
||||||
pa_threaded_mainloop_lock(pa->mainloop);
|
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)))
|
if (state == PA_CONTEXT_READY)
|
||||||
{
|
break;
|
||||||
throw std::runtime_error("Failed to create a new PulseAudio stream.");
|
|
||||||
|
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);
|
void Play(const PulseStream& stream, const Sound& sound)
|
||||||
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_flags>(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);
|
pa_threaded_mainloop_lock(pa->mainloop);
|
||||||
throw std::runtime_error(std::format("Failed to connect the stream: {}", error));
|
|
||||||
|
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;
|
return ctx_connected;
|
||||||
state = pa_stream_get_state(new_stream.stream);
|
}
|
||||||
|
|
||||||
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_flags>(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);
|
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);
|
auto *p = static_cast<pa_impl *>(userdata);
|
||||||
|
switch(pa_context_get_state(c)) {
|
||||||
streams.insert({stream_name, new_stream});
|
case PA_CONTEXT_READY:
|
||||||
|
std::cout << "Context Ready" << std::endl;
|
||||||
return new_stream;
|
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||||
}
|
break;
|
||||||
protected:
|
case PA_CONTEXT_TERMINATED:
|
||||||
private:
|
std::cout << "Context Terminated" << std::endl;
|
||||||
static void OnCtxStateChange(pa_context* c, void* userdata)
|
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||||
{
|
break;
|
||||||
std::cout << "context state callback: ";
|
case PA_CONTEXT_FAILED:
|
||||||
|
std::cout << "Context Failed" << std::endl;
|
||||||
auto *p = static_cast<pa_impl *>(userdata);
|
pa_threaded_mainloop_signal(p->mainloop, 0);
|
||||||
switch(pa_context_get_state(c)) {
|
break;
|
||||||
case PA_CONTEXT_READY:
|
case PA_CONTEXT_UNCONNECTED:
|
||||||
std::cout << "Context Ready" << std::endl;
|
std::cout << "Context Unconnected" << std::endl;
|
||||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
break;
|
||||||
break;
|
case PA_CONTEXT_CONNECTING:
|
||||||
case PA_CONTEXT_TERMINATED:
|
std::cout << "Context Connecting" << std::endl;
|
||||||
std::cout << "Context Terminated" << std::endl;
|
break;
|
||||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
case PA_CONTEXT_AUTHORIZING:
|
||||||
break;
|
std::cout << "Context Authorizing" << std::endl;
|
||||||
case PA_CONTEXT_FAILED:
|
break;
|
||||||
std::cout << "Context Failed" << std::endl;
|
case PA_CONTEXT_SETTING_NAME:
|
||||||
pa_threaded_mainloop_signal(p->mainloop, 0);
|
std::cout << "Context Setting Name" << std::endl;
|
||||||
break;
|
break;
|
||||||
case PA_CONTEXT_UNCONNECTED:
|
default:
|
||||||
std::cout << "Context Unconnected" << std::endl;
|
std::cerr << "The fuck?" << std::endl;
|
||||||
break;
|
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;
|
||||||
pa_impl* pa;
|
bool ctx_connected = false;
|
||||||
bool ctx_connected = false;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
@@ -1,11 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ReMixer/stream.h>
|
|
||||||
#include <ReMixer/sound.h>
|
|
||||||
|
|
||||||
namespace ReMixer {
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,110 +2,114 @@
|
|||||||
|
|
||||||
#include <Event.h>
|
#include <Event.h>
|
||||||
|
|
||||||
/// A managed sound handler object.
|
namespace ReMixer
|
||||||
class NuSound
|
|
||||||
{
|
{
|
||||||
public:
|
/// A managed sound handler object.
|
||||||
virtual bool IsPlaying() = 0;
|
class NuSound
|
||||||
virtual bool IsPaused() = 0;
|
{
|
||||||
virtual bool IsFinished() = 0;
|
public:
|
||||||
float PlaybackLoudness();
|
virtual bool IsPlaying() = 0;
|
||||||
int ChannelCount();
|
virtual bool IsPaused() = 0;
|
||||||
bool Looped();
|
virtual bool IsFinished() = 0;
|
||||||
float PlaybackSpeed();
|
float PlaybackLoudness();
|
||||||
float TimeLength();
|
int ChannelCount();
|
||||||
float TimePosition();
|
bool Looped();
|
||||||
float Volume();
|
float PlaybackSpeed();
|
||||||
|
float TimeLength();
|
||||||
|
float TimePosition();
|
||||||
|
float Volume();
|
||||||
|
|
||||||
|
|
||||||
void SetTimePosition(float timePos);
|
void SetTimePosition(float timePos);
|
||||||
void SetVolume(float vol);
|
void SetVolume(float vol);
|
||||||
void SetPlaybackSpeed(float speed);
|
void SetPlaybackSpeed(float speed);
|
||||||
|
|
||||||
|
|
||||||
virtual void Pause() = 0;
|
virtual void Pause() = 0;
|
||||||
virtual void Play() = 0;
|
virtual void Play() = 0;
|
||||||
void Resume();
|
void Resume();
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
Event<int> DidLoop;
|
Event<int> DidLoop;
|
||||||
Event<> Ended;
|
Event<> Ended;
|
||||||
Event<> Paused;
|
Event<> Paused;
|
||||||
Event<> Played;
|
Event<> Played;
|
||||||
Event<> Resumed;
|
Event<> Resumed;
|
||||||
protected:
|
protected:
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// TODO: Equalizer Effect
|
/// TODO: Equalizer Effect
|
||||||
/// TODO: Compressor Effect
|
/// TODO: Compressor Effect
|
||||||
/// TODO: Reverb Effect
|
/// TODO: Reverb Effect
|
||||||
/// TODO: Chorus Effect
|
/// TODO: Chorus Effect
|
||||||
/// TODO: Distortion Effect
|
/// TODO: Distortion Effect
|
||||||
/// TODO: Echo Effect
|
/// TODO: Echo Effect
|
||||||
/// TODO: Flange Effect
|
/// TODO: Flange Effect
|
||||||
/// TODO: Pitch Shift Effect
|
/// TODO: Pitch Shift Effect
|
||||||
/// TODO: Tremolo Effect
|
/// TODO: Tremolo Effect
|
||||||
/// TODO: Positional Sound Object?
|
/// TODO: Positional Sound Object?
|
||||||
|
|
||||||
/// Physical representation of Pulse-Code-Modulated sound data.
|
/// Physical representation of Pulse-Code-Modulated sound data.
|
||||||
class PCM
|
class PCM
|
||||||
{
|
{
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Decibels
|
class Decibels
|
||||||
{
|
{
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SoundApi
|
enum class SoundApi
|
||||||
{
|
{
|
||||||
PulseAudio, /// Currently supported.
|
PulseAudio, /// Currently supported.
|
||||||
ALSA, /// Not yet supported.
|
ALSA, /// Not yet supported.
|
||||||
PipeWire, /// Not yet supported.
|
PipeWire, /// Not yet supported.
|
||||||
WASAPI, /// Work-in-progress support.
|
WASAPI, /// Work-in-progress support.
|
||||||
OSS, /// Not yet supported.
|
OSS, /// Not yet supported.
|
||||||
CoreAudio /// Support not planned.
|
CoreAudio /// Support not planned.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class AudioDevice {};
|
class AudioDevice {};
|
||||||
class PlaybackDevice : public AudioDevice {};
|
class PlaybackDevice : public AudioDevice {};
|
||||||
class RecordingDevice : public AudioDevice {};
|
class RecordingDevice : public AudioDevice {};
|
||||||
|
|
||||||
/// An abstract class that defines the SoundSubsystem which will be used by the higher-level ReMixer API.
|
/// 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,
|
/// 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.
|
/// The subsystem also functions as a "controller" that manages these separate threads.
|
||||||
/// Therefore, it is safe to use in a single-threaded application.
|
/// Therefore, it is safe to use in a single-threaded application.
|
||||||
class SoundSubsystem
|
class SoundSubsystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::vector<AudioDevice> GetAudioDevices();
|
std::vector<AudioDevice> GetAudioDevices();
|
||||||
std::vector<PlaybackDevice> GetPlaybackDevices();
|
std::vector<PlaybackDevice> GetPlaybackDevices();
|
||||||
std::vector<RecordingDevice> GetRecordingDevices();
|
std::vector<RecordingDevice> GetRecordingDevices();
|
||||||
|
|
||||||
std::string GetPlaybackDevice();
|
std::string GetPlaybackDevice();
|
||||||
std::string GetRecordingDevice();
|
std::string GetRecordingDevice();
|
||||||
void SetPlaybackDevice();
|
void SetPlaybackDevice();
|
||||||
void SetRecordingDevice();
|
void SetRecordingDevice();
|
||||||
|
|
||||||
|
|
||||||
void SetMasterVolume(Decibels db);
|
void SetMasterVolume(Decibels db);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A Windows Sound API implementation of the SoundSubsystem.
|
/// A Windows Sound API implementation of the SoundSubsystem.
|
||||||
class WASAPISubsystem : public SoundSubsystem
|
class WASAPISubsystem : public SoundSubsystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
protected:
|
protected:
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -17,20 +17,20 @@ class TakenStreamIDException : public std::exception
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class StreamManager {
|
//class StreamManager {
|
||||||
public:
|
//public:
|
||||||
static std::map<uint16_t, Stream *> streamList;
|
//static std::map<uint16_t, Stream *> streamList;
|
||||||
|
|
||||||
//StreamManager();
|
//StreamManager();
|
||||||
//StreamManager(const StreamManager&);
|
//StreamManager(const StreamManager&);
|
||||||
// Get stream using handle
|
// Get stream using handle
|
||||||
static Stream *GetStream(uint16_t handle)
|
//static Stream *GetStream(uint16_t handle)
|
||||||
{
|
//{
|
||||||
return streamList.at(handle);
|
// return streamList.at(handle);
|
||||||
}
|
//}
|
||||||
// Return Stream handle
|
// Return Stream handle
|
||||||
// TODO:
|
// TODO:
|
||||||
static Stream* CreateStream()
|
/*static Stream* CreateStream()
|
||||||
{
|
{
|
||||||
|
|
||||||
// Can we create the stream here and assign the handle on our side?
|
// Can we create the stream here and assign the handle on our side?
|
||||||
@@ -45,4 +45,4 @@ public:
|
|||||||
auto begin() { return streamList.begin(); };
|
auto begin() { return streamList.begin(); };
|
||||||
auto end() { return streamList.end(); };
|
auto end() { return streamList.end(); };
|
||||||
|
|
||||||
};
|
};*/
|
@@ -4,39 +4,42 @@
|
|||||||
#include <pulse/simple.h>
|
#include <pulse/simple.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <ReMixer/stream.h>
|
#include <ReMixer/stream.h>
|
||||||
|
#include <ReMixer/ReMixer.h>
|
||||||
|
#include <iterator>
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
|
||||||
|
namespace ReMixer
|
||||||
|
{
|
||||||
|
class Sound {
|
||||||
|
private:
|
||||||
|
std::vector<char> 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<char> buffer);
|
||||||
|
|
||||||
|
|
||||||
|
void LoadOGGVorbisFile(const std::filesystem::path& file_name);
|
||||||
|
void LoadPCMFile(const std::filesystem::path& file_name);
|
||||||
|
void LoadPCMBuffer(std::vector<char> buffer);
|
||||||
|
|
||||||
|
unsigned int DataSize() const { return audio_data.size();};
|
||||||
class Sound {
|
char* ptr() { return audio_data.data();}
|
||||||
private:
|
[[nodiscard]] const char* ptr() const { return audio_data.data();}
|
||||||
std::vector<char> audio_data;
|
Sound() = default;
|
||||||
SampleRate sample_rate;
|
};
|
||||||
StreamMode num_channels;
|
|
||||||
public:
|
|
||||||
|
|
||||||
static Sound FromOGGVorbisFile(const std::filesystem::path& file_name)
|
|
||||||
{
|
|
||||||
return Sound{};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<char> getAudioData();
|
|
||||||
void setAudioData(const std::vector<char>& 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Class that holds a short sound that can fit in memory and requires no latency. (i.e. footsteps or gun shots).
|
/// 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.
|
/// Music class is for longer sounds that should be streamed in.
|
||||||
class Music {
|
class Music {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
};
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
/// ReMixer
|
/// ReMixer
|
||||||
/// A Public Domain C++ Audio Playback Library
|
/// 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
|
/// (c) 2024 Redacted Software
|
||||||
/// This work is explicitly dedicated to the public domain, for the hopeful betterment of the software industry.
|
/// This work is explicitly dedicated to the public domain, for the hopeful betterment of the software industry.
|
||||||
|
|
||||||
@@ -14,107 +14,87 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <pulse/stream.h>
|
#include <pulse/stream.h>
|
||||||
#include <pulse/thread-mainloop.h>
|
#include <pulse/thread-mainloop.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include "sound.h"
|
||||||
|
#include <ReMixer/ReMixer.h>
|
||||||
|
|
||||||
enum class StreamMode : bool {
|
|
||||||
MONO = false,
|
|
||||||
STEREO = true
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned operator ""_Hz(unsigned long long int frequency)
|
namespace ReMixer
|
||||||
{
|
{
|
||||||
return frequency;
|
class Stream {
|
||||||
}
|
private:
|
||||||
unsigned operator ""_kHz(long double frequency)
|
std::string name;
|
||||||
{
|
std::string parent_name;
|
||||||
return frequency * 1000;
|
unsigned int channel_count;
|
||||||
}
|
unsigned int sample_rate;
|
||||||
|
StreamDirection dir;
|
||||||
|
|
||||||
enum class StreamDirection {
|
std::vector<char> buffer;
|
||||||
PLAYBACK,
|
|
||||||
RECORD,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Stream {
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
std::string parent_name;
|
|
||||||
unsigned int channel_count;
|
|
||||||
unsigned int sample_rate;
|
|
||||||
StreamDirection dir;
|
|
||||||
|
|
||||||
std::vector<char> 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();
|
|
||||||
|
|
||||||
|
|
||||||
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
|
class PulseStream : public Stream
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
|
|
||||||
pa_stream* stream;
|
|
||||||
|
|
||||||
static void OnStreamStateChanged(pa_stream* s, void* userdata)
|
|
||||||
{
|
{
|
||||||
std::cout << "stream state callback: ";
|
public:
|
||||||
|
pa_stream* stream;
|
||||||
|
|
||||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
|
||||||
|
|
||||||
auto state = pa_stream_get_state(s);
|
|
||||||
switch(state) {
|
static void OnStreamStateChanged(pa_stream* s, void* userdata)
|
||||||
case PA_STREAM_READY:
|
{
|
||||||
std::cout << "Stream Ready" << std::endl;
|
std::cout << "stream state callback: ";
|
||||||
pa_threaded_mainloop_signal(p, 0);
|
|
||||||
break;
|
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||||
case PA_STREAM_FAILED:
|
|
||||||
std::cout << "Stream Failed" << std::endl;
|
auto state = pa_stream_get_state(s);
|
||||||
pa_threaded_mainloop_signal(p, 0);
|
switch(state) {
|
||||||
break;
|
case PA_STREAM_READY:
|
||||||
case PA_STREAM_TERMINATED:
|
std::cout << "Stream Ready" << std::endl;
|
||||||
std::cout << "Stream Terminated" << std::endl;
|
pa_threaded_mainloop_signal(p, 0);
|
||||||
pa_threaded_mainloop_signal(p, 0);
|
break;
|
||||||
break;
|
case PA_STREAM_FAILED:
|
||||||
case PA_STREAM_UNCONNECTED:
|
std::cout << "Stream Failed" << std::endl;
|
||||||
std::cout << "Stream Unconnected" << std::endl;
|
pa_threaded_mainloop_signal(p, 0);
|
||||||
break;
|
break;
|
||||||
case PA_STREAM_CREATING:
|
case PA_STREAM_TERMINATED:
|
||||||
std::cout << "Stream Creating" << std::endl;
|
std::cout << "Stream Terminated" << std::endl;
|
||||||
break;
|
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)
|
static void OnStreamRequest(pa_stream* s, size_t length, void* userdata)
|
||||||
{
|
{
|
||||||
std::cout << "Received stream request callback: " << length << std::endl;
|
std::cout << "Received stream request callback: " << length << std::endl;
|
||||||
|
|
||||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||||
|
|
||||||
pa_threaded_mainloop_signal(p, 0);
|
pa_threaded_mainloop_signal(p, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnLatencyUpdate(pa_stream* s, void* userdata)
|
static void OnLatencyUpdate(pa_stream* s, void* userdata)
|
||||||
{
|
{
|
||||||
std::cout << "Received stream latency update callback" << std::endl;
|
std::cout << "Received stream latency update callback" << std::endl;
|
||||||
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
|
||||||
|
|
||||||
pa_threaded_mainloop_signal(p, 0);
|
pa_threaded_mainloop_signal(p, 0);
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
19
main.cpp
19
main.cpp
@@ -9,27 +9,36 @@
|
|||||||
/// @edit 2024-08-06
|
/// @edit 2024-08-06
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ReMixer/stream.h>
|
|
||||||
#include <ReMixer/sound.h>
|
|
||||||
#include <ReMixer/PulseSubsystem.h>
|
|
||||||
|
|
||||||
#include <vorbis/codec.h>
|
#include <vorbis/codec.h>
|
||||||
#include <vorbis/vorbisfile.h>
|
#include <vorbis/vorbisfile.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <ReMixer/stream.h>
|
||||||
|
#include <ReMixer/sound.h>
|
||||||
|
#include <ReMixer/PulseSubsystem.h>
|
||||||
|
#include <ReMixer/ReMixer.h>
|
||||||
|
|
||||||
|
|
||||||
[[noreturn]] int main() {
|
[[noreturn]] int main() {
|
||||||
|
|
||||||
|
using namespace ReMixer;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test");
|
PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test");
|
||||||
|
|
||||||
PulseStream test_stream = test.CreateStream("Test Stream");
|
PulseStream test_stream = test.CreateStream("Test Stream");
|
||||||
PulseStream test_stream2 = test.CreateStream("Another 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)
|
while(true)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(25ms);
|
//std::this_thread::sleep_for(25ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
#include <ReMixer/ReMixer.h>
|
#include <ReMixer/ReMixer.h>
|
||||||
|
|
||||||
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) {
|
unsigned ReMixer::operator ""_kHz(long double frequency) {
|
||||||
//return new SFX(file);
|
return frequency * 1000;
|
||||||
}
|
}
|
||||||
|
3
src/linux/SoundSubsystem.cpp
Normal file
3
src/linux/SoundSubsystem.cpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//
|
||||||
|
// Created by dawsh on 8/28/24.
|
||||||
|
//
|
@@ -1,64 +1,69 @@
|
|||||||
#include <ReMixer/sound.h>
|
#include <ReMixer/sound.h>
|
||||||
#include <vorbis/codec.h>
|
#include <vorbis/codec.h>
|
||||||
#include <vorbis/vorbisfile.h>
|
#include <vorbis/vorbisfile.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
std::vector<char> 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<char>& audioData) {
|
ReMixer::Sound ReMixer::Sound::FromPCMFile(const std::filesystem::path &file_name) {
|
||||||
audio_data = audioData;
|
Sound sound;
|
||||||
|
sound.LoadPCMFile(file_name);
|
||||||
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Sound::getNumberOfChannels() const {
|
ReMixer::Sound ReMixer::Sound::FromPCMBuffer(std::vector<char> buffer) {
|
||||||
return (unsigned int) num_channels + 1;
|
Sound sound;
|
||||||
|
sound.LoadPCMBuffer(buffer);
|
||||||
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleRate Sound::getSampleRate() {
|
void ReMixer::Sound::LoadPCMFile(const std::filesystem::path &file_name) {
|
||||||
return sample_rate;
|
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<char>(file),
|
||||||
|
std::istream_iterator<char>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sound::setNumberOfChannels(StreamMode mode) {
|
void ReMixer::Sound::LoadPCMBuffer(std::vector<char> buffer) {
|
||||||
num_channels = mode;
|
audio_data = buffer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sound::setSampleRate(SampleRate rate) {
|
void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
|
||||||
sample_rate = rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* SFX::SFX(const std::string& sfx_file_path) {
|
|
||||||
OggVorbis_File vf;
|
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)
|
if (ov_open(inFile, &vf, NULL, 0) < 0) {
|
||||||
throw std::runtime_error(std::string("Couldn't open the input file " + sfx_file_path));
|
std::cerr << "Error opening Ogg Vorbis file." << std::endl;
|
||||||
|
fclose(inFile);
|
||||||
if (ov_open(inFile, &vf, nullptr, 0) < 0)
|
return;
|
||||||
throw std::runtime_error(std::string("libVorbis couldn't open the input file " + sfx_file_path));
|
}
|
||||||
|
|
||||||
vorbis_info* vi = ov_info(&vf, -1);
|
vorbis_info* vi = ov_info(&vf, -1);
|
||||||
|
|
||||||
char pcmout[4096];
|
char pcmout[4096];
|
||||||
std::vector<char> raw_audio;
|
|
||||||
int current_section;
|
int current_section;
|
||||||
long bytes;
|
long bytes;
|
||||||
|
|
||||||
while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0)
|
while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0) {
|
||||||
raw_audio.insert(raw_audio.end(), pcmout, pcmout + bytes);
|
audio_data.insert(audio_data.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);
|
|
||||||
}
|
|
||||||
*/
|
|
BIN
test-sounds/output.raw
Normal file
BIN
test-sounds/output.raw
Normal file
Binary file not shown.
BIN
test-sounds/wind.raw
Normal file
BIN
test-sounds/wind.raw
Normal file
Binary file not shown.
Reference in New Issue
Block a user