summaryrefslogtreecommitdiffstats
path: root/src/audio_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core')
-rw-r--r--src/audio_core/CMakeLists.txt12
-rw-r--r--src/audio_core/audio_out.cpp17
-rw-r--r--src/audio_core/audio_out.h8
-rw-r--r--src/audio_core/buffer.h3
-rw-r--r--src/audio_core/cubeb_sink.cpp190
-rw-r--r--src/audio_core/cubeb_sink.h31
-rw-r--r--src/audio_core/null_sink.h27
-rw-r--r--src/audio_core/sink.h29
-rw-r--r--src/audio_core/sink_details.cpp44
-rw-r--r--src/audio_core/sink_details.h32
-rw-r--r--src/audio_core/sink_stream.h32
-rw-r--r--src/audio_core/stream.cpp35
-rw-r--r--src/audio_core/stream.h22
13 files changed, 456 insertions, 26 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index f00a55994..81121167d 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -2,10 +2,22 @@ add_library(audio_core STATIC
audio_out.cpp
audio_out.h
buffer.h
+ cubeb_sink.cpp
+ cubeb_sink.h
+ null_sink.h
stream.cpp
stream.h
+ sink.h
+ sink_details.cpp
+ sink_details.h
+ sink_stream.h
)
create_target_directory_groups(audio_core)
target_link_libraries(audio_core PUBLIC common core)
+
+if(ENABLE_CUBEB)
+ target_link_libraries(audio_core PRIVATE cubeb)
+ target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
+endif()
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 6d418a05b..77cedb6ba 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -3,13 +3,15 @@
// Refer to the license.txt file included.
#include "audio_core/audio_out.h"
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
#include "common/assert.h"
#include "common/logging/log.h"
namespace AudioCore {
/// Returns the stream format from the specified number of channels
-static Stream::Format ChannelsToStreamFormat(int num_channels) {
+static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
switch (num_channels) {
case 1:
return Stream::Format::Mono16;
@@ -24,11 +26,16 @@ static Stream::Format ChannelsToStreamFormat(int num_channels) {
return {};
}
-StreamPtr AudioOut::OpenStream(int sample_rate, int num_channels,
+StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels,
Stream::ReleaseCallback&& release_callback) {
- streams.push_back(std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
- std::move(release_callback)));
- return streams.back();
+ if (!sink) {
+ const SinkDetails& sink_details = GetSinkDetails("auto");
+ sink = sink_details.factory("");
+ }
+
+ return std::make_shared<Stream>(sample_rate, ChannelsToStreamFormat(num_channels),
+ std::move(release_callback),
+ sink->AcquireSinkStream(sample_rate, num_channels));
}
std::vector<u64> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) {
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h
index a86499d10..8d9b695d4 100644
--- a/src/audio_core/audio_out.h
+++ b/src/audio_core/audio_out.h
@@ -8,20 +8,19 @@
#include <vector>
#include "audio_core/buffer.h"
+#include "audio_core/sink.h"
#include "audio_core/stream.h"
#include "common/common_types.h"
namespace AudioCore {
-using StreamPtr = std::shared_ptr<Stream>;
-
/**
* Represents an audio playback interface, used to open and play audio streams
*/
class AudioOut {
public:
/// Opens a new audio stream
- StreamPtr OpenStream(int sample_rate, int num_channels,
+ StreamPtr OpenStream(u32 sample_rate, u32 num_channels,
Stream::ReleaseCallback&& release_callback);
/// Returns a vector of recently released buffers specified by tag for the specified stream
@@ -37,8 +36,7 @@ public:
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<u8>&& data);
private:
- /// Active audio streams on the interface
- std::vector<StreamPtr> streams;
+ SinkPtr sink;
};
} // namespace AudioCore
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h
index 874ec787e..4bf5fd58a 100644
--- a/src/audio_core/buffer.h
+++ b/src/audio_core/buffer.h
@@ -4,6 +4,7 @@
#pragma once
+#include <memory>
#include <vector>
#include "common/common_types.h"
@@ -34,4 +35,6 @@ private:
std::vector<u8> data;
};
+using BufferPtr = std::shared_ptr<Buffer>;
+
} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
new file mode 100644
index 000000000..34ae5b062
--- /dev/null
+++ b/src/audio_core/cubeb_sink.cpp
@@ -0,0 +1,190 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "audio_core/cubeb_sink.h"
+#include "audio_core/stream.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+class SinkStreamImpl final : public SinkStream {
+public:
+ SinkStreamImpl(cubeb* ctx, cubeb_devid output_device) : ctx{ctx} {
+ cubeb_stream_params params;
+ params.rate = 48000;
+ params.channels = GetNumChannels();
+ params.format = CUBEB_SAMPLE_S16NE;
+ params.layout = CUBEB_LAYOUT_STEREO;
+
+ u32 minimum_latency = 0;
+ if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
+ }
+
+ if (cubeb_stream_init(ctx, &stream_backend, "yuzu Audio Output", nullptr, nullptr,
+ output_device, &params, std::max(512u, minimum_latency),
+ &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback,
+ this) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
+ return;
+ }
+
+ if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
+ return;
+ }
+ }
+
+ ~SinkStreamImpl() {
+ if (!ctx) {
+ return;
+ }
+
+ if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
+ }
+
+ cubeb_stream_destroy(stream_backend);
+ }
+
+ void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) override {
+ if (!ctx) {
+ return;
+ }
+
+ queue.reserve(queue.size() + sample_count * GetNumChannels());
+
+ if (num_channels == 2) {
+ // Copy as-is
+ std::copy(samples, samples + sample_count * GetNumChannels(),
+ std::back_inserter(queue));
+ } else if (num_channels == 6) {
+ // Downsample 6 channels to 2
+ const size_t sample_count_copy_size = sample_count * num_channels * 2;
+ queue.reserve(sample_count_copy_size);
+ for (size_t i = 0; i < sample_count * num_channels; i += num_channels) {
+ queue.push_back(samples[i]);
+ queue.push_back(samples[i + 1]);
+ }
+ } else {
+ ASSERT_MSG(false, "Unimplemented");
+ }
+ }
+
+ u32 GetNumChannels() const {
+ // Only support 2-channel stereo output for now
+ return 2;
+ }
+
+private:
+ std::vector<std::string> device_list;
+
+ cubeb* ctx{};
+ cubeb_stream* stream_backend{};
+
+ std::vector<s16> queue;
+
+ static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
+ void* output_buffer, long num_frames);
+ static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
+};
+
+CubebSink::CubebSink(std::string target_device_name) {
+ if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
+ return;
+ }
+
+ if (target_device_name != auto_device_name && !target_device_name.empty()) {
+ cubeb_device_collection collection;
+ if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
+ LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
+ } else {
+ const auto collection_end{collection.device + collection.count};
+ const auto device{std::find_if(collection.device, collection_end,
+ [&](const cubeb_device_info& device) {
+ return target_device_name == device.friendly_name;
+ })};
+ if (device != collection_end) {
+ output_device = device->devid;
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ }
+ }
+}
+
+CubebSink::~CubebSink() {
+ if (!ctx) {
+ return;
+ }
+
+ for (auto& sink_stream : sink_streams) {
+ sink_stream.reset();
+ }
+
+ cubeb_destroy(ctx);
+}
+
+SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels) {
+ sink_streams.push_back(std::make_unique<SinkStreamImpl>(ctx, output_device));
+ return *sink_streams.back();
+}
+
+long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
+ void* output_buffer, long num_frames) {
+ SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data);
+ u8* buffer = reinterpret_cast<u8*>(output_buffer);
+
+ if (!impl) {
+ return {};
+ }
+
+ const size_t frames_to_write{
+ std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
+
+ memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels());
+ impl->queue.erase(impl->queue.begin(),
+ impl->queue.begin() + frames_to_write * impl->GetNumChannels());
+
+ if (frames_to_write < num_frames) {
+ // Fill the rest of the frames with silence
+ memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0,
+ (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels());
+ }
+
+ return num_frames;
+}
+
+void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
+
+std::vector<std::string> ListCubebSinkDevices() {
+ std::vector<std::string> device_list;
+ cubeb* ctx;
+
+ if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
+ return {};
+ }
+
+ cubeb_device_collection collection;
+ if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
+ LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
+ } else {
+ for (size_t i = 0; i < collection.count; i++) {
+ const cubeb_device_info& device = collection.device[i];
+ if (device.friendly_name) {
+ device_list.emplace_back(device.friendly_name);
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ }
+
+ cubeb_destroy(ctx);
+ return device_list;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
new file mode 100644
index 000000000..d07113f1f
--- /dev/null
+++ b/src/audio_core/cubeb_sink.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <cubeb/cubeb.h>
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class CubebSink final : public Sink {
+public:
+ explicit CubebSink(std::string device_id);
+ ~CubebSink() override;
+
+ SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) override;
+
+private:
+ cubeb* ctx{};
+ cubeb_devid output_device{};
+ std::vector<SinkStreamPtr> sink_streams;
+};
+
+std::vector<std::string> ListCubebSinkDevices();
+
+} // namespace AudioCore
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
new file mode 100644
index 000000000..2e04438f7
--- /dev/null
+++ b/src/audio_core/null_sink.h
@@ -0,0 +1,27 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class NullSink final : public Sink {
+public:
+ explicit NullSink(std::string){};
+ ~NullSink() override = default;
+
+ SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/) override {
+ return null_sink_stream;
+ }
+
+private:
+ struct NullSinkStreamImpl final : SinkStream {
+ void EnqueueSamples(u32 /*num_channels*/, const s16* /*samples*/,
+ size_t /*sample_count*/) override {}
+ } null_sink_stream;
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
new file mode 100644
index 000000000..d1bb98c3d
--- /dev/null
+++ b/src/audio_core/sink.h
@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "audio_core/sink_stream.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+constexpr char auto_device_name[] = "auto";
+
+/**
+ * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
+ * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
+ * They are dumb outputs.
+ */
+class Sink {
+public:
+ virtual ~Sink() = default;
+ virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels) = 0;
+};
+
+using SinkPtr = std::unique_ptr<Sink>;
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
new file mode 100644
index 000000000..955ba20fb
--- /dev/null
+++ b/src/audio_core/sink_details.cpp
@@ -0,0 +1,44 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+#include "audio_core/null_sink.h"
+#include "audio_core/sink_details.h"
+#ifdef HAVE_CUBEB
+#include "audio_core/cubeb_sink.h"
+#endif
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+// g_sink_details is ordered in terms of desirability, with the best choice at the top.
+const std::vector<SinkDetails> g_sink_details = {
+#ifdef HAVE_CUBEB
+ SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
+#endif
+ SinkDetails{"null", &std::make_unique<NullSink, std::string>,
+ [] { return std::vector<std::string>{"null"}; }},
+};
+
+const SinkDetails& GetSinkDetails(std::string sink_id) {
+ auto iter =
+ std::find_if(g_sink_details.begin(), g_sink_details.end(),
+ [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
+
+ if (sink_id == "auto" || iter == g_sink_details.end()) {
+ if (sink_id != "auto") {
+ LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
+ }
+ // Auto-select.
+ // g_sink_details is ordered in terms of desirability, with the best choice at the front.
+ iter = g_sink_details.begin();
+ }
+
+ return *iter;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
new file mode 100644
index 000000000..aa8aae1a9
--- /dev/null
+++ b/src/audio_core/sink_details.h
@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace AudioCore {
+
+class Sink;
+
+struct SinkDetails {
+ SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>(std::string)> factory_,
+ std::function<std::vector<std::string>()> list_devices_)
+ : id(id_), factory(factory_), list_devices(list_devices_) {}
+
+ /// Name for this sink.
+ const char* id;
+ /// A method to call to construct an instance of this type of sink.
+ std::function<std::unique_ptr<Sink>(std::string device_id)> factory;
+ /// A method to call to list available devices.
+ std::function<std::vector<std::string>()> list_devices;
+};
+
+extern const std::vector<SinkDetails> g_sink_details;
+
+const SinkDetails& GetSinkDetails(std::string sink_id);
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h
new file mode 100644
index 000000000..e7a3f01b0
--- /dev/null
+++ b/src/audio_core/sink_stream.h
@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+/**
+ * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
+ * expect the correct sample rate. They are dumb outputs.
+ */
+class SinkStream {
+public:
+ virtual ~SinkStream() = default;
+
+ /**
+ * Feed stereo samples to sink.
+ * @param num_channels Number of channels used.
+ * @param samples Samples in interleaved stereo PCM16 format.
+ * @param sample_count Number of samples.
+ */
+ virtual void EnqueueSamples(u32 num_channels, const s16* samples, size_t sample_count) = 0;
+};
+
+using SinkStreamPtr = std::unique_ptr<SinkStream>;
+
+} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 82bff4b9e..689f51a1d 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -7,30 +7,37 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
#include "audio_core/stream.h"
namespace AudioCore {
constexpr size_t MaxAudioBufferCount{32};
-/// Returns the sample size for the specified audio stream format
-static size_t SampleSizeFromFormat(Stream::Format format) {
+u32 Stream::GetNumChannels() const {
switch (format) {
- case Stream::Format::Mono16:
+ case Format::Mono16:
+ return 1;
+ case Format::Stereo16:
return 2;
- case Stream::Format::Stereo16:
- return 4;
- case Stream::Format::Multi51Channel16:
- return 12;
- };
-
+ case Format::Multi51Channel16:
+ return 6;
+ }
LOG_CRITICAL(Audio, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
return {};
}
-Stream::Stream(int sample_rate, Format format, ReleaseCallback&& release_callback)
- : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)} {
+u32 Stream::GetSampleSize() const {
+ return GetNumChannels() * 2;
+}
+
+Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
+ SinkStream& sink_stream)
+ : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
+ sink_stream{sink_stream} {
+
release_event = CoreTiming::RegisterEvent(
"Stream::Release", [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
}
@@ -45,7 +52,7 @@ void Stream::Stop() {
}
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
- const size_t num_samples{buffer.GetData().size() / SampleSizeFromFormat(format)};
+ const size_t num_samples{buffer.GetData().size() / GetSampleSize()};
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
}
@@ -68,6 +75,10 @@ void Stream::PlayNextBuffer() {
active_buffer = queued_buffers.front();
queued_buffers.pop();
+ sink_stream.EnqueueSamples(GetNumChannels(),
+ reinterpret_cast<const s16*>(active_buffer->GetData().data()),
+ active_buffer->GetData().size() / GetSampleSize());
+
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
}
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 5f43b0798..35253920e 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -10,14 +10,13 @@
#include <queue>
#include "audio_core/buffer.h"
+#include "audio_core/sink_stream.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core_timing.h"
namespace AudioCore {
-using BufferPtr = std::shared_ptr<Buffer>;
-
/**
* Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
*/
@@ -33,7 +32,8 @@ public:
/// Callback function type, used to change guest state on a buffer being released
using ReleaseCallback = std::function<void()>;
- Stream(int sample_rate, Format format, ReleaseCallback&& release_callback);
+ Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
+ SinkStream& sink_stream);
/// Plays the audio stream
void Play();
@@ -60,6 +60,17 @@ public:
return queued_buffers.size();
}
+ /// Gets the sample rate
+ u32 GetSampleRate() const {
+ return sample_rate;
+ }
+
+ /// Gets the number of channels
+ u32 GetNumChannels() const;
+
+ /// Gets the sample size in bytes
+ u32 GetSampleSize() const;
+
private:
/// Current state of the stream
enum class State {
@@ -76,7 +87,7 @@ private:
/// Gets the number of core cycles when the specified buffer will be released
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
- int sample_rate; ///< Sample rate of the stream
+ u32 sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream
ReleaseCallback release_callback; ///< Buffer release callback for the stream
State state{State::Stopped}; ///< Playback state of the stream
@@ -84,6 +95,9 @@ private:
BufferPtr active_buffer; ///< Actively playing buffer in the stream
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
+ SinkStream& sink_stream; ///< Output sink for the stream
};
+using StreamPtr = std::shared_ptr<Stream>;
+
} // namespace AudioCore