diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_AudioPipeline.h b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_AudioPipeline.h new file mode 100644 index 0000000000..b20d490f55 --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_AudioPipeline.h @@ -0,0 +1,94 @@ +/* Audio Pipeline + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_AudioPipeline_H +#define PokemonAutomation_Kernels_AudioPipeline_H + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +enum class SampleFormat{ + UINT8, + SINT16, + SINT32, + FLOAT32, +}; + + + +class RawAudioStreamListener{ +public: + virtual ~RawAudioStreamListener() = default; + + RawAudioStreamListener( + SampleFormat sample_type, + size_t sample_rate, + size_t channels + ) + : m_sample_type(sample_type) + , m_channels(channels) + , m_sample_rate(sample_rate) + {} + + SampleFormat sample_type() const{ + return m_sample_type; + } + size_t channels() const{ + return m_channels; + } + size_t sample_rate() const{ + return m_sample_rate; + } + + virtual void push_samples(size_t frames, const void* data) = 0; + + +private: + const SampleFormat m_sample_type; + const size_t m_channels; + const size_t m_sample_rate; +}; + + + +class WorkingAudioStreamListener{ +public: + virtual ~WorkingAudioStreamListener() = default; + + WorkingAudioStreamListener( + size_t sample_rate, + size_t channels + ) + : m_channels(channels) + , m_sample_rate(sample_rate) + {} + + size_t channels() const{ + return m_channels; + } + size_t sample_rate() const{ + return m_sample_rate; + } + + virtual void push_samples(size_t samples, size_t stride, const float* data) = 0; + + +private: + const size_t m_channels; + const size_t m_sample_rate; +}; + + + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.cpp b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.cpp new file mode 100644 index 0000000000..81e5a5c5b0 --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.cpp @@ -0,0 +1,68 @@ +/* Audio Pipeline - ChannelMixer + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Containers/AlignedVector.tpp" +#include "Kernels/AudioChannelMixer/Kernels_AudioChannelMixer.h" +#include "Kernels_ChannelMixer.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +ChannelMixer::~ChannelMixer() = default; +ChannelMixer::ChannelMixer( + size_t sample_rate, + size_t in_channels, + size_t out_channels, + std::vector weight_matrix, + size_t buffer_size +) + : WorkingAudioStreamListener(sample_rate, in_channels) + , m_in_channels(in_channels) + , m_out_channels(out_channels) + , m_buffer_size(buffer_size) + , m_weight_matrix(std::move(weight_matrix)) + , m_buffer(out_channels * buffer_size) +{ + if (m_weight_matrix.size() != in_channels * out_channels){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Weight matrix size doesn't match."); + } +} + +void ChannelMixer::add_listener(WorkingAudioStreamListener& listener){ + if (sample_rate() != listener.sample_rate()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Sample rate doesn't match."); + } + if (m_out_channels != listener.channels()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Channel count doesn't match."); + } + m_listeners.add(listener); +} +void ChannelMixer::remove_listener(WorkingAudioStreamListener& listener){ + m_listeners.remove(listener); +} + +void ChannelMixer::push_samples(size_t samples, size_t stride, const float* data){ + AudioChannelMixer::mix_channels( + m_weight_matrix.data(), + samples, + m_out_channels, m_buffer_size, m_buffer.data(), + m_in_channels, stride, data + ); + m_listeners.run_method( + &WorkingAudioStreamListener::push_samples, + samples, m_buffer_size, m_buffer.data() + ); +} + + + +} +} +} diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.h b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.h new file mode 100644 index 0000000000..212bd4399b --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_ChannelMixer.h @@ -0,0 +1,53 @@ +/* Audio Pipeline - ChannelMixer + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_AudioPipeline_ChannelMixer_H +#define PokemonAutomation_Kernels_AudioPipeline_ChannelMixer_H + +#include +#include "Common/Cpp/ListenerSet.h" +#include "Common/Cpp/Containers/AlignedVector.h" +#include "Kernels_AudioPipeline.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +class ChannelMixer : public WorkingAudioStreamListener{ +public: + ~ChannelMixer(); + ChannelMixer( + size_t sample_rate, + size_t in_channels, + size_t out_channels, + std::vector weight_matrix, + size_t buffer_size = 4096 + 256 // 4k + break super-alignment + ); + + void add_listener(WorkingAudioStreamListener& listener); + void remove_listener(WorkingAudioStreamListener& listener); + + virtual void push_samples(size_t samples, size_t stride, const float* data) override; + + +private: + size_t m_in_channels; + size_t m_out_channels; + size_t m_buffer_size; + std::vector m_weight_matrix; + AlignedVector m_buffer; + ListenerSet m_listeners; +}; + + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.cpp b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.cpp new file mode 100644 index 0000000000..881250bfdc --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.cpp @@ -0,0 +1,149 @@ +/* Audio Pipeline - RawAudio2WorkingAudio + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Containers/AlignedVector.tpp" +#include "Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.h" +#include "Kernels/AudioChannelInterleave/Kernels_AudioChannelInterleave.h" +#include "Kernels_RawAudio2WorkingAudio.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +namespace{ + + +size_t sample_format_size(SampleFormat sample_type){ + switch (sample_type){ + case SampleFormat::UINT8: return sizeof(uint8_t); + case SampleFormat::SINT16: return sizeof(int16_t); + case SampleFormat::SINT32: return sizeof(int32_t); + case SampleFormat::FLOAT32: return sizeof(float); + default:; + } + return 0; +} + + +} + + + +template +void RawAudio2WorkingAudio::set_converter(){ + m_convert = [this](const void* data, size_t samples){ + Kernels::AudioStreamConverter::convert_from( + m_interleaved_floats.data(), (const Type*)data, samples + ); + }; +} + +RawAudio2WorkingAudio::~RawAudio2WorkingAudio() = default; +RawAudio2WorkingAudio::RawAudio2WorkingAudio( + SampleFormat sample_type, + size_t sample_rate, + size_t channels, + size_t buffer_size +) + : RawAudioStreamListener(sample_type, sample_rate, channels) + , m_buffer_size(buffer_size) +{ + // Single-channel float is pass-through. + if (sample_type == SampleFormat::FLOAT32 && channels == 1){ + return; + } + + switch (sample_type){ + case SampleFormat::UINT8: + set_converter(); + break; + case SampleFormat::SINT16: + set_converter(); + break; + case SampleFormat::SINT32: + set_converter(); + break; + default:; + } + + // Only allocate the interleaved buffer if we need to convert types. + if (m_convert != nullptr){ + m_interleaved_floats = AlignedVector(channels * buffer_size); + } + + // Only allocate the split buffer if we need to split channels. + if (channels != 1){ + m_split_floats = AlignedVector(channels * buffer_size); + } +} + +void RawAudio2WorkingAudio::add_listener(WorkingAudioStreamListener& listener){ + if (sample_rate() != listener.sample_rate()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Sample rate doesn't match."); + } + if (channels() != listener.channels()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Channel count doesn't match."); + } + m_listeners.add(listener); +} +void RawAudio2WorkingAudio::remove_listener(WorkingAudioStreamListener& listener){ + m_listeners.remove(listener); +} + +void RawAudio2WorkingAudio::push_samples(size_t frames, const void* data){ + // Single-channel float pass-through. + if (sample_type() == SampleFormat::FLOAT32 && channels() == 1){ + m_listeners.run_method( + &WorkingAudioStreamListener::push_samples, + frames, 0, (const float*)data + ); + return; + } + + const size_t sample_size = sample_format_size(sample_type()); + while (frames > 0){ + size_t block = std::min(frames, m_buffer_size); + + // Convert to float. (if needed) + const float* interleaved_floats; + if (m_convert){ + m_convert(data, block * channels()); + interleaved_floats = m_interleaved_floats.data(); + }else{ + interleaved_floats = (const float*)data; + } + + // Now split the channels. (if needed) + const float* split_floats; + if (channels() != 1){ + Kernels::AudioChannelInterleave::channel_deinterleave( + channels(), block, + m_buffer_size, m_split_floats.data(), + interleaved_floats + ); + split_floats = m_split_floats.data(); + }else{ + split_floats = interleaved_floats; + } + + m_listeners.run_method( + &WorkingAudioStreamListener::push_samples, + block, m_buffer_size, split_floats + ); + + frames -= block; + data = (const char*)data + block * channels() * sample_size; + } +} + + + +} +} +} diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.h b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.h new file mode 100644 index 0000000000..9e816043f4 --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.h @@ -0,0 +1,57 @@ +/* Audio Pipeline - RawAudio2WorkingAudio + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_AudioPipeline_RawAudio2WorkingAudio_H +#define PokemonAutomation_Kernels_AudioPipeline_RawAudio2WorkingAudio_H + +#include +#include "Common/Cpp/ListenerSet.h" +#include "Common/Cpp/Containers/AlignedVector.h" +#include "Kernels_AudioPipeline.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +class RawAudio2WorkingAudio : public RawAudioStreamListener{ +public: + ~RawAudio2WorkingAudio(); + RawAudio2WorkingAudio( + SampleFormat sample_type, + size_t sample_rate, + size_t channels, + size_t buffer_size = 4096 + 256 // 4k + break super-alignment + ); + + void add_listener(WorkingAudioStreamListener& listener); + void remove_listener(WorkingAudioStreamListener& listener); + + virtual void push_samples(size_t frames, const void* data) override; + + +private: + template + void set_converter(); + + +private: + size_t m_buffer_size; + std::function m_convert = nullptr; + + AlignedVector m_interleaved_floats; + AlignedVector m_split_floats; + ListenerSet m_listeners; +}; + + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.cpp b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.cpp new file mode 100644 index 0000000000..b873401e5b --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.cpp @@ -0,0 +1,142 @@ +/* Audio Pipeline - Kernels_WorkingAudio2RawAudio + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Containers/AlignedVector.tpp" +#include "Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.h" +#include "Kernels/AudioChannelInterleave/Kernels_AudioChannelInterleave.h" +#include "Kernels_WorkingAudio2RawAudio.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +template +size_t WorkingAudio2RawAudio::set_converter(){ + m_convert = [this](const void* data, size_t samples){ + Kernels::AudioStreamConverter::convert_to( + (const float*)data, (Type*)m_interleaved_raw.data(), samples, + m_multiplier.load(std::memory_order_acquire) + ); + }; + return sizeof(Type); +} + + +WorkingAudio2RawAudio::~WorkingAudio2RawAudio() = default; +WorkingAudio2RawAudio::WorkingAudio2RawAudio( + SampleFormat sample_type, + size_t sample_rate, + size_t channels, + float multiplier, + size_t buffer_size +) + : WorkingAudioStreamListener(sample_rate, channels) + , m_sample_type(sample_type) + , m_multiplier(multiplier) + , m_buffer_size(buffer_size) +{ + // Single-channel float is pass-through. + if (sample_type == SampleFormat::FLOAT32 && channels == 1){ + return; + } + + size_t sample_size = sizeof(float); + switch (sample_type){ + case SampleFormat::UINT8: + sample_size = set_converter(); + break; + case SampleFormat::SINT16: + sample_size = set_converter(); + break; + case SampleFormat::SINT32: + sample_size = set_converter(); + break; + default:; + } + + // Only allocate the interleaved buffer if we need to convert types. + if (m_convert != nullptr){ + m_interleaved_raw = AlignedVector(channels * buffer_size * sample_size); + } + + // Only allocate the split buffer if we need to split channels. + if (channels != 1){ + m_interleaved_floats = AlignedVector(channels * buffer_size); + } +} + +void WorkingAudio2RawAudio::add_listener(RawAudioStreamListener& listener){ + if (m_sample_type != listener.sample_type()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Sample type doesn't match."); + } + if (sample_rate() != listener.sample_rate()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Sample rate doesn't match."); + } + if (channels() != listener.channels()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Channel count doesn't match."); + } + m_listeners.add(listener); +} +void WorkingAudio2RawAudio::remove_listener(RawAudioStreamListener& listener){ + m_listeners.remove(listener); +} +void WorkingAudio2RawAudio::set_multiplier(float multiplier){ + m_multiplier.store(multiplier, std::memory_order_release); +} + +void WorkingAudio2RawAudio::push_samples(size_t samples, size_t stride, const float* data){ + // Single-channel float pass-through. + if (m_sample_type == SampleFormat::FLOAT32 && channels() == 1){ + m_listeners.run_method( + &RawAudioStreamListener::push_samples, + samples, (const float*)data + ); + return; + } + + while (samples > 0){ + size_t block = std::min(samples, m_buffer_size); + + // Combine the channels. (if needed) + const float* interleaved_floats; + if (channels() != 1){ + Kernels::AudioChannelInterleave::channel_interleave( + channels(), block, + stride, data, + m_interleaved_floats.data() + ); + interleaved_floats = m_interleaved_floats.data(); + }else{ + interleaved_floats = data; + } + + // Convert to output type. (if needed) + const void* interleaved_raw; + if (m_convert){ + m_convert(interleaved_floats, block * channels()); + interleaved_raw = m_interleaved_raw.data(); + }else{ + interleaved_raw = interleaved_floats; + } + + m_listeners.run_method( + &RawAudioStreamListener::push_samples, + block, interleaved_raw + ); + + samples -= block; + data += block * stride; + } +} + + + +} +} +} diff --git a/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.h b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.h new file mode 100644 index 0000000000..cb22b9dd2c --- /dev/null +++ b/SerialPrograms/Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.h @@ -0,0 +1,64 @@ +/* Audio Pipeline - Kernels_WorkingAudio2RawAudio + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_AudioPipeline_WorkingAudio2RawAudio_H +#define PokemonAutomation_Kernels_AudioPipeline_WorkingAudio2RawAudio_H + +#include +#include +#include "Common/Cpp/ListenerSet.h" +#include "Common/Cpp/Containers/AlignedVector.h" +#include "Kernels_AudioPipeline.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace AudioPipeline{ + + + +class WorkingAudio2RawAudio : public WorkingAudioStreamListener{ +public: + ~WorkingAudio2RawAudio(); + WorkingAudio2RawAudio( + SampleFormat sample_type, + size_t sample_rate, + size_t channels, + float multiplier, + size_t buffer_size = 4096 + 256 // 4k + break super-alignment + ); + + void add_listener(RawAudioStreamListener& listener); + void remove_listener(RawAudioStreamListener& listener); + + void set_multiplier(float multiplier); + + virtual void push_samples(size_t samples, size_t stride, const float* data) override; + + +private: + template + size_t set_converter(); + + +private: + SampleFormat m_sample_type; + std::atomic m_multiplier; + size_t m_buffer_size; + std::function m_convert = nullptr; + + AlignedVector m_interleaved_raw; + AlignedVector m_interleaved_floats; + ListenerSet m_listeners; +}; + + + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.cpp b/SerialPrograms/Source/Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.cpp index 5b9727a549..de72f612d9 100644 --- a/SerialPrograms/Source/Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.cpp +++ b/SerialPrograms/Source/Kernels/AudioStreamConverter/Kernels_AudioStreamConverter.cpp @@ -97,6 +97,17 @@ void convert_to(c_ptr native, r_ptr ext, size_t samples, fl +template void convert_from(r_ptr native, c_ptr ext, size_t samples); +template void convert_from(r_ptr native, c_ptr ext, size_t samples); +template void convert_from(r_ptr native, c_ptr ext, size_t samples); +template void convert_to(c_ptr native, r_ptr ext, size_t samples, float multiplier); +template void convert_to(c_ptr native, r_ptr ext, size_t samples, float multiplier); +template void convert_to(c_ptr native, r_ptr ext, size_t samples, float multiplier); + + + + + void add_tests(UnitTestDatabase& database){ add_tests_Default(database); #ifdef PA_AutoDispatch_x64_08_Nehalem diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 5893eb0f1d..397ef04db3 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1017,6 +1017,13 @@ file(GLOB LIBRARY_SOURCES Source/Kernels/AudioChannelMixer/Kernels_AudioChannelMixer_x64_AVX512.cpp Source/Kernels/AudioChannelMixer/Kernels_AudioChannelMixer_x64_FMA3.cpp Source/Kernels/AudioChannelMixer/Kernels_AudioChannelMixer_x64_SSE.cpp + Source/Kernels/AudioPipeline/Kernels_AudioPipeline.h + Source/Kernels/AudioPipeline/Kernels_ChannelMixer.cpp + Source/Kernels/AudioPipeline/Kernels_ChannelMixer.h + Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.cpp + Source/Kernels/AudioPipeline/Kernels_RawAudio2WorkingAudio.h + Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.cpp + Source/Kernels/AudioPipeline/Kernels_WorkingAudio2RawAudio.h Source/Kernels/AudioResampler/Kernels_AudioResampler_BlockKernel_Default.h Source/Kernels/AudioResampler/Kernels_AudioResampler_BlockKernel_x64_SSE2.h Source/Kernels/AudioResampler/Kernels_AudioResampler_BlockKernel_x64_FMA3.h