diff --git a/CMakeLists.txt b/CMakeLists.txt index 38045628..d96a4f38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ set(SA_SOURCE include/SparkyStudios/Audio/Amplitude/Core/Playback/Channel.h include/SparkyStudios/Audio/Amplitude/Core/Playback/ChannelEventListener.h include/SparkyStudios/Audio/Amplitude/Core/Asset.h + include/SparkyStudios/Audio/Amplitude/Core/AudioBuffer.h include/SparkyStudios/Audio/Amplitude/Core/Codec.h include/SparkyStudios/Audio/Amplitude/Core/Common.h include/SparkyStudios/Audio/Amplitude/Core/Device.h @@ -161,6 +162,9 @@ set(SA_SOURCE include/SparkyStudios/Audio/Amplitude/Core/RefCounter.h include/SparkyStudios/Audio/Amplitude/Core/Thread.h include/SparkyStudios/Audio/Amplitude/Core/Version.h + include/SparkyStudios/Audio/Amplitude/DSP/AudioConverter.h + include/SparkyStudios/Audio/Amplitude/DSP/Filter.h + include/SparkyStudios/Audio/Amplitude/DSP/Resampler.h include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h include/SparkyStudios/Audio/Amplitude/IO/DiskFile.h include/SparkyStudios/Audio/Amplitude/IO/DiskFileSystem.h @@ -182,13 +186,12 @@ set(SA_SOURCE include/SparkyStudios/Audio/Amplitude/Math/SplitComplex.h include/SparkyStudios/Audio/Amplitude/Math/Utils.h include/SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h - include/SparkyStudios/Audio/Amplitude/Mixer/Resampler.h + include/SparkyStudios/Audio/Amplitude/Mixer/Pipeline.h include/SparkyStudios/Audio/Amplitude/Mixer/SoundProcessor.h include/SparkyStudios/Audio/Amplitude/Sound/Attenuation.h include/SparkyStudios/Audio/Amplitude/Sound/Collection.h include/SparkyStudios/Audio/Amplitude/Sound/Effect.h include/SparkyStudios/Audio/Amplitude/Sound/Fader.h - include/SparkyStudios/Audio/Amplitude/Sound/Filter.h include/SparkyStudios/Audio/Amplitude/Sound/Rtpc.h include/SparkyStudios/Audio/Amplitude/Sound/Sound.h include/SparkyStudios/Audio/Amplitude/Sound/SoundBank.h @@ -227,6 +230,7 @@ set(SA_SOURCE src/Core/Asset.cpp src/Core/Asset.h + src/Core/AudioBuffer.cpp src/Core/Codec.cpp src/Core/Common.cpp src/Core/DefaultPlugins.h @@ -251,6 +255,36 @@ set(SA_SOURCE src/Core/Thread.cpp src/Core/Version.cpp + src/DSP/Filters/BassBoostFilter.cpp + src/DSP/Filters/BassBoostFilter.h + src/DSP/Filters/BiquadResonantFilter.cpp + src/DSP/Filters/BiquadResonantFilter.h + src/DSP/Filters/DCRemovalFilter.cpp + src/DSP/Filters/DCRemovalFilter.h + src/DSP/Filters/DelayFilter.cpp + src/DSP/Filters/DelayFilter.h + src/DSP/Filters/EqualizerFilter.cpp + src/DSP/Filters/EqualizerFilter.h + src/DSP/Filters/FFTFilter.cpp + src/DSP/Filters/FFTFilter.h + src/DSP/Filters/FlangerFilter.cpp + src/DSP/Filters/FlangerFilter.h + src/DSP/Filters/FreeverbFilter.cpp + src/DSP/Filters/FreeverbFilter.h + src/DSP/Filters/LofiFilter.cpp + src/DSP/Filters/LofiFilter.h + src/DSP/Filters/MonoPoleFilter.cpp + src/DSP/Filters/MonoPoleFilter.h + src/DSP/Filters/RobotizeFilter.cpp + src/DSP/Filters/RobotizeFilter.h + src/DSP/Filters/WaveShaperFilter.cpp + src/DSP/Filters/WaveShaperFilter.h + src/DSP/Resamplers/DefaultResampler.cpp + src/DSP/Resamplers/DefaultResampler.h + src/DSP/AudioConverter.cpp + src/DSP/Filter.cpp + src/DSP/Resampler.cpp + src/HRTF/HRIRSphere.cpp src/HRTF/HRIRSphere.h src/IO/DiskFile.cpp @@ -275,20 +309,19 @@ set(SA_SOURCE src/Mixer/Resamplers/R8BrainResampler.h src/Mixer/Resamplers/LibsamplerateResampler.h - src/Mixer/SoundProcessors/ClipProcessor.h - src/Mixer/SoundProcessors/EffectProcessor.h - src/Mixer/SoundProcessors/EnvironmentProcessor.h - src/Mixer/SoundProcessors/ObstructionProcessor.h - src/Mixer/SoundProcessors/OcclusionProcessor.h - src/Mixer/SoundProcessors/PassThroughProcessor.h - src/Mixer/SoundProcessors/SilenceProcessor.h + src/Mixer/Pipeline/BinauralProcessor.h + src/Mixer/Pipeline/ClipProcessor.h + src/Mixer/Pipeline/EffectProcessor.h + src/Mixer/Pipeline/EnvironmentProcessor.h + src/Mixer/Pipeline/ObstructionProcessor.h + src/Mixer/Pipeline/OcclusionProcessor.h + src/Mixer/Pipeline/PassThroughProcessor.h + src/Mixer/Pipeline/SilenceProcessor.h src/Mixer/Amplimix.h src/Mixer/Amplimix.cpp - src/Mixer/ProcessorPipeline.cpp - src/Mixer/ProcessorPipeline.h + src/Mixer/Pipeline.cpp src/Mixer/RealChannel.cpp src/Mixer/RealChannel.h - src/Mixer/Resampler.cpp src/Mixer/SoundData.cpp src/Mixer/SoundData.h src/Mixer/SoundProcessor.cpp @@ -301,30 +334,6 @@ set(SA_SOURCE src/Sound/Faders/ExponentialFader.h src/Sound/Faders/LinearFader.h src/Sound/Faders/SCurveFader.h - src/Sound/Filters/BassBoostFilter.cpp - src/Sound/Filters/BassBoostFilter.h - src/Sound/Filters/BiquadResonantFilter.cpp - src/Sound/Filters/BiquadResonantFilter.h - src/Sound/Filters/DCRemovalFilter.cpp - src/Sound/Filters/DCRemovalFilter.h - src/Sound/Filters/DelayFilter.cpp - src/Sound/Filters/DelayFilter.h - src/Sound/Filters/EqualizerFilter.cpp - src/Sound/Filters/EqualizerFilter.h - src/Sound/Filters/FFTFilter.cpp - src/Sound/Filters/FFTFilter.h - src/Sound/Filters/FlangerFilter.cpp - src/Sound/Filters/FlangerFilter.h - src/Sound/Filters/FreeverbFilter.cpp - src/Sound/Filters/FreeverbFilter.h - src/Sound/Filters/LofiFilter.cpp - src/Sound/Filters/LofiFilter.h - src/Sound/Filters/MonoPoleFilter.cpp - src/Sound/Filters/MonoPoleFilter.h - src/Sound/Filters/RobotizeFilter.cpp - src/Sound/Filters/RobotizeFilter.h - src/Sound/Filters/WaveShaperFilter.cpp - src/Sound/Filters/WaveShaperFilter.h src/Sound/Schedulers/RandomScheduler.cpp src/Sound/Schedulers/RandomScheduler.h src/Sound/Schedulers/SequenceScheduler.cpp @@ -338,7 +347,6 @@ set(SA_SOURCE src/Sound/Effect.cpp src/Sound/Effect.h src/Sound/Fader.cpp - src/Sound/Filter.cpp src/Sound/RefCounter.cpp src/Sound/Rtpc.cpp src/Sound/Rtpc.h @@ -398,6 +406,7 @@ set(SA_SOURCE src/Utils/pffft/pffft_common.cpp src/Utils/pffft/pffft_priv_impl.h src/Utils/intrusive_list.h + src/Utils/Utils.cpp src/Utils/Utils.h ) diff --git a/include/SparkyStudios/Audio/Amplitude/Amplitude.h b/include/SparkyStudios/Audio/Amplitude/Amplitude.h index 95381e21..ba64d5f8 100644 --- a/include/SparkyStudios/Audio/Amplitude/Amplitude.h +++ b/include/SparkyStudios/Audio/Amplitude/Amplitude.h @@ -37,6 +37,10 @@ #include #include +#include +#include +#include + #include #include @@ -61,14 +65,12 @@ #include #include -#include #include #include #include #include #include -#include #include #include #include diff --git a/include/SparkyStudios/Audio/Amplitude/Core/AudioBuffer.h b/include/SparkyStudios/Audio/Amplitude/Core/AudioBuffer.h new file mode 100644 index 00000000..cdcfbae8 --- /dev/null +++ b/include/SparkyStudios/Audio/Amplitude/Core/AudioBuffer.h @@ -0,0 +1,313 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _AM_CORE_AUDIO_BUFFER_H +#define _AM_CORE_AUDIO_BUFFER_H + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + /** + * @brief Represents a vire to a single channel in an @c AudioBuffer. + */ + class AM_API_PUBLIC AudioBufferChannel + { + public: + /** + * @brief Gets the size of the buffer. + * + * @return The size of the buffer. + */ + [[nodiscard]] AmSize size() const; + + /** + * @brief Returns a float pointer to the begin of the channel data. + * + * @return A float pointer to the begin of the channel data. + */ + AmReal32* begin(); + + /** + * @brief Returns a const float pointer to the begin of the channel data. + * + * @return A const float pointer to the begin of the channel data. + */ + [[nodiscard]] const AmReal32* begin() const; + + /** + * @brief Returns a float pointer to the end of the channel data. + * + * @return A float pointer to the end of the channel data. + */ + AmReal32* end(); + + /** + * @brief Returns a const float pointer to the end of the channel data. + * + * @return A const float pointer to the end of the channel data. + */ + [[nodiscard]] const AmReal32* end() const; + + /** + * @brief Clears the channel data to zero. + */ + void clear(); + + /** + * @brief Checks if the channel is enabled. + * + * @return @c true if the channel is enabled, @c false otherwise. + */ + [[nodiscard]] bool enabled() const; + + /** + * @brief Gets the frame at the specified index. + * + * @param index The frame index. + * + * @return The frame at the specified index. + */ + AmReal32& operator[](AmSize index); + + /** + * @brief Gets the frame at the specified index. + * + * @param index The frame index. + * + * @return The frame at the specified index. + */ + [[nodiscard]] const AmReal32& operator[](AmSize index) const; + + /** + * @brief Sets the entire channel data from the provided vector. + * + * @param data The vector containing the new channel data. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator=(const std::vector& data); + + /** + * @brief Sets the entire channel data from the provided @c AudioBufferChannel. + * + * @param channel The @c AudioBufferChannel to copy the data from. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator=(const AudioBufferChannel& channel); + + /** + * @brief Adds the provided @c AudioBufferChannel to this channel. + * + * @param channel The @c AudioBufferChannel to add. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator+=(const AudioBufferChannel& channel); + + /** + * @brief Subtracts the provided @c AudioBufferChannel from this channel. + * + * @param channel The @c AudioBufferChannel to subtract. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator-=(const AudioBufferChannel& channel); + + /** + * @brief Pointwise multiplies this channel with the provided @c AudioBufferChannel. + * + * @param channel The @c AudioBufferChannel to multiply with. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator*=(const AudioBufferChannel& channel); + + /** + * @brief Pointwise multiplies this channel with the provided scalar. + * + * @param scalar The scalar to multiply with. + * + * @return A reference to the modified channel. + */ + AudioBufferChannel& operator*=(AmReal32 scalar); + + private: + friend class AudioBuffer; + + AudioBufferChannel(AmReal32* begin, AmSize numFrames); + + AmReal32* _begin; + AmSize _frameCount; + bool _isEnabled; + }; + + class AM_API_PUBLIC AudioBuffer + { + public: + /** + * @brief Copies the given number of frames from the source buffer to the destination buffer. + * + * @param source The source buffer to copy. + * @param sourceOffset The offset in the source buffer. + * @param destination The destination buffer to copy to. + * @param destinationOffset The offset in the destination buffer. + * @param numFrames The number of frames to copy. + */ + static void Copy( + const AudioBuffer& source, AmSize sourceOffset, AudioBuffer& destination, AmSize destinationOffset, AmSize numFrames); + + /** + * @brief Creates an empty audio buffer. + */ + AudioBuffer(); + + /** + * @brief Creates an audio buffer with the specified number of frames and channels. + * + * @param numFrames The number of frames in the buffer. + * @param numChannels The number of channels in the buffer. + */ + AudioBuffer(AmSize numFrames, AmSize numChannels); + + /** + * @brief Explicitly deletes the audio buffer copy to avoid unintended usage. + * + * @param buffer The other audio buffer to copy. + * + * @note Use the assignment operator to copy the audio buffer. + */ + AudioBuffer(const AudioBuffer& buffer) = delete; + + /** + * @brief Moves the given audio buffer data in this one. + * + * @param buffer The other audio buffer to move. + */ + AudioBuffer(AudioBuffer&& buffer) noexcept; + + ~AudioBuffer(); + + /** + * @brief Gets the number of frames in the buffer. + * + * @return The number of frames in the buffer. + */ + [[nodiscard]] AmSize GetFrameCount() const; + + /** + * @brief Gets the number of channels in the buffer. + * + * @return The number of channels in the buffer. + */ + [[nodiscard]] AmSize GetChannelCount() const; + + /** + * @brief Sets the entire audio buffer data to zero. + */ + void Clear(); + + /** + * @brief Gets the wrapped audio buffer data. + * + * @return The wrapped audio buffer data. + */ + [[nodiscard]] const AmAlignedReal32Buffer& GetData() const; + + /** + * @brief Gets the @c AudioBufferChannel at the specified index. + * + * @param index The channel index. + * + * @return The @c AudioBufferChannel at the specified index. + */ + AudioBufferChannel& GetChannel(AmSize index); + + /** + * @brief Gets the @c AudioBufferChannel at the specified index. + * + * @param index The channel index. + * + * @return The @c AudioBufferChannel at the specified index. + */ + [[nodiscard]] const AudioBufferChannel& GetChannel(AmSize index) const; + + /** + * @brief Gets the @c AudioBufferChannel at the specified index. + * + * @param index The channel index. + * + * @return The @c AudioBufferChannel at the specified index. + */ + AudioBufferChannel& operator[](AmSize index); + + /** + * @brief Gets the @c AudioBufferChannel at the specified index. + * + * @param index The channel index. + * + * @return The @c AudioBufferChannel at the specified index. + */ + [[nodiscard]] const AudioBufferChannel& operator[](AmSize index) const; + + /** + * @brief Copies the audio buffer data from the provided @c AudioBuffer. + * + * @param buffer The other audio buffer to copy. + * + * @return This instance with the copied audio buffer data. + */ + AudioBuffer& operator=(const AudioBuffer& buffer); + + /** + * @brief Accumulates the audio buffer data from the provided @c AudioBuffer. + * + * @param buffer The buffer to add in this one. + * + * @return This instance with the added audio buffer data. + */ + AudioBuffer& operator+=(const AudioBuffer& buffer); + + /** + * @brief Subtracts the audio buffer data from the provided @c AudioBuffer. + * + * @param buffer The buffer to subtract from this one. + * + * @return This instance with the subtracted audio buffer data. + */ + AudioBuffer& operator-=(const AudioBuffer& buffer); + + /** + * @brief Pointwise multiplies the audio buffer data with the provided @c AudioBuffer. + * + * @param buffer The buffer to multiply with this one. + * + * @return This instance with the multiplied audio buffer data. + */ + AudioBuffer& operator*=(const AudioBuffer& buffer); + + private: + void Initialize(AmSize channelCount); + + AmSize _frameCount; + std::vector _channels; + + AmAlignedReal32Buffer _data; + }; +} // namespace SparkyStudios::Audio::Amplitude + +#endif // _AM_CORE_AUDIO_BUFFER_H diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Codec.h b/include/SparkyStudios/Audio/Amplitude/Core/Codec.h index d8860537..2f8c50c2 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Codec.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Codec.h @@ -17,6 +17,7 @@ #ifndef _AM_CORE_CODEC_H #define _AM_CORE_CODEC_H +#include #include #include @@ -74,13 +75,10 @@ namespace SparkyStudios::Audio::Amplitude * * @return The audio sample format. */ - [[nodiscard]] const SoundFormat& GetFormat() const - { - return m_format; - } + [[nodiscard]] const SoundFormat& GetFormat() const; /** - * @breif Loads the entire audio file into the output buffer. + * @brief Loads the entire audio file into the output buffer. * * The output buffer must allocate enough size for this operation * to be successful. @@ -89,18 +87,19 @@ namespace SparkyStudios::Audio::Amplitude * * @return The number of audio frames loaded into the buffer. */ - virtual AmUInt64 Load(AmVoidPtr out) = 0; + virtual AmUInt64 Load(AudioBuffer* out) = 0; /** * @brief Streams a part of the file from disk into the output buffer. * * @param out The buffer to stream the file data into. - * @param offset The offset in frames from which start to read the file. + * @param bufferOffset + * @param seekOffset The offset in frames from which start to read the file. * @param length The length in frames to read from the file. * * @return The number of frames read. */ - virtual AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) = 0; + virtual AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) = 0; /** * @brief Moves the cursor to the given frame. @@ -167,10 +166,7 @@ namespace SparkyStudios::Audio::Amplitude * * @param format The audio sample format. */ - virtual void SetFormat(const SoundFormat& format) - { - m_format = format; - } + virtual void SetFormat(const SoundFormat& format); /** * @brief Writes a the given buffer into the file. @@ -181,7 +177,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The number of frames written. */ - virtual AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) = 0; + virtual AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) = 0; protected: /** @@ -255,10 +251,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The name of this codec. */ - [[nodiscard]] const AmString& GetName() const - { - return m_name; - } + [[nodiscard]] const AmString& GetName() const; /** * @brief Registers a new audio codec. diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Common.h b/include/SparkyStudios/Audio/Amplitude/Core/Common.h index 9956f4f2..bd809ee9 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Common.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Common.h @@ -180,7 +180,7 @@ namespace SparkyStudios::Audio::Amplitude return m_data[index]; } - const AmReal32& operator[](size_t index) const + const AmReal32& operator[](AmSize index) const { AMPLITUDE_ASSERT(m_data != nullptr && index < m_floats); return m_data[index]; diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Common/Config.h b/include/SparkyStudios/Audio/Amplitude/Core/Common/Config.h index 1a3c5240..fffb0ed5 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Common/Config.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Common/Config.h @@ -95,9 +95,6 @@ // Maximum number of filters per stream #define AM_FILTERS_PER_STREAM 8 -// 1) Mono, 2) Stereo, 4) Quad, 6) 5.1, 8) 7.1 -#define AM_MAX_CHANNELS 8 - // Maximum number of tasks in a single pool #define AM_MAX_THREAD_POOL_TASKS 1024 diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Common/Constants.h b/include/SparkyStudios/Audio/Amplitude/Core/Common/Constants.h index ac02dc3e..5356e9b6 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Common/Constants.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Common/Constants.h @@ -54,6 +54,21 @@ namespace SparkyStudios::Audio::Amplitude constexpr AmInt32 kAmFixedPointUnit = (1 << kAmFixedPointBits); constexpr AmInt32 kAmFixedPointMask = (kAmFixedPointBits - 1); + + /** + * @brief The maximum number of frames that can be processed at once. + */ + constexpr AmUInt64 kAmMaxSupportedFrameCount = 16384; + + /** + * @brief The maximum supported ambisonic order. + */ + constexpr AmUInt32 kAmMaxSupportedAmbisonicOrder = 3; + + /** + * @brief The maximum supported channel count for an ambisonic audio source. + */ + constexpr AmUInt32 kAmMaxSupportedChannelCount = (kAmMaxSupportedAmbisonicOrder + 1) * (kAmMaxSupportedAmbisonicOrder + 1); } // namespace SparkyStudios::Audio::Amplitude #endif // _AM_CORE_COMMON_CONSTANTS_H diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Common/Platforms/UNIX/Types.h b/include/SparkyStudios/Audio/Amplitude/Core/Common/Platforms/UNIX/Types.h index 76862dc3..83624c36 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Common/Platforms/UNIX/Types.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Common/Platforms/UNIX/Types.h @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace SparkyStudios::Audio::Amplitude diff --git a/include/SparkyStudios/Audio/Amplitude/DSP/AudioConverter.h b/include/SparkyStudios/Audio/Amplitude/DSP/AudioConverter.h new file mode 100644 index 00000000..f02de04f --- /dev/null +++ b/include/SparkyStudios/Audio/Amplitude/DSP/AudioConverter.h @@ -0,0 +1,164 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _AM_DSP_AUDIO_CONVERTER_H +#define _AM_DSP_AUDIO_CONVERTER_H + +#include +#include + +namespace SparkyStudios::Audio::Amplitude +{ + /** + * @brief Allow converting audio buffers between different sample rates and channel counts. + * + * @note This class uses the @c Resampler class to perform sample rate conversion. + * @note Only mono to stereo or vice versa conversions are supported. + */ + class AudioConverter final + { + public: + /** + * @brief Store conversion settings for an @c AudioConverter instance. + */ + struct Settings + { + /** + * @brief The source sample rate. + */ + AmUInt32 m_sourceSampleRate; + + /** + * @brief The destination sample rate. + */ + AmUInt32 m_targetSampleRate; + + /** + * @brief The source channel count. + */ + AmUInt16 m_sourceChannelCount; + + /** + * @brief The destination channel count. + */ + AmUInt16 m_targetChannelCount; + }; + + /** + * @brief Default constructor. + */ + AudioConverter(); + + ~AudioConverter(); + + /** + * @brief Initializes the audio converter with the given conversion settings. + * + * @param settings The conversion settings. + * + * @return @c true if the initialization was successful, @c false otherwise. + */ + bool Configure(const Settings& settings); + + /** + * @brief Converts the audio buffer from the source sample rate and channel count to the target sample rate and channel count. + * + * @param input The source audio buffer. + * @param inputFrames The number of frames to process in the input audio buffer. + * @param output The target audio buffer to store the converted audio. + * @param outputFrames The number of frames to process in the target audio buffer. + */ + void Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames); + + /** + * @brief Updates the source sample rate and target sample rate. + * + * @param sourceSampleRate The source sample rate. + * @param targetSampleRate The target sample rate. + */ + void SetSampleRate(AmUInt64 sourceSampleRate, AmUInt64 targetSampleRate); + + /** + * @brief Returns the required number of frames to have as input for the + * given amount of output frames. + * + * @param outputFrameCount The number of output frames. + * + * @return The input frame count needed to produce the given output frame count. + */ + [[nodiscard]] AmUInt64 GetRequiredInputFrameCount(AmUInt64 outputFrameCount) const; + + /** + * @brief Returns the expected number of frames to have as output for the + * given amount of input frames. + * + * @param inputFrameCount The number of input frames. + * + * @return The expected number of output frames for the given input frame count. + */ + [[nodiscard]] AmUInt64 GetExpectedOutputFrameCount(AmUInt64 inputFrameCount) const; + + /** + * @brief Returns the current input latency in frames. + * + * @return The current input latency in frames. + */ + [[nodiscard]] AmUInt64 GetInputLatency() const; + + /** + * @brief Returns the current output latency in frames. + * + * @return The current output latency in frames. + */ + [[nodiscard]] AmUInt64 GetOutputLatency() const; + + /** + * @brief Resets the internal state of the converter. + */ + void Reset(); + + private: + enum ChannelConversionMode + { + kChannelConversionModeDisabled, + kChannelConversionModeStereoToMono, + kChannelConversionModeMonoToStereo, + }; + + /** + * @brief Convert stereo audio to mono. + * + * @param input The input audio buffer. + * @param output The output audio buffer to store the converted audio. + */ + static void ConvertStereoFromMono(const AudioBuffer& input, AudioBuffer& output); + + /** + * @brief Convert mono audio to stereo. + * + * @param input The input audio buffer. + * @param output The output audio buffer to store the converted audio. + */ + static void ConvertMonoFromStereo(const AudioBuffer& input, AudioBuffer& output); + + ResamplerInstance* _resampler; + ChannelConversionMode _channelConversionMode; + + bool _needResampling; + }; +} // namespace SparkyStudios::Audio::Amplitude + +#endif // _AM_DSP_AUDIO_CONVERTER_H diff --git a/include/SparkyStudios/Audio/Amplitude/Sound/Filter.h b/include/SparkyStudios/Audio/Amplitude/DSP/Filter.h similarity index 56% rename from include/SparkyStudios/Audio/Amplitude/Sound/Filter.h rename to include/SparkyStudios/Audio/Amplitude/DSP/Filter.h index d147ac32..f63da0d7 100644 --- a/include/SparkyStudios/Audio/Amplitude/Sound/Filter.h +++ b/include/SparkyStudios/Audio/Amplitude/DSP/Filter.h @@ -14,15 +14,107 @@ #pragma once -#ifndef _AM_SOUND_FILTER_H -#define _AM_SOUND_FILTER_H +#ifndef _AM_DSP_FILTER_H +#define _AM_DSP_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { - class FilterInstance; + class Filter; + /** + * @brief A Filter instance. + * + * An object of this class will be created each time a @c Filter is requested. + */ + class AM_API_PUBLIC FilterInstance + { + public: + /** + * @brief Constructs a new @c FilterInstance object. + * + * @param parent The parent @c Filter object that created this instance. + */ + explicit FilterInstance(Filter* parent); + + virtual ~FilterInstance(); + + /** + * @brief Initializes the filter instance with the provided number of + * parameters. + * + * @param paramCount The number of parameters the filter will need. + */ + AmResult Initialize(AmUInt32 paramCount); + + /** + * @brief Updates the filter instance state for the provided delta time. + * + * @param deltaTime The time in milliseconds since the last frame. + */ + virtual void AdvanceFrame(AmTime deltaTime); + + /** + * @brief Executes the filter instance. + * + * @param in The input buffer on which the filter should be applied. + * @param out The output buffer where the filtered output will be stored. + * @param frames The number of frames to process. + * @param sampleRate The current sample rate of the @c buffer. + */ + virtual void Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate); + + /** + * @brief Gets the current value of the parameter at the given index. + * + * @param parameterIndex The index of the parameter to retrieve. + * + * @return The current value of the parameter. + */ + virtual AmReal32 GetParameter(AmUInt32 parameterIndex); + + /** + * @brief Sets the value of the parameter at the given index. + * + * @param parameterIndex The index of the parameter to retrieve. + * @param value The value to set to the parameter. + */ + virtual void SetParameter(AmUInt32 parameterIndex, AmReal32 value); + + protected: + /** + * @brief Executes the filter instance on a single channel of the given buffer. + * + * @param in The input buffer on which the filter should be applied. + * @param out The output buffer where the filtered output will be stored. + * @param channel The index of the channel to process. + * @param frames The number of frames to process. + * @param sampleRate The current sample rate of the @c buffer. + */ + virtual void ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate); + + /** + * @brief Executes the filter instance on a single sample of the given buffer. + * + * @param sample The audio sample to process. + * @param channel The index of the channel to process. + * @param sampleRate The current sample rate of the @c buffer. + */ + virtual AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate); + + Filter* m_parent; + + AmUInt32 m_numParams; + AmUInt32 m_numParamsChanged; + AmReal32Buffer m_parameters; + }; + + /** + * @brief Helper class to manage filters. + * + * A filter applies transformations on an audio buffer.. + */ class AM_API_PUBLIC Filter { friend class FilterInstance; @@ -36,11 +128,11 @@ namespace SparkyStudios::Audio::Amplitude }; /** - * @brief Create a new Filter instance. + * @brief Create a new @c Filter instance. * - * @param name The filter name. eg. "MiniAudioLinear". + * @param name The filter name. eg. "Delay". */ - explicit Filter(std::string name); + explicit Filter(AmString name); virtual ~Filter(); @@ -96,7 +188,7 @@ namespace SparkyStudios::Audio::Amplitude /** * @brief Destroys an instance of the filter. The instance should have - * been created with CreateInstance(). + * been created with @c Filter::CreateInstance(). * * @param instance The filter instance to be destroyed. */ @@ -107,7 +199,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The name of this filter. */ - [[nodiscard]] const std::string& GetName() const; + [[nodiscard]] const AmString& GetName() const; /** * @brief Registers a new filter. @@ -126,19 +218,19 @@ namespace SparkyStudios::Audio::Amplitude /** * @brief Look up a filter by name. * - * @return The filter with the given name, or NULL if none. + * @return The filter with the given name, or @c nullptr if none. */ - static Filter* Find(const std::string& name); + static Filter* Find(const AmString& name); /** * @brief Creates a new instance of the the filter with the given name - * and returns its pointer. The returned pointer should be deleted using Filter::Destruct(). + * and returns its pointer. The returned pointer should be deleted using @c Filter::Destruct(). * * @param name The name of the filter. * - * @return The filter with the given name, or NULL if none. + * @return The filter with the given name, or @c nullptr if none. */ - static FilterInstance* Construct(const std::string& name); + static FilterInstance* Construct(const AmString& name); /** * @brief Destroys the given filter instance. @@ -146,7 +238,7 @@ namespace SparkyStudios::Audio::Amplitude * @param name The name of the filter. * @param instance The filter instance to destroy. */ - static void Destruct(const std::string& name, FilterInstance* instance); + static void Destruct(const AmString& name, FilterInstance* instance); /** * @brief Locks the filters registry. @@ -167,46 +259,18 @@ namespace SparkyStudios::Audio::Amplitude static void UnlockRegistry(); /** - * @brief Gets the list of registered Faders. + * @brief Gets the list of registered filters. * - * @return The registry of Faders. + * @return The registry of filters. */ - static const std::map& GetRegistry(); + static const std::map& GetRegistry(); protected: /** * @brief The name of this filter. */ - std::string m_name; - }; - - class AM_API_PUBLIC FilterInstance - { - public: - explicit FilterInstance(Filter* parent); - virtual ~FilterInstance(); - - AmResult Init(AmUInt32 numParams); - - virtual void AdvanceFrame(AmTime delta_time); - - virtual void Process(AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate); - - virtual void ProcessChannel(AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate); - - virtual AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate); - - virtual AmReal32 GetFilterParameter(AmUInt32 attributeId); - - virtual void SetFilterParameter(AmUInt32 attributeId, AmReal32 value); - - protected: - Filter* m_parent; - - AmUInt32 m_numParams; - AmUInt32 m_numParamsChanged; - AmReal32Buffer m_parameters; + AmString m_name; }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_SOUND_FILTER_H +#endif // _AM_DSP_FILTER_H diff --git a/include/SparkyStudios/Audio/Amplitude/Mixer/Resampler.h b/include/SparkyStudios/Audio/Amplitude/DSP/Resampler.h similarity index 82% rename from include/SparkyStudios/Audio/Amplitude/Mixer/Resampler.h rename to include/SparkyStudios/Audio/Amplitude/DSP/Resampler.h index 50cebff1..35276382 100644 --- a/include/SparkyStudios/Audio/Amplitude/Mixer/Resampler.h +++ b/include/SparkyStudios/Audio/Amplitude/DSP/Resampler.h @@ -22,16 +22,31 @@ namespace SparkyStudios::Audio::Amplitude { + /** + * @brief A Resampler instance. + * + * An object of this class will be created each time a @c Resampler is requested. + */ class AM_API_PUBLIC ResamplerInstance { public: + /** + * @brief Construct a new ResamplerInstance object. + * + * This will initialize the resampler instance state to default values. + */ ResamplerInstance() = default; + virtual ~ResamplerInstance() = default; /** * @brief Initialize a new instance of the resampler. + * + * @param channelCount The number of channels in the audio data. + * @param sampleRateIn The input sample rate. + * @param sampleRateOut The output sample rate. */ - virtual void Init(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut, AmUInt64 frameCount) = 0; + virtual void Initialize(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) = 0; /** * @brief Processes the audio data. @@ -43,7 +58,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return @c true if the resampling was successful, otherwise @c false. */ - virtual bool Process(AmConstAudioSampleBuffer input, AmUInt64& inputFrames, AmAudioSampleBuffer output, AmUInt64& outputFrames) = 0; + virtual bool Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) = 0; /** * @brief Changes the input and output sample rate. @@ -82,7 +97,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The input frame count needed to produce the given output frame count. */ - [[nodiscard]] virtual AmUInt64 GetRequiredInputFrameCount(AmUInt64 outputFrameCount) const = 0; + [[nodiscard]] virtual AmUInt64 GetRequiredInputFrames(AmUInt64 outputFrameCount) const = 0; /** * @brief Returns the expected number of frames to have as output for the @@ -92,7 +107,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The expected number of output frames for the given input frame count. */ - [[nodiscard]] virtual AmUInt64 GetExpectedOutputFrameCount(AmUInt64 inputFrameCount) const = 0; + [[nodiscard]] virtual AmUInt64 GetExpectedOutputFrames(AmUInt64 inputFrameCount) const = 0; /** * @brief Returns the current input latency in frames. @@ -109,12 +124,12 @@ namespace SparkyStudios::Audio::Amplitude [[nodiscard]] virtual AmUInt64 GetOutputLatency() const = 0; /** - * @biref Resets the internal resampler state. + * @brief Resets the internal resampler state. */ virtual void Reset() = 0; /** - * @bbrief Cleans up the internal resampler state and allocated data. + * @brief Cleans up the internal resampler state and allocated data. * @note This method is called when the resampler is about to be destroyed. */ virtual void Clear() = 0; @@ -128,7 +143,7 @@ namespace SparkyStudios::Audio::Amplitude * * @param name The resampler name. eg. "MiniAudioLinear". */ - explicit Resampler(std::string name); + explicit Resampler(AmString name); /** * @brief Default Resampler constructor. @@ -157,7 +172,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The name of this resampler. */ - [[nodiscard]] const std::string& GetName() const; + [[nodiscard]] const AmString& GetName() const; /** * @brief Registers a new resampler. @@ -181,7 +196,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The resampler with the given name, or NULL if none. */ - static ResamplerInstance* Construct(const std::string& name); + static ResamplerInstance* Construct(const AmString& name); /** * @brief Destroys the given resampler instance. @@ -189,7 +204,7 @@ namespace SparkyStudios::Audio::Amplitude * @param name The name of the resampler. * @param instance The resampler instance to destroy. */ - static void Destruct(const std::string& name, ResamplerInstance* instance); + static void Destruct(const AmString& name, ResamplerInstance* instance); /** * @brief Locks the resamplers registry. @@ -213,7 +228,7 @@ namespace SparkyStudios::Audio::Amplitude /** * @brief The name of this resampler. */ - std::string m_name; + AmString m_name; private: /** @@ -221,7 +236,7 @@ namespace SparkyStudios::Audio::Amplitude * * @return The resampler with the given name, or NULL if none. */ - static Resampler* Find(const std::string& name); + static Resampler* Find(const AmString& name); }; } // namespace SparkyStudios::Audio::Amplitude diff --git a/include/SparkyStudios/Audio/Amplitude/Math/Utils.h b/include/SparkyStudios/Audio/Amplitude/Math/Utils.h index c2868ca4..8919f15a 100644 --- a/include/SparkyStudios/Audio/Amplitude/Math/Utils.h +++ b/include/SparkyStudios/Audio/Amplitude/Math/Utils.h @@ -17,6 +17,7 @@ #ifndef _AM_MATH_UTILS_H #define _AM_MATH_UTILS_H +#include #include #define AM_LCG_M 2147483647 @@ -125,7 +126,7 @@ namespace SparkyStudios::Audio::Amplitude } AM_API_PRIVATE AM_INLINE AmReal32 - CatmullRom(const AmReal32 t, const AmReal32 p0, const AmReal32 p1, const AmReal32 p2, const AmReal32 p3) + CatmullRom(const AmReal32 t, const AmReal32 p0, const AmReal32 p1, const AmReal32 p2, const AmReal32 p3) { // clang-format off return 0.5f * ( @@ -216,11 +217,35 @@ namespace SparkyStudios::Audio::Amplitude * * @return A relative direction vector (not normalized). */ - AM_API_PRIVATE AM_INLINE AmVec3 - GetRelativeDirection(const AmVec3& originPosition, const AmQuat& originRotation, const AmVec3& position) + AM_API_PRIVATE AM_INLINE AmVec3 GetRelativeDirection(const AmVec3& originPosition, const AmQuat& originRotation, const AmVec3& position) { return AM_RotateV3Q(position - originPosition, AM_InvQ(originRotation)); } + + /** + * @brief Finds the greatest common divisor (GCD) of two integers. + * + * @param a First integer. + * @param b Second integer. + * + * @return The greatest common divisor of a and b. + */ + AM_API_PRIVATE AM_INLINE AmInt64 FindGCD(AmInt64 a, AmInt64 b) + { + a = std::abs(a); + b = std::abs(b); + + AmInt64 c = 0; + + while (b != 0) + { + c = b; + b = a % b; + a = c; + } + + return a; + } } // namespace SparkyStudios::Audio::Amplitude #endif // _AM_MATH_UTILS_H diff --git a/include/SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h b/include/SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h index 42d0df54..1c282aa6 100644 --- a/include/SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h +++ b/include/SparkyStudios/Audio/Amplitude/Mixer/Amplimix.h @@ -15,6 +15,7 @@ #ifndef _AM_MIXER_AMPLIMIX_H #define _AM_MIXER_AMPLIMIX_H +#include #include #include #include @@ -119,12 +120,12 @@ namespace SparkyStudios::Audio::Amplitude /** * @brief Processes the audio data by mixing multiple audio sources for the specified number of frames. * - * @param mixBuffer The buffer to store the mixed audio data. + * @param outBuffer The buffer to store the mixed audio data. * @param frameCount The number of frames to mix. * * @return The number of frames actually mixed. */ - virtual AmUInt64 Mix(AmVoidPtr mixBuffer, AmUInt64 frameCount) = 0; + virtual AmUInt64 Mix(AudioBuffer** outBuffer, AmUInt64 frameCount) = 0; /** * @brief Gets the description of the rendering audio device. diff --git a/include/SparkyStudios/Audio/Amplitude/Mixer/Pipeline.h b/include/SparkyStudios/Audio/Amplitude/Mixer/Pipeline.h new file mode 100644 index 00000000..63e912b1 --- /dev/null +++ b/include/SparkyStudios/Audio/Amplitude/Mixer/Pipeline.h @@ -0,0 +1,45 @@ +// Copyright (c) 2021-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _AM_MIXER_PIPELINE_H +#define _AM_MIXER_PIPELINE_H + +#include +#include +#include + +namespace SparkyStudios::Audio::Amplitude +{ + class ProcessorPipeline + { + public: + ProcessorPipeline(); + ~ProcessorPipeline(); + + void Append(SoundProcessorInstance* processor); + + void Insert(SoundProcessorInstance* processor, AmSize index); + + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out); + + void Cleanup(const AmplimixLayer* layer); + + private: + std::vector _processors; + }; +} // namespace SparkyStudios::Audio::Amplitude + +#endif // _AM_MIXER_PIPELINE_H diff --git a/include/SparkyStudios/Audio/Amplitude/Mixer/SoundProcessor.h b/include/SparkyStudios/Audio/Amplitude/Mixer/SoundProcessor.h index 7381da08..dd88bae3 100644 --- a/include/SparkyStudios/Audio/Amplitude/Mixer/SoundProcessor.h +++ b/include/SparkyStudios/Audio/Amplitude/Mixer/SoundProcessor.h @@ -17,8 +17,8 @@ #ifndef _AM_MIXER_SOUND_PROCESSOR_H #define _AM_MIXER_SOUND_PROCESSOR_H +#include #include -#include namespace SparkyStudios::Audio::Amplitude { @@ -28,14 +28,7 @@ namespace SparkyStudios::Audio::Amplitude SoundProcessorInstance() = default; virtual ~SoundProcessorInstance() = default; - virtual void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) = 0; + virtual void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) = 0; virtual AmSize GetOutputBufferSize(AmUInt64 frames, AmSize bufferSize, AmUInt16 channels, AmUInt32 sampleRate); @@ -166,14 +159,7 @@ namespace SparkyStudios::Audio::Amplitude void SetDryProcessor(SoundProcessorInstance* processor, AmReal32 dry); - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override; + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override; private: SoundProcessorInstance* _wetProcessor; diff --git a/plugins/codec-flac/Codec.cpp b/plugins/codec-flac/Codec.cpp index 113f0363..33282bbf 100644 --- a/plugins/codec-flac/Codec.cpp +++ b/plugins/codec-flac/Codec.cpp @@ -14,10 +14,11 @@ #include -void FlacCodec::FlacDecoderInternal::set_current_output_buffer(AmAudioSampleBuffer output, AmUInt64 size) +void FlacCodec::FlacDecoderInternal::set_current_output_buffer(AudioBuffer* output, AmUInt64 size, AmUInt64 offset) { _current_output_buffer = output; _current_output_buffer_size = size; + _current_output_buffer_offset = offset; } ::FLAC__StreamDecoderWriteStatus FlacCodec::FlacDecoderInternal::write_callback( @@ -34,7 +35,8 @@ ::FLAC__StreamDecoderWriteStatus FlacCodec::FlacDecoderInternal::write_callback( { for (AmUInt32 j = 0; j < frame->header.channels; j++) { - _current_output_buffer[(sample * frame->header.channels) + j] = AmInt16ToReal32(buffer[j][i]); + auto& channel = _current_output_buffer->GetChannel(j); + channel[sample + _current_output_buffer_offset] = AmInt16ToReal32(buffer[j][i]); } sample++; @@ -156,12 +158,12 @@ bool FlacCodec::FlacDecoder::Close() return true; } -AmUInt64 FlacCodec::FlacDecoder::Load(AmVoidPtr out) +AmUInt64 FlacCodec::FlacDecoder::Load(AudioBuffer* out) { if (!_initialized) return 0; - _flac.set_current_output_buffer(static_cast(out), m_format.GetFramesCount()); + _flac.set_current_output_buffer(out, m_format.GetFramesCount(), 0); if (!Seek(0)) return 0; @@ -172,19 +174,22 @@ AmUInt64 FlacCodec::FlacDecoder::Load(AmVoidPtr out) return 0; } -AmUInt64 FlacCodec::FlacDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) +AmUInt64 FlacCodec::FlacDecoder::Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) { if (!_initialized) return 0; - _flac.set_current_output_buffer(static_cast(out), length); + _flac.set_current_output_buffer(out, length, bufferOffset); - bool seeked = Seek(offset); + bool seeked = Seek(seekOffset); while (_flac.need_more_frames() && _flac.get_state() != FLAC__STREAM_DECODER_END_OF_STREAM) _flac.process_single(); - return _flac.read_frame_count(); + AmUInt64 read = _flac.read_frame_count(); + _flac.reset_read_frame_count(); + + return read; } bool FlacCodec::FlacDecoder::Seek(AmUInt64 offset) @@ -206,7 +211,7 @@ bool FlacCodec::FlacEncoder::Close() return true; } -AmUInt64 FlacCodec::FlacEncoder::Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) +AmUInt64 FlacCodec::FlacEncoder::Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) { return 0; } diff --git a/plugins/codec-flac/Codec.h b/plugins/codec-flac/Codec.h index 802aed16..701de7b0 100644 --- a/plugins/codec-flac/Codec.h +++ b/plugins/codec-flac/Codec.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_FLAC_CODEC_CODEC_H -#define SS_AMPLITUDE_AUDIO_FLAC_CODEC_CODEC_H +#ifndef _AM_PLUGIN_CODEC_FLAC_CODEC_H +#define _AM_PLUGIN_CODEC_FLAC_CODEC_H #include @@ -42,7 +42,7 @@ class FlacCodec final : public Codec FlacDecoderInternal(const FlacDecoderInternal&) = delete; FlacDecoderInternal& operator=(const FlacDecoderInternal&) = delete; - void set_current_output_buffer(AmAudioSampleBuffer output, AmUInt64 size); + void set_current_output_buffer(AudioBuffer* output, AmUInt64 size, AmUInt64 offset); [[nodiscard]] bool need_more_frames() const { @@ -54,6 +54,11 @@ class FlacCodec final : public Codec return _read_frame_count; } + void reset_read_frame_count() + { + _read_frame_count = 0; + } + protected: ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame* frame, const FLAC__int32* const buffer[]) override; void metadata_callback(const ::FLAC__StreamMetadata* metadata) override; @@ -70,8 +75,9 @@ class FlacCodec final : public Codec bool _need_more_frames; AmUInt64 _read_frame_count; - AmAudioSampleBuffer _current_output_buffer; + AudioBuffer* _current_output_buffer; AmUInt64 _current_output_buffer_size; + AmUInt64 _current_output_buffer_offset; }; class FlacDecoder final : public Codec::Decoder @@ -87,9 +93,9 @@ class FlacCodec final : public Codec bool Close() override; - AmUInt64 Load(AmVoidPtr out) override; + AmUInt64 Load(AudioBuffer* out) override; - AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) override; bool Seek(AmUInt64 offset) override; @@ -114,7 +120,7 @@ class FlacCodec final : public Codec bool Close() override; - AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) override; private: bool _initialized; @@ -134,4 +140,4 @@ class FlacCodec final : public Codec [[nodiscard]] bool CanHandleFile(std::shared_ptr file) const override; }; -#endif // SS_AMPLITUDE_AUDIO_FLAC_CODEC_CODEC_H +#endif // _AM_PLUGIN_CODEC_FLAC_CODEC_H diff --git a/plugins/codec-flac/Plugin.h b/plugins/codec-flac/Plugin.h index 345ddb9c..e2cff063 100644 --- a/plugins/codec-flac/Plugin.h +++ b/plugins/codec-flac/Plugin.h @@ -14,11 +14,11 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_FLAC_CODEC_PLUGIN_H -#define SS_AMPLITUDE_AUDIO_FLAC_CODEC_PLUGIN_H +#ifndef _AM_PLUGIN_CODEC_FLAC_PLUGIN_H +#define _AM_PLUGIN_CODEC_FLAC_PLUGIN_H #include using namespace SparkyStudios::Audio::Amplitude; -#endif // SS_AMPLITUDE_AUDIO_FLAC_CODEC_PLUGIN_H +#endif // _AM_PLUGIN_CODEC_FLAC_PLUGIN_H diff --git a/plugins/codec-vorbis/Codec.cpp b/plugins/codec-vorbis/Codec.cpp index 17df27e0..cfd0d6da 100644 --- a/plugins/codec-vorbis/Codec.cpp +++ b/plugins/codec-vorbis/Codec.cpp @@ -14,16 +14,6 @@ #include -AM_API_PRIVATE void vorbis_interleave(float* dest, const float* const* src, unsigned nframes, unsigned channels) -{ - for (const float* const* src_end = src + channels; src != src_end; ++src, ++dest) - { - float* d = dest; - for (const float *s = *src, *s_end = s + nframes; s != s_end; ++s, d += channels) - *d = *s; - } -} - AM_API_PRIVATE size_t read_callback(void* ptr, size_t size, size_t nmemb, void* userdata) { auto* file = static_cast(userdata); @@ -93,12 +83,12 @@ bool VorbisCodec::VorbisDecoder::Close() return true; } -AmUInt64 VorbisCodec::VorbisDecoder::Load(AmVoidPtr out) +AmUInt64 VorbisCodec::VorbisDecoder::Load(AudioBuffer* out) { - return Stream(out, 0, m_format.GetFramesCount()); + return Stream(out, 0, 0, m_format.GetFramesCount()); } -AmUInt64 VorbisCodec::VorbisDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) +AmUInt64 VorbisCodec::VorbisDecoder::Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) { if (!_initialized) return 0; @@ -112,7 +102,7 @@ AmUInt64 VorbisCodec::VorbisDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUI while (size > 0) { - if (!Seek(offset + read)) + if (!Seek(seekOffset + read)) return 0; const AmInt64 ret = ov_read_float(&_vorbis, &data, static_cast(size), nullptr); @@ -122,7 +112,11 @@ AmUInt64 VorbisCodec::VorbisDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUI if (ret > 0) { - vorbis_interleave(static_cast(out) + read * channels, data, ret, channels); + for (AmUInt16 i = 0; i < channels; ++i) + { + auto& channel = out->GetChannel(i); + std::memcpy(&channel[bufferOffset] + read, data[i], ret * sizeof(AmAudioSample)); + } size -= ret; read += ret; @@ -162,7 +156,7 @@ bool VorbisCodec::VorbisEncoder::Close() return true; } -AmUInt64 VorbisCodec::VorbisEncoder::Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) +AmUInt64 VorbisCodec::VorbisEncoder::Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) { return 0; } diff --git a/plugins/codec-vorbis/Codec.h b/plugins/codec-vorbis/Codec.h index af86287f..61414e54 100644 --- a/plugins/codec-vorbis/Codec.h +++ b/plugins/codec-vorbis/Codec.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_VORBIS_CODEC_CODEC_H -#define SS_AMPLITUDE_AUDIO_VORBIS_CODEC_CODEC_H +#ifndef _AM_PLUGIN_CODEC_VORBIS_CODEC_H +#define _AM_PLUGIN_CODEC_VORBIS_CODEC_H #include @@ -39,9 +39,9 @@ class VorbisCodec final : public Codec bool Close() override; - AmUInt64 Load(AmVoidPtr out) override; + AmUInt64 Load(AudioBuffer* out) override; - AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) override; bool Seek(AmUInt64 offset) override; @@ -65,7 +65,7 @@ class VorbisCodec final : public Codec bool Close() override; - AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) override; private: bool _initialized; @@ -87,4 +87,4 @@ class VorbisCodec final : public Codec [[nodiscard]] bool CanHandleFile(std::shared_ptr file) const override; }; -#endif // SS_AMPLITUDE_AUDIO_VORBIS_CODEC_CODEC_H +#endif // _AM_PLUGIN_CODEC_VORBIS_CODEC_H diff --git a/plugins/codec-vorbis/Plugin.h b/plugins/codec-vorbis/Plugin.h index 9ef98f1b..fd322b0e 100644 --- a/plugins/codec-vorbis/Plugin.h +++ b/plugins/codec-vorbis/Plugin.h @@ -14,11 +14,11 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_VORBIS_CODEC_PLUGIN_H -#define SS_AMPLITUDE_AUDIO_VORBIS_CODEC_PLUGIN_H +#ifndef _AM_PLUGIN_CODEC_VORBIS_H +#define _AM_PLUGIN_CODEC_VORBIS_H #include using namespace SparkyStudios::Audio::Amplitude; -#endif // SS_AMPLITUDE_AUDIO_VORBIS_CODEC_PLUGIN_H +#endif // _AM_PLUGIN_CODEC_VORBIS_H diff --git a/src/Core/AudioBuffer.cpp b/src/Core/AudioBuffer.cpp new file mode 100644 index 00000000..945400c3 --- /dev/null +++ b/src/Core/AudioBuffer.cpp @@ -0,0 +1,338 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + AmSize AudioBufferChannel::size() const + { + return _frameCount; + } + + AmReal32* AudioBufferChannel::begin() + { + AMPLITUDE_ASSERT(_isEnabled); + return _begin; + } + + const AmReal32* AudioBufferChannel::begin() const + { + AMPLITUDE_ASSERT(_isEnabled); + return _begin; + } + + AmReal32* AudioBufferChannel::end() + { + AMPLITUDE_ASSERT(_isEnabled); + return _begin + _frameCount; + } + + const AmReal32* AudioBufferChannel::end() const + { + AMPLITUDE_ASSERT(_isEnabled); + return _begin + _frameCount; + } + + void AudioBufferChannel::clear() + { + AMPLITUDE_ASSERT(_isEnabled); + std::memset(begin(), 0, sizeof(AmReal32) * _frameCount); + } + + bool AudioBufferChannel::enabled() const + { + return _isEnabled; + } + + AmReal32& AudioBufferChannel::operator[](const AmSize index) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(index < _frameCount); + return _begin[index]; + } + + const AmReal32& AudioBufferChannel::operator[](const AmSize index) const + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(index < _frameCount); + return _begin[index]; + } + + AudioBufferChannel& AudioBufferChannel::operator=(const std::vector& data) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(data.size() == _frameCount); + std::memcpy(_begin, data.data(), sizeof(AmReal32) * _frameCount); + return *this; + } + + AudioBufferChannel& AudioBufferChannel::operator=(const AudioBufferChannel& channel) + { + if (this != &channel) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(channel._isEnabled); + AMPLITUDE_ASSERT(_frameCount == channel._frameCount); + std::memcpy(_begin, channel._begin, sizeof(AmReal32) * _frameCount); + } + + return *this; + } + + AudioBufferChannel& AudioBufferChannel::operator+=(const AudioBufferChannel& channel) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(channel._isEnabled); + AMPLITUDE_ASSERT(_frameCount == channel._frameCount); + + AmSize remaining = _frameCount; + +#if defined(AM_SIMD_INTRINSICS) + const AmSize end = GetNumSimdChunks(_frameCount); + constexpr AmSize blockSize = GetSimdBlockSize(); + remaining = remaining - end; + + for (AmSize i = 0; i < end; i += blockSize) + { + const auto ba = xsimd::load_aligned(_begin + i); + const auto bb = xsimd::load_aligned(channel._begin + i); + + auto res = xsimd::add(ba, bb); + res.store_aligned(_begin + i); + } +#endif + + for (AmSize i = _frameCount - remaining; i < _frameCount; i++) + { + _begin[i] += channel._begin[i]; + } + + return *this; + } + + AudioBufferChannel& AudioBufferChannel::operator-=(const AudioBufferChannel& channel) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(channel._isEnabled); + AMPLITUDE_ASSERT(_frameCount == channel._frameCount); + + AmSize remaining = _frameCount; + +#if defined(AM_SIMD_INTRINSICS) + const AmSize end = GetNumSimdChunks(_frameCount); + constexpr AmSize blockSize = GetSimdBlockSize(); + remaining = remaining - end; + + for (AmSize i = 0; i < end; i += blockSize) + { + const auto ba = xsimd::load_aligned(_begin + i); + const auto bb = xsimd::load_aligned(channel._begin + i); + + auto res = xsimd::sub(ba, bb); + res.store_aligned(_begin + i); + } +#endif + + for (AmSize i = _frameCount - remaining; i < _frameCount; i++) + { + _begin[i] -= channel._begin[i]; + } + + return *this; + } + + AudioBufferChannel& AudioBufferChannel::operator*=(const AudioBufferChannel& channel) + { + AMPLITUDE_ASSERT(_isEnabled); + AMPLITUDE_ASSERT(channel._isEnabled); + AMPLITUDE_ASSERT(_frameCount == channel._frameCount); + + PointwiseMultiply(_begin, channel._begin, _begin, _frameCount); + + return *this; + } + + AudioBufferChannel& AudioBufferChannel::operator*=(AmReal32 scalar) + { + AMPLITUDE_ASSERT(_isEnabled); + + ScalarMultiply(_begin, _begin, scalar, _frameCount); + + return *this; + } + + AudioBufferChannel::AudioBufferChannel(AmReal32* begin, const AmSize numFrames) + : _begin(begin) + , _frameCount(numFrames) + , _isEnabled(true) + {} + + void AudioBuffer::Copy( + const AudioBuffer& source, AmSize sourceOffset, AudioBuffer& destination, AmSize destinationOffset, AmSize numFrames) + { + AMPLITUDE_ASSERT(destination.GetChannelCount() >= source.GetChannelCount()); + AMPLITUDE_ASSERT(sourceOffset + numFrames <= source.GetFrameCount()); + AMPLITUDE_ASSERT(destinationOffset + numFrames <= destination.GetFrameCount()); + + for (AmSize i = 0, l = source.GetChannelCount(); i < l; ++i) + { + const auto& srcChannel = source[i]; + auto& dstChannel = destination[i]; + + std::memcpy(dstChannel.begin() + destinationOffset, srcChannel.begin() + sourceOffset, sizeof(AmReal32) * numFrames); + } + } + + AudioBuffer::AudioBuffer() + : _frameCount(0) + {} + + AudioBuffer::AudioBuffer(const AmSize numFrames, AmSize numChannels) + : _frameCount(numFrames) + { + Initialize(numChannels); + } + + AudioBuffer::AudioBuffer(AudioBuffer&& buffer) noexcept + { + _frameCount = buffer._frameCount; + _channels = std::move(buffer._channels); + AmAlignedReal32Buffer::Swap(_data, buffer._data); + + buffer._frameCount = 0; + buffer._channels.clear(); + buffer._data.Clear(); + } + + AudioBuffer::~AudioBuffer() + { + _data.Release(); + } + + AmSize AudioBuffer::GetFrameCount() const + { + return _frameCount; + } + + AmSize AudioBuffer::GetChannelCount() const + { + return _channels.size(); + } + + void AudioBuffer::Clear() + { + for (auto& channel : _channels) + channel.clear(); + } + + const AmAlignedReal32Buffer& AudioBuffer::GetData() const + { + return _data; + } + + AudioBufferChannel& AudioBuffer::GetChannel(AmSize index) + { + AMPLITUDE_ASSERT(index < _channels.size()); + return _channels[index]; + } + + const AudioBufferChannel& AudioBuffer::GetChannel(AmSize index) const + { + AMPLITUDE_ASSERT(index < _channels.size()); + return _channels[index]; + } + + AudioBuffer AudioBuffer::Clone() const + { + AudioBuffer result(_frameCount, GetChannelCount()); + Copy(*this, 0, result, 0, _frameCount); + return result; + } + + AudioBufferChannel& AudioBuffer::operator[](const AmSize index) + { + return GetChannel(index); + } + + const AudioBufferChannel& AudioBuffer::operator[](const AmSize index) const + { + return GetChannel(index); + } + + AudioBuffer& AudioBuffer::operator=(const AudioBuffer& buffer) + { + if (this != &buffer) + { + _frameCount = buffer._frameCount; + Initialize(buffer.GetChannelCount()); + + for (AmSize i = 0; i < GetChannelCount(); ++i) + _channels[i] = buffer._channels[i]; + } + + return *this; + } + + AudioBuffer& AudioBuffer::operator+=(const AudioBuffer& buffer) + { + AMPLITUDE_ASSERT(_frameCount == buffer._frameCount); + + for (AmSize i = 0; i < GetChannelCount(); ++i) + _channels[i] += buffer._channels[i]; + + return *this; + } + + AudioBuffer& AudioBuffer::operator-=(const AudioBuffer& buffer) + { + AMPLITUDE_ASSERT(_frameCount == buffer._frameCount); + + for (AmSize i = 0; i < GetChannelCount(); ++i) + _channels[i] -= buffer._channels[i]; + + return *this; + } + + AudioBuffer& AudioBuffer::operator*=(const AudioBuffer& buffer) + { + AMPLITUDE_ASSERT(_frameCount == buffer._frameCount); + + for (AmSize i = 0; i < GetChannelCount(); ++i) + _channels[i] *= buffer._channels[i]; + + return *this; + } + + void AudioBuffer::Initialize(const AmSize channelCount) + { + const AmSize alignedSize = FindNextAlignedArrayIndex(_frameCount, AM_SIMD_ALIGNMENT); + + _data.Resize(alignedSize * channelCount, true); + + _channels.clear(); + _channels.reserve(channelCount); + + AmReal32* itr = _data.GetBuffer(); + + for (AmSize i = 0; i < channelCount; ++i) + { + AudioBufferChannel channelBuffer(itr, _frameCount); + _channels.push_back(std::move(channelBuffer)); + itr += alignedSize; + } + } +} // namespace SparkyStudios::Audio::Amplitude \ No newline at end of file diff --git a/src/Core/Codec.cpp b/src/Core/Codec.cpp index f718ad69..29a11a95 100644 --- a/src/Core/Codec.cpp +++ b/src/Core/Codec.cpp @@ -45,11 +45,21 @@ namespace SparkyStudios::Audio::Amplitude , m_codec(codec) {} + const SoundFormat& Codec::Decoder::GetFormat() const + { + return m_format; + } + Codec::Encoder::Encoder(const Codec* codec) : m_format() , m_codec(codec) {} + void Codec::Encoder::SetFormat(const SoundFormat& format) + { + m_format = format; + } + Codec::Codec(AmString name) : m_name(std::move(name)) { @@ -61,6 +71,11 @@ namespace SparkyStudios::Audio::Amplitude Unregister(this); } + const AmString& Codec::GetName() const + { + return m_name; + } + void Codec::Register(Codec* codec) { if (lockCodecs()) diff --git a/src/Core/Codecs/AMS/Codec.cpp b/src/Core/Codecs/AMS/Codec.cpp index 7820d7cb..7a475df4 100644 --- a/src/Core/Codecs/AMS/Codec.cpp +++ b/src/Core/Codecs/AMS/Codec.cpp @@ -558,7 +558,7 @@ namespace SparkyStudios::Audio::Amplitude return true; } - AmUInt64 AMSCodec::AMSDecoder::Load(AmVoidPtr out) + AmUInt64 AMSCodec::AMSDecoder::Load(AudioBuffer* out) { if (!_initialized) return 0; @@ -569,15 +569,15 @@ namespace SparkyStudios::Audio::Amplitude return Decode(_file, m_format, out, 0, m_format.GetFramesCount(), _blockSize); } - AmUInt64 AMSCodec::AMSDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) + AmUInt64 AMSCodec::AMSDecoder::Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) { if (!_initialized) return 0; - if (!Seek(offset)) + if (!Seek(seekOffset)) return 0; - return Decode(_file, m_format, out, offset, length, _blockSize); + return Decode(_file, m_format, out, seekOffset, length, _blockSize); } bool AMSCodec::AMSDecoder::Seek(AmUInt64 offset) @@ -619,7 +619,7 @@ namespace SparkyStudios::Audio::Amplitude return true; } - AmUInt64 AMSCodec::AMSEncoder::Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) + AmUInt64 AMSCodec::AMSEncoder::Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) { _file->Seek(sizeof(ADPCMHeader) + offset, eFSO_START); return Encode(_file, m_format, in, length, _samplesPerBlock, _lookAhead, _noiseShaping); diff --git a/src/Core/Codecs/AMS/Codec.h b/src/Core/Codecs/AMS/Codec.h index c6f2b237..97b9b4c3 100644 --- a/src/Core/Codecs/AMS/Codec.h +++ b/src/Core/Codecs/AMS/Codec.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_AMS_CODEC_H -#define SS_AMPLITUDE_AUDIO_AMS_CODEC_H +#ifndef _AM_IMPLEMENTATION_CORE_CODECS_AMS_CODEC_H +#define _AM_IMPLEMENTATION_CORE_CODECS_AMS_CODEC_H #include @@ -40,9 +40,9 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Load(AmVoidPtr out) override; + AmUInt64 Load(AudioBuffer* out) override; - AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) override; bool Seek(AmUInt64 offset) override; @@ -69,7 +69,7 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) override; void SetEncodingParams( AmUInt32 blockSize, AmUInt32 samplesPerBlock, AmUInt32 lookAhead, Compression::ADPCM::NoiseShapingMode noiseShaping); @@ -99,4 +99,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_AMS_CODEC_H +#endif // _AM_IMPLEMENTATION_CORE_CODECS_AMS_CODEC_H diff --git a/src/Core/Codecs/MP3/Codec.cpp b/src/Core/Codecs/MP3/Codec.cpp index 15bf48e5..fac07064 100644 --- a/src/Core/Codecs/MP3/Codec.cpp +++ b/src/Core/Codecs/MP3/Codec.cpp @@ -16,6 +16,7 @@ #include "dr_mp3.h" #include +#include namespace SparkyStudios::Audio::Amplitude { @@ -109,20 +110,27 @@ namespace SparkyStudios::Audio::Amplitude return true; } - AmUInt64 MP3Codec::MP3Decoder::Load(AmVoidPtr out) + AmUInt64 MP3Codec::MP3Decoder::Load(AudioBuffer* out) { - return Stream(out, 0, m_format.GetFramesCount()); + return Stream(out, 0, 0, m_format.GetFramesCount()); } - AmUInt64 MP3Codec::MP3Decoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) + AmUInt64 MP3Codec::MP3Decoder::Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) { if (!_initialized) return 0; - if (!Seek(offset)) + if (!Seek(seekOffset)) return 0; - return drmp3_read_pcm_frames_f32(&_mp3, length, static_cast(out)); + AmAlignedReal32Buffer buffer; + buffer.Init(length); + + const AmUInt64 read = drmp3_read_pcm_frames_f32(&_mp3, length, buffer.GetBuffer()); + + Deinterleave(buffer.GetBuffer(), 0, out->GetData().GetBuffer(), bufferOffset, length, _mp3.channels); + + return read; } bool MP3Codec::MP3Decoder::Seek(AmUInt64 offset) @@ -144,7 +152,7 @@ namespace SparkyStudios::Audio::Amplitude return true; } - AmUInt64 MP3Codec::MP3Encoder::Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) + AmUInt64 MP3Codec::MP3Encoder::Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) { return 0; } diff --git a/src/Core/Codecs/MP3/Codec.h b/src/Core/Codecs/MP3/Codec.h index 2cd9890b..f0f5f843 100644 --- a/src/Core/Codecs/MP3/Codec.h +++ b/src/Core/Codecs/MP3/Codec.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_MP3_CODEC_H -#define SS_AMPLITUDE_AUDIO_MP3_CODEC_H +#ifndef _AM_IMPLEMENTATION_CORE_CODECS_MP3_CODEC_H +#define _AM_IMPLEMENTATION_CORE_CODECS_MP3_CODEC_H #include @@ -39,9 +39,9 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Load(AmVoidPtr out) override; + AmUInt64 Load(AudioBuffer* out) override; - AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) override; bool Seek(AmUInt64 offset) override; @@ -64,7 +64,7 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) override; private: bool _initialized; @@ -89,4 +89,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_MP3_CODEC_H +#endif // _AM_IMPLEMENTATION_CORE_CODECS_MP3_CODEC_H diff --git a/src/Core/Codecs/WAV/Codec.cpp b/src/Core/Codecs/WAV/Codec.cpp index 6ccae47a..cfcaab77 100644 --- a/src/Core/Codecs/WAV/Codec.cpp +++ b/src/Core/Codecs/WAV/Codec.cpp @@ -16,6 +16,7 @@ #include "dr_wav.h" #include +#include namespace SparkyStudios::Audio::Amplitude { @@ -106,26 +107,27 @@ namespace SparkyStudios::Audio::Amplitude return drwav_uninit(&_wav) == DRWAV_SUCCESS; } - AmUInt64 WAVCodec::WAVDecoder::Load(AmVoidPtr out) + AmUInt64 WAVCodec::WAVDecoder::Load(AudioBuffer* out) { - if (!_initialized) - return 0; - - if (!Seek(0)) - return 0; - - return drwav_read_pcm_frames_f32(&_wav, _wav.totalPCMFrameCount, static_cast(out)); + return Stream(out, 0, 0, _wav.totalPCMFrameCount); } - AmUInt64 WAVCodec::WAVDecoder::Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) + AmUInt64 WAVCodec::WAVDecoder::Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) { if (!_initialized) return 0; - if (!Seek(offset)) + if (!Seek(seekOffset)) return 0; - return drwav_read_pcm_frames_f32(&_wav, length, static_cast(out)); + AmAlignedReal32Buffer buffer; + buffer.Init(length); + + const AmUInt64 read = drwav_read_pcm_frames_f32(&_wav, length, buffer.GetBuffer()); + + Deinterleave(buffer.GetBuffer(), 0, out->GetData().GetBuffer(), bufferOffset, length, _wav.channels); + + return read; } bool WAVCodec::WAVDecoder::Seek(AmUInt64 offset) @@ -189,9 +191,17 @@ namespace SparkyStudios::Audio::Amplitude return drwav_uninit(&_wav) == DRWAV_SUCCESS; } - AmUInt64 WAVCodec::WAVEncoder::Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) + AmUInt64 WAVCodec::WAVEncoder::Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) { - return !_initialized ? 0 : drwav_write_pcm_frames(&_wav, length, in); + if (!_initialized) + return 0; + + AmAlignedReal32Buffer buffer; + buffer.Init(length); + + Interleave(in, 0, buffer.GetBuffer(), 0, length, _wav.channels); + + return drwav_write_pcm_frames(&_wav, length, buffer.GetBuffer()); } Codec::Decoder* WAVCodec::CreateDecoder() diff --git a/src/Core/Codecs/WAV/Codec.h b/src/Core/Codecs/WAV/Codec.h index 75033aad..81d5bced 100644 --- a/src/Core/Codecs/WAV/Codec.h +++ b/src/Core/Codecs/WAV/Codec.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_WAV_CODEC_H -#define SS_AMPLITUDE_AUDIO_WAV_CODEC_H +#ifndef _AM_IMPLEMENTATION_CORE_CODECS_WAV_CODEC_H +#define _AM_IMPLEMENTATION_CORE_CODECS_WAV_CODEC_H #include @@ -39,9 +39,9 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Load(AmVoidPtr out) override; + AmUInt64 Load(AudioBuffer* out) override; - AmUInt64 Stream(AmVoidPtr out, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Stream(AudioBuffer* out, AmUInt64 bufferOffset, AmUInt64 seekOffset, AmUInt64 length) override; bool Seek(AmUInt64 offset) override; @@ -68,7 +68,7 @@ namespace SparkyStudios::Audio::Amplitude bool Close() override; - AmUInt64 Write(AmVoidPtr in, AmUInt64 offset, AmUInt64 length) override; + AmUInt64 Write(AudioBuffer* in, AmUInt64 offset, AmUInt64 length) override; private: std::shared_ptr _file; @@ -95,4 +95,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_WAV_CODEC_H +#endif // _AM_IMPLEMENTATION_CORE_CODECS_WAV_CODEC_H diff --git a/src/Core/Common.cpp b/src/Core/Common.cpp index 020543bd..7008bc1c 100644 --- a/src/Core/Common.cpp +++ b/src/Core/Common.cpp @@ -91,10 +91,8 @@ namespace SparkyStudios::Audio::Amplitude return; } - if (size != m_floats) + if (size > m_floats) { - m_floats = size; - #ifndef AM_SIMD_INTRINSICS m_basePtr = static_cast(amrealloc(m_basePtr, size * sizeof(AmReal32))); #else @@ -104,6 +102,8 @@ namespace SparkyStudios::Audio::Amplitude m_data = reinterpret_cast(m_basePtr); } + m_floats = size; + if (clear) Clear(); } diff --git a/src/Core/DefaultPlugins.h b/src/Core/DefaultPlugins.h index 8af44c11..e6811213 100644 --- a/src/Core/DefaultPlugins.h +++ b/src/Core/DefaultPlugins.h @@ -14,23 +14,25 @@ #pragma once -#ifndef AMPLITUDE_DEFAULTPLUGINS_H -#define AMPLITUDE_DEFAULTPLUGINS_H +#ifndef _AM_IMPLEMENTATION_CORE_DEFAULT_PLUGINS_H +#define _AM_IMPLEMENTATION_CORE_DEFAULT_PLUGINS_H #pragma region Default Sound Processors -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #pragma endregion #pragma region Default Resamplers +#include #include #include @@ -66,17 +68,23 @@ #pragma region Default Filters -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #pragma endregion -#endif // AMPLITUDE_DEFAULTPLUGINS_H +#pragma region Default HRTFs + +#include + +#pragma endregion + +#endif // _AM_IMPLEMENTATION_CORE_DEFAULT_PLUGINS_H diff --git a/src/Core/Drivers/MiniAudio/Driver.cpp b/src/Core/Drivers/MiniAudio/Driver.cpp index ec2aa21b..c4450608 100644 --- a/src/Core/Drivers/MiniAudio/Driver.cpp +++ b/src/Core/Drivers/MiniAudio/Driver.cpp @@ -64,10 +64,19 @@ namespace SparkyStudios::Audio::Amplitude static void miniaudio_mixer(ma_device* pDevice, AmVoidPtr pOutput, AmConstVoidPtr pInput, ma_uint32 frameCount) { - AM_UNUSED(pDevice); AM_UNUSED(pInput); - amEngine->GetMixer()->Mix(pOutput, frameCount); + auto* driver = static_cast(pDevice->pUserData); + + AudioBuffer* pOutputBuffer = nullptr; + frameCount = amEngine->GetMixer()->Mix(&pOutputBuffer, frameCount); + + if (pOutputBuffer == nullptr || frameCount == 0) + return; + + Interleave( + pOutputBuffer, 0, static_cast(pOutput), 0, frameCount, + static_cast(driver->GetDeviceDescription().mRequestedOutputChannels)); } void miniaudio_device_notification(const ma_device_notification* pNotification) @@ -115,154 +124,8 @@ namespace SparkyStudios::Audio::Amplitude } } - static ma_result ma_resampling_backend_get_heap_size_ls(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) - { - AM_UNUSED(pConfig); - AM_UNUSED(pUserData); - - *pHeapSizeInBytes = 0; - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_init_ls( - void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend) - { - AM_UNUSED(pHeap); - - auto* pResampler = Resampler::Construct("libsamplerate"); - const auto* pDeviceDescription = static_cast(pUserData); - - const AmUInt64 maxFramesIn = pDeviceDescription->mOutputBufferSize / pConfig->channels; - pResampler->Init(pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut, maxFramesIn); - - *ppBackend = pResampler; - - return MA_SUCCESS; - } - - static void ma_resampling_backend_uninit_ls( - void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) - { - AM_UNUSED(pUserData); - AM_UNUSED(pAllocationCallbacks); - - auto* pResampler = static_cast(pBackend); - pResampler->Clear(); - - Resampler::Destruct("libsamplerate", pResampler); - } - - static ma_result ma_resampling_backend_process_ls( - void* pUserData, - ma_resampling_backend* pBackend, - const void* pFramesIn, - ma_uint64* pFrameCountIn, - void* pFramesOut, - ma_uint64* pFrameCountOut) - { - AM_UNUSED(pUserData); - - auto* pResampler = static_cast(pBackend); - - if (pResampler == nullptr) - return MA_INVALID_ARGS; - - if (pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - { - std::memcpy(pFramesOut, pFramesIn, *pFrameCountIn * pResampler->GetChannelCount() * sizeof(AmAudioSample)); - return MA_SUCCESS; - } - - const bool result = pResampler->Process( - static_cast(pFramesIn), *pFrameCountIn, static_cast(pFramesOut), - *pFrameCountOut); - - return result ? MA_SUCCESS : MA_ERROR; - } - - static ma_result ma_resampling_backend_set_rate_ls( - void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) - { - AM_UNUSED(pUserData); - - if (auto* pResampler = static_cast(pBackend); - pResampler->GetSampleRateIn() != sampleRateIn || pResampler->GetSampleRateOut() != sampleRateOut) - pResampler->SetSampleRate(sampleRateIn, sampleRateOut); - - return MA_SUCCESS; - } - - static ma_uint64 ma_resampling_backend_get_input_latency_ls(void* pUserData, const ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - - const auto* pResampler = static_cast(pBackend); - - return pResampler->GetInputLatency(); - } - - static ma_uint64 ma_resampling_backend_get_output_latency_ls(void* pUserData, const ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - - const auto* pResampler = static_cast(pBackend); - - return pResampler->GetOutputLatency(); - } - - static ma_result ma_resampling_backend_get_required_input_frame_count_ls( - void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) - { - AM_UNUSED(pUserData); - - // Sample rate is the same, so ratio is 1:1 - if (const auto* pResampler = static_cast(pBackend); - pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - *pInputFrameCount = outputFrameCount; - else - *pInputFrameCount = pResampler->GetRequiredInputFrameCount(outputFrameCount); - - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_get_expected_output_frame_count_ls( - void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) - { - AM_UNUSED(pUserData); - - // Sample rate is the same, so ratio is 1:1 - if (const auto* pResampler = static_cast(pBackend); - pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - *pOutputFrameCount = inputFrameCount; - else - *pOutputFrameCount = pResampler->GetExpectedOutputFrameCount(inputFrameCount); - - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_reset_ls(void* pUserData, ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - - auto* pResampler = static_cast(pBackend); - pResampler->Reset(); - - return MA_SUCCESS; - } - static ma_allocation_callbacks gAllocationCallbacks = { nullptr, ma_malloc, ma_realloc, ma_free }; - static ma_resampling_backend_vtable gResamplerVTable = { ma_resampling_backend_get_heap_size_ls, - ma_resampling_backend_init_ls, - ma_resampling_backend_uninit_ls, - ma_resampling_backend_process_ls, - ma_resampling_backend_set_rate_ls, - ma_resampling_backend_get_input_latency_ls, - ma_resampling_backend_get_output_latency_ls, - ma_resampling_backend_get_required_input_frame_count_ls, - ma_resampling_backend_get_expected_output_frame_count_ls, - ma_resampling_backend_reset_ls }; - MiniAudioDriver::MiniAudioDriver() : Driver("miniaudio") , _initialized(false) @@ -323,9 +186,7 @@ namespace SparkyStudios::Audio::Amplitude deviceConfig.dataCallback = miniaudio_mixer; deviceConfig.notificationCallback = miniaudio_device_notification; deviceConfig.pUserData = static_cast(this); - deviceConfig.resampling.algorithm = ma_resample_algorithm_custom; - deviceConfig.resampling.pBackendVTable = &gResamplerVTable; - deviceConfig.resampling.pBackendUserData = static_cast(&m_deviceDescription); + deviceConfig.resampling.algorithm = ma_resample_algorithm_linear; ma_channel_map_init_standard(ma_standard_channel_map_vorbis, deviceConfig.playback.pChannelMap, channelsCount, channelsCount); diff --git a/src/Core/Drivers/MiniAudio/Driver.h b/src/Core/Drivers/MiniAudio/Driver.h index 702dea26..15c75eee 100644 --- a/src/Core/Drivers/MiniAudio/Driver.h +++ b/src/Core/Drivers/MiniAudio/Driver.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_MINIAUDIO_DRIVER_H -#define SS_AMPLITUDE_AUDIO_MINIAUDIO_DRIVER_H +#ifndef _AM_IMPLEMENTATION_CORE_DRIVERS_MINIAUDIO_DRIVER_H +#define _AM_IMPLEMENTATION_CORE_DRIVERS_MINIAUDIO_DRIVER_H #include @@ -52,4 +52,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_MINIAUDIO_DRIVER_H +#endif // _AM_IMPLEMENTATION_CORE_DRIVERS_MINIAUDIO_DRIVER_H diff --git a/src/Core/Drivers/Null/Driver.cpp b/src/Core/Drivers/Null/Driver.cpp index 90ff8f43..ebe29f4e 100644 --- a/src/Core/Drivers/Null/Driver.cpp +++ b/src/Core/Drivers/Null/Driver.cpp @@ -24,7 +24,7 @@ namespace SparkyStudios::Audio::Amplitude while (data->mRunning) { - Engine::GetInstance()->GetMixer()->Mix(data->mOutputBuffer, data->mOutputBufferSize); + Engine::GetInstance()->GetMixer()->Mix(nullptr, data->mOutputBufferSize); Thread::Sleep(1); } } @@ -48,7 +48,6 @@ namespace SparkyStudios::Audio::Amplitude return false; _deviceData.mOutputBufferSize = device.mOutputBufferSize / static_cast(device.mRequestedOutputChannels); - _deviceData.mOutputBuffer = ampoolmalign(MemoryPoolKind::Amplimix, device.mOutputBufferSize * sizeof(AmReal32), AM_SIMD_ALIGNMENT); _deviceData.mRunning = true; _thread = Thread::CreateThread(null_mix, &_deviceData); @@ -68,9 +67,6 @@ namespace SparkyStudios::Audio::Amplitude Thread::Release(_thread); _thread = nullptr; - ampoolfree(MemoryPoolKind::Amplimix, _deviceData.mOutputBuffer); - _deviceData.mOutputBuffer = nullptr; - _deviceData.mOutputBufferSize = 0; _initialized = false; diff --git a/src/Core/Drivers/Null/Driver.h b/src/Core/Drivers/Null/Driver.h index ec73015b..3a29fadb 100644 --- a/src/Core/Drivers/Null/Driver.h +++ b/src/Core/Drivers/Null/Driver.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_NULL_DRIVER_H -#define SS_AMPLITUDE_AUDIO_NULL_DRIVER_H +#ifndef _AM_IMPLEMENTATION_CORE_DRIVERS_NULL_DRIVER_H +#define _AM_IMPLEMENTATION_CORE_DRIVERS_NULL_DRIVER_H #include @@ -24,7 +24,6 @@ namespace SparkyStudios::Audio::Amplitude struct NullDriverDeviceData { AmUInt32 mOutputBufferSize; - AmVoidPtr mOutputBuffer; bool mRunning; }; @@ -50,4 +49,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_NULL_DRIVER_H +#endif // _AM_IMPLEMENTATION_CORE_DRIVERS_NULL_DRIVER_H diff --git a/src/Core/Engine.cpp b/src/Core/Engine.cpp index b640d606..87a2424f 100644 --- a/src/Core/Engine.cpp +++ b/src/Core/Engine.cpp @@ -64,6 +64,7 @@ namespace SparkyStudios::Audio::Amplitude static AmUniquePtr sPassThroughProcessorPlugin = nullptr; static AmUniquePtr sSilenceProcessorPlugin = nullptr; // --- + static AmUniquePtr sDefaultResamplerPlugin = nullptr; static AmUniquePtr sLibsamplerateResamplerPlugin = nullptr; static AmUniquePtr sR8BrainResamplerPlugin = nullptr; // --- @@ -319,6 +320,7 @@ namespace SparkyStudios::Audio::Amplitude sPassThroughProcessorPlugin.reset(ampoolnew(MemoryPoolKind::Engine, PassThroughProcessor)); sSilenceProcessorPlugin.reset(ampoolnew(MemoryPoolKind::Engine, SilenceProcessor)); // --- + sDefaultResamplerPlugin.reset(ampoolnew(MemoryPoolKind::Engine, DefaultResampler)); sLibsamplerateResamplerPlugin.reset(ampoolnew(MemoryPoolKind::Engine, LibsamplerateResampler)); sR8BrainResamplerPlugin.reset(ampoolnew(MemoryPoolKind::Engine, R8BrainResampler)); // --- @@ -366,6 +368,7 @@ namespace SparkyStudios::Audio::Amplitude sPassThroughProcessorPlugin.reset(nullptr); sSilenceProcessorPlugin.reset(nullptr); // --- + sDefaultResamplerPlugin.reset(nullptr); sLibsamplerateResamplerPlugin.reset(nullptr); sR8BrainResamplerPlugin.reset(nullptr); // --- diff --git a/src/Core/EnvironmentInternalState.h b/src/Core/EnvironmentInternalState.h index 1938e62f..2a5f7eb7 100644 --- a/src/Core/EnvironmentInternalState.h +++ b/src/Core/EnvironmentInternalState.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_ENVIRONMENT_INTERNAL_STATE_H -#define SS_AMPLITUDE_AUDIO_ENVIRONMENT_INTERNAL_STATE_H +#ifndef _AM_IMPLEMENTATION_CORE_ENVIRONMENT_INTERNAL_STATE_H +#define _AM_IMPLEMENTATION_CORE_ENVIRONMENT_INTERNAL_STATE_H #include @@ -173,4 +173,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_ENVIRONMENT_INTERNAL_STATE_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_CORE_ENVIRONMENT_INTERNAL_STATE_H \ No newline at end of file diff --git a/src/Core/ListenerInternalState.h b/src/Core/ListenerInternalState.h index 8bcfbc50..4d2b06b9 100644 --- a/src/Core/ListenerInternalState.h +++ b/src/Core/ListenerInternalState.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SPARK_AUDIO_LISTENER_INTERNAL_STATE_H -#define SPARK_AUDIO_LISTENER_INTERNAL_STATE_H +#ifndef _AM_IMPLEMENTATION_CORE_LISTENER_INTERNAL_STATE_H +#define _AM_IMPLEMENTATION_CORE_LISTENER_INTERNAL_STATE_H #include @@ -193,4 +193,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SPARK_AUDIO_LISTENER_INTERNAL_STATE_H +#endif // _AM_IMPLEMENTATION_CORE_LISTENER_INTERNAL_STATE_H diff --git a/src/Core/Playback/ChannelInternalState.h b/src/Core/Playback/ChannelInternalState.h index 2eedeb7b..9ee57d04 100644 --- a/src/Core/Playback/ChannelInternalState.h +++ b/src/Core/Playback/ChannelInternalState.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_CHANNEL_INTERNAL_STATE_H -#define SS_AMPLITUDE_AUDIO_CHANNEL_INTERNAL_STATE_H +#ifndef _AM_IMPLEMENTATION_CORE_PLAYBACK_CHANNEL_INTERNAL_STATE_H +#define _AM_IMPLEMENTATION_CORE_PLAYBACK_CHANNEL_INTERNAL_STATE_H #include @@ -421,4 +421,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_CHANNEL_INTERNAL_STATE_H +#endif // _AM_IMPLEMENTATION_CORE_PLAYBACK_CHANNEL_INTERNAL_STATE_H diff --git a/src/DSP/AudioConverter.cpp b/src/DSP/AudioConverter.cpp new file mode 100644 index 00000000..a0b05167 --- /dev/null +++ b/src/DSP/AudioConverter.cpp @@ -0,0 +1,158 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + AudioConverter::AudioConverter() + : _resampler(nullptr) + , _channelConversionMode(kChannelConversionModeDisabled) + { + _resampler = Resampler::Construct("default"); + Reset(); + } + + AudioConverter::~AudioConverter() + { + Resampler::Destruct("default", _resampler); + } + + bool AudioConverter::Configure(const Settings& settings) + { + if (settings.m_sourceChannelCount == settings.m_targetChannelCount) + _channelConversionMode = kChannelConversionModeDisabled; + else if (settings.m_sourceChannelCount == 1 && settings.m_targetChannelCount == 2) + _channelConversionMode = kChannelConversionModeMonoToStereo; + else if (settings.m_sourceChannelCount == 2 && settings.m_targetChannelCount == 1) + _channelConversionMode = kChannelConversionModeStereoToMono; + else + return false; // Unsupported channel conversion mode + + _needResampling = settings.m_sourceChannelCount != settings.m_targetChannelCount; + _resampler->Initialize(settings.m_targetChannelCount, settings.m_sourceSampleRate, settings.m_targetSampleRate); + + return true; + } + + void AudioConverter::Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) + { + AudioBuffer temp; + if (_channelConversionMode == kChannelConversionModeDisabled) + { + // No channel conversion required, just copy the input to the output. + temp = input; + } + else if (_channelConversionMode == kChannelConversionModeMonoToStereo) + { + temp = AudioBuffer(input.GetFrameCount(), 2); + ConvertStereoFromMono(input, temp); + } + else if (_channelConversionMode == kChannelConversionModeStereoToMono) + { + temp = AudioBuffer(input.GetFrameCount(), 1); + ConvertMonoFromStereo(input, temp); + } + + AMPLITUDE_ASSERT(temp.GetChannelCount() == output.GetChannelCount()); + + if (_needResampling) + _resampler->Process(temp, inputFrames, output, outputFrames); + else + AudioBuffer::Copy(temp, 0, output, 0, outputFrames); + } + + void AudioConverter::SetSampleRate(AmUInt64 sourceSampleRate, AmUInt64 targetSampleRate) + { + _needResampling = sourceSampleRate != targetSampleRate; + _resampler->SetSampleRate(sourceSampleRate, targetSampleRate); + } + + AmUInt64 AudioConverter::GetRequiredInputFrameCount(AmUInt64 outputFrameCount) const + { + return _resampler->GetRequiredInputFrames(outputFrameCount); + } + + AmUInt64 AudioConverter::GetExpectedOutputFrameCount(AmUInt64 inputFrameCount) const + { + return _resampler->GetExpectedOutputFrames(inputFrameCount); + } + + AmUInt64 AudioConverter::GetInputLatency() const + { + return _resampler->GetInputLatency(); + } + + AmUInt64 AudioConverter::GetOutputLatency() const + { + return _resampler->GetOutputLatency(); + } + + void AudioConverter::Reset() + { + _channelConversionMode = kChannelConversionModeDisabled; + _resampler->Reset(); + } + + void AudioConverter::ConvertStereoFromMono(const AudioBuffer& input, AudioBuffer& output) + { + AMPLITUDE_ASSERT(input.GetChannelCount() == 1); + AMPLITUDE_ASSERT(output.GetChannelCount() == 2); + AMPLITUDE_ASSERT(input.GetFrameCount() == output.GetFrameCount()); + + auto& left = output[0]; + auto& right = output[1]; + const auto& mono = input[0]; + + ScalarMultiply(mono.begin(), left.begin(), AM_InvSqrtF(2), input.GetFrameCount()); + std::copy_n(left.begin(), input.GetFrameCount(), right.begin()); + } + + void AudioConverter::ConvertMonoFromStereo(const AudioBuffer& input, AudioBuffer& output) + { + AMPLITUDE_ASSERT(input.GetChannelCount() == 2); + AMPLITUDE_ASSERT(output.GetChannelCount() == 1); + AMPLITUDE_ASSERT(input.GetFrameCount() == output.GetFrameCount()); + + const AmSize length = input.GetFrameCount(); + AmSize remaining = input.GetFrameCount(); + const AmReal32 invSqrt2 = AM_InvSqrtF(2); + + const auto& left = input[0]; + const auto& right = input[1]; + auto& mono = output[0]; + +#if defined(AM_SIMD_INTRINSICS) + const auto invSqrt2Vec = xsimd::batch(invSqrt2); + const AmSize end = GetNumSimdChunks(length); + constexpr AmSize blockSize = GetSimdBlockSize(); + + remaining = remaining - end; + + for (AmSize i = 0; i < end; i += blockSize) + { + const auto ba = xsimd::load_unaligned(&left[i]); + const auto bb = xsimd::load_unaligned(&right[i]); + + auto res = invSqrt2Vec * (ba + bb); + res.store_aligned(&mono[i]); + } +#endif + + for (AmSize i = length - remaining; i < length; i++) + mono[i] = invSqrt2 * (left[i] + right[i]); + } +} // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Sound/Filter.cpp b/src/DSP/Filter.cpp similarity index 84% rename from src/Sound/Filter.cpp rename to src/DSP/Filter.cpp index fb0b103e..8f6362bf 100644 --- a/src/Sound/Filter.cpp +++ b/src/DSP/Filter.cpp @@ -13,7 +13,7 @@ // limitations under the License. #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -163,7 +163,7 @@ namespace SparkyStudios::Audio::Amplitude ampoolfree(MemoryPoolKind::Filtering, m_parameters); } - AmResult FilterInstance::Init(AmUInt32 numParams) + AmResult FilterInstance::Initialize(AmUInt32 numParams) { if (m_parameters != nullptr) ampoolfree(MemoryPoolKind::Filtering, m_parameters); @@ -191,23 +191,21 @@ namespace SparkyStudios::Audio::Amplitude void FilterInstance::AdvanceFrame(AmTime delta_time) {} - void FilterInstance::Process(AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) + void FilterInstance::Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) { - if (buffer == nullptr) - return; + AMPLITUDE_ASSERT(out.GetChannelCount() >= in.GetChannelCount()); - for (AmUInt16 c = 0; c < channels; c++) - ProcessChannel(buffer, c, frames, channels, sampleRate); + for (AmUInt16 c = 0, l = in.GetChannelCount(); c < l; c++) + ProcessChannel(in, out, c, frames, sampleRate); } - void FilterInstance::ProcessChannel( - AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) + void FilterInstance::ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) { - if (buffer == nullptr) - return; + const auto& inChannel = in[channel]; + auto& outChannel = out[channel]; - for (AmUInt64 i = channel; i < frames * channels; i += channels) - buffer[i] = ProcessSample(buffer[i], channel, sampleRate); + for (AmUInt64 s = 0, l = in.GetFrameCount(); s < l; s++) + outChannel[s] = ProcessSample(inChannel[s], channel, sampleRate); } AmAudioSample FilterInstance::ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) @@ -215,7 +213,7 @@ namespace SparkyStudios::Audio::Amplitude return sample; } - AmReal32 FilterInstance::GetFilterParameter(AmUInt32 attributeId) + AmReal32 FilterInstance::GetParameter(AmUInt32 attributeId) { if (attributeId >= m_numParams) return 0; @@ -223,7 +221,7 @@ namespace SparkyStudios::Audio::Amplitude return m_parameters[attributeId]; } - void FilterInstance::SetFilterParameter(AmUInt32 attributeId, AmReal32 value) + void FilterInstance::SetParameter(AmUInt32 attributeId, AmReal32 value) { if (attributeId >= m_numParams) return; diff --git a/src/Sound/Filters/BassBoostFilter.cpp b/src/DSP/Filters/BassBoostFilter.cpp similarity index 94% rename from src/Sound/Filters/BassBoostFilter.cpp rename to src/DSP/Filters/BassBoostFilter.cpp index a25cb015..bc71c1f5 100644 --- a/src/Sound/Filters/BassBoostFilter.cpp +++ b/src/DSP/Filters/BassBoostFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -23,7 +23,7 @@ namespace SparkyStudios::Audio::Amplitude , m_boost(2.0f) {} - AmResult BassBoostFilter::Init(AmReal32 aBoost) + AmResult BassBoostFilter::Initialize(AmReal32 aBoost) { if (aBoost < 0) return AM_ERROR_INVALID_PARAMETER; @@ -76,7 +76,7 @@ namespace SparkyStudios::Audio::Amplitude BassBoostFilterInstance::BassBoostFilterInstance(BassBoostFilter* parent) : FFTFilterInstance(parent) { - Init(BassBoostFilter::ATTRIBUTE_LAST); + Initialize(BassBoostFilter::ATTRIBUTE_LAST); m_parameters[BassBoostFilter::ATTRIBUTE_BOOST] = parent->m_boost; } diff --git a/src/Sound/Filters/BassBoostFilter.h b/src/DSP/Filters/BassBoostFilter.h similarity index 87% rename from src/Sound/Filters/BassBoostFilter.h rename to src/DSP/Filters/BassBoostFilter.h index f2bcbeb8..15119120 100644 --- a/src/Sound/Filters/BassBoostFilter.h +++ b/src/DSP/Filters/BassBoostFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_BASS_BOOST_FILTER_H -#define SS_AMPLITUDE_AUDIO_BASS_BOOST_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_BASS_BOOST_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_BASS_BOOST_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,6 +29,7 @@ namespace SparkyStudios::Audio::Amplitude explicit BassBoostFilterInstance(BassBoostFilter* parent); ~BassBoostFilterInstance() override = default; + protected: void ProcessFFTChannel(SplitComplex& fft, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) override; }; @@ -46,7 +47,7 @@ namespace SparkyStudios::Audio::Amplitude BassBoostFilter(); - AmResult Init(AmReal32 boost); + AmResult Initialize(AmReal32 boost); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -67,4 +68,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_BASS_BOOST_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_BASS_BOOST_FILTER_H diff --git a/src/Sound/Filters/BiquadResonantFilter.cpp b/src/DSP/Filters/BiquadResonantFilter.cpp similarity index 86% rename from src/Sound/Filters/BiquadResonantFilter.cpp rename to src/DSP/Filters/BiquadResonantFilter.cpp index f694a472..bd343473 100644 --- a/src/Sound/Filters/BiquadResonantFilter.cpp +++ b/src/DSP/Filters/BiquadResonantFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include namespace SparkyStudios::Audio::Amplitude @@ -27,7 +27,7 @@ namespace SparkyStudios::Audio::Amplitude , _gain(0.0f) {} - AmResult BiquadResonantFilter::Init(TYPE type, AmReal32 frequency, AmReal32 resonance, AmReal32 gain) + AmResult BiquadResonantFilter::Initialize(TYPE type, AmReal32 frequency, AmReal32 resonance, AmReal32 gain) { if (type >= TYPE_LAST || frequency <= 0 || resonance <= 0) return AM_ERROR_INVALID_PARAMETER; @@ -40,39 +40,39 @@ namespace SparkyStudios::Audio::Amplitude return AM_ERROR_NO_ERROR; } - AmResult BiquadResonantFilter::InitLowPass(AmReal32 frequency, AmReal32 q) + AmResult BiquadResonantFilter::InitializeLowPass(AmReal32 frequency, AmReal32 q) { - return Init(TYPE_LOW_PASS, frequency, q, 0.0f); + return Initialize(TYPE_LOW_PASS, frequency, q, 0.0f); } - AmResult BiquadResonantFilter::InitHighPass(AmReal32 frequency, AmReal32 q) + AmResult BiquadResonantFilter::InitializeHighPass(AmReal32 frequency, AmReal32 q) { - return Init(TYPE_HIGH_PASS, frequency, q, 0.0f); + return Initialize(TYPE_HIGH_PASS, frequency, q, 0.0f); } - AmResult BiquadResonantFilter::InitBandPass(AmReal32 frequency, AmReal32 q) + AmResult BiquadResonantFilter::InitializeBandPass(AmReal32 frequency, AmReal32 q) { - return Init(TYPE_BAND_PASS, frequency, q, 0.0f); + return Initialize(TYPE_BAND_PASS, frequency, q, 0.0f); } - AmResult BiquadResonantFilter::InitPeaking(AmReal32 frequency, AmReal32 q, AmReal32 gain) + AmResult BiquadResonantFilter::InitializePeaking(AmReal32 frequency, AmReal32 q, AmReal32 gain) { - return Init(TYPE_PEAK, frequency, q, gain); + return Initialize(TYPE_PEAK, frequency, q, gain); } - AmResult BiquadResonantFilter::InitNotching(AmReal32 frequency, AmReal32 q) + AmResult BiquadResonantFilter::InitializeNotching(AmReal32 frequency, AmReal32 q) { - return Init(TYPE_NOTCH, frequency, q, 0.0); + return Initialize(TYPE_NOTCH, frequency, q, 0.0); } - AmResult BiquadResonantFilter::InitLowShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain) + AmResult BiquadResonantFilter::InitializeLowShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain) { - return Init(TYPE_LOW_SHELF, frequency, s, gain); + return Initialize(TYPE_LOW_SHELF, frequency, s, gain); } - AmResult BiquadResonantFilter::InitHighShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain) + AmResult BiquadResonantFilter::InitializeHighShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain) { - return Init(TYPE_HIGH_SHELF, frequency, s, gain); + return Initialize(TYPE_HIGH_SHELF, frequency, s, gain); } AmUInt32 BiquadResonantFilter::GetParamCount() const @@ -165,7 +165,7 @@ namespace SparkyStudios::Audio::Amplitude i.y2 = 0; } - Init(BiquadResonantFilter::ATTRIBUTE_LAST); + Initialize(BiquadResonantFilter::ATTRIBUTE_LAST); m_parameters[BiquadResonantFilter::ATTRIBUTE_GAIN] = parent->_gain; m_parameters[BiquadResonantFilter::ATTRIBUTE_RESONANCE] = parent->_resonance; @@ -178,7 +178,7 @@ namespace SparkyStudios::Audio::Amplitude } void BiquadResonantFilterInstance::ProcessChannel( - AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) + const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) { if (m_numParamsChanged & (1 << BiquadResonantFilter::ATTRIBUTE_FREQUENCY | 1 << BiquadResonantFilter::ATTRIBUTE_RESONANCE | @@ -191,7 +191,7 @@ namespace SparkyStudios::Audio::Amplitude m_numParamsChanged = 0; - FilterInstance::ProcessChannel(buffer, channel, frames, channels, sampleRate); + FilterInstance::ProcessChannel(in, out, channel, frames, sampleRate); } AmAudioSample BiquadResonantFilterInstance::ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) diff --git a/src/Sound/Filters/BiquadResonantFilter.h b/src/DSP/Filters/BiquadResonantFilter.h similarity index 70% rename from src/Sound/Filters/BiquadResonantFilter.h rename to src/DSP/Filters/BiquadResonantFilter.h index 33db56b2..1022d032 100644 --- a/src/Sound/Filters/BiquadResonantFilter.h +++ b/src/DSP/Filters/BiquadResonantFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_BIQUAD_RESONANT_FILTER_H -#define SS_AMPLITUDE_AUDIO_BIQUAD_RESONANT_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_BIQUAD_RESONANT_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_BIQUAD_RESONANT_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -34,14 +34,15 @@ namespace SparkyStudios::Audio::Amplitude explicit BiquadResonantFilterInstance(BiquadResonantFilter* parent); ~BiquadResonantFilterInstance() override = default; - void ProcessChannel(AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) override; + protected: + void ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) override; AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; private: void ComputeBiquadResonantParams(); - BiquadResonantStateData _state[AM_MAX_CHANNELS]; + BiquadResonantStateData _state[kAmMaxSupportedChannelCount]; AmReal32 _a0, _a1, _a2, _b1, _b2; AmUInt32 _sampleRate; }; @@ -76,21 +77,21 @@ namespace SparkyStudios::Audio::Amplitude BiquadResonantFilter(); ~BiquadResonantFilter() override = default; - AmResult Init(TYPE type, AmReal32 frequency, AmReal32 qOrS, AmReal32 gain); + AmResult Initialize(TYPE type, AmReal32 frequency, AmReal32 qOrS, AmReal32 gain); - AmResult InitLowPass(AmReal32 frequency, AmReal32 q); + AmResult InitializeLowPass(AmReal32 frequency, AmReal32 q); - AmResult InitHighPass(AmReal32 frequency, AmReal32 q); + AmResult InitializeHighPass(AmReal32 frequency, AmReal32 q); - AmResult InitBandPass(AmReal32 frequency, AmReal32 q); + AmResult InitializeBandPass(AmReal32 frequency, AmReal32 q); - AmResult InitPeaking(AmReal32 frequency, AmReal32 q, AmReal32 gain); + AmResult InitializePeaking(AmReal32 frequency, AmReal32 q, AmReal32 gain); - AmResult InitNotching(AmReal32 frequency, AmReal32 q); + AmResult InitializeNotching(AmReal32 frequency, AmReal32 q); - AmResult InitLowShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain); + AmResult InitializeLowShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain); - AmResult InitHighShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain); + AmResult InitializeHighShelf(AmReal32 frequency, AmReal32 s, AmReal32 gain); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -114,4 +115,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_BIQUAD_RESONANT_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_BIQUAD_RESONANT_FILTER_H diff --git a/src/Sound/Filters/DCRemovalFilter.cpp b/src/DSP/Filters/DCRemovalFilter.cpp similarity index 79% rename from src/Sound/Filters/DCRemovalFilter.cpp rename to src/DSP/Filters/DCRemovalFilter.cpp index 7b1c85d0..5f175b46 100644 --- a/src/Sound/Filters/DCRemovalFilter.cpp +++ b/src/DSP/Filters/DCRemovalFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -23,7 +23,7 @@ namespace SparkyStudios::Audio::Amplitude , _length(0.1f) {} - AmResult DCRemovalFilter::Init(AmReal32 length) + AmResult DCRemovalFilter::Initialize(AmReal32 length) { if (length <= 0) return AM_ERROR_INVALID_PARAMETER; @@ -50,7 +50,7 @@ namespace SparkyStudios::Audio::Amplitude , _bufferLength(0) , _offset(0) { - Init(1); + Initialize(1); } DCRemovalFilterInstance::~DCRemovalFilterInstance() @@ -59,23 +59,27 @@ namespace SparkyStudios::Audio::Amplitude ampoolfree(MemoryPoolKind::Filtering, _totals); } - void DCRemovalFilterInstance::Process( - AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) + void DCRemovalFilterInstance::Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) { + const AmUInt16 channels = in.GetChannelCount(); + if (_buffer == nullptr) { - InitBuffer(channels, sampleRate); + InitializeBuffer(channels, sampleRate); } - for (AmInt64 i = 0; i < frames; i++) + for (AmUInt16 c = 0; c < channels; c++) { - for (AmUInt16 c = 0; c < channels; c++) + const auto& inChannel = in[c]; + auto& outChannel = out[c]; + + _offset = 0; + + for (AmInt64 i = 0; i < frames; i++) { - const AmUInt64 o = i * channels + c; - buffer[o] = ProcessSample(buffer[o], c, sampleRate); + outChannel[i] = ProcessSample(inChannel[i], c, sampleRate); + _offset = (_offset + 1) % _bufferLength; } - - _offset = (_offset + 1) % _bufferLength; } } @@ -97,7 +101,7 @@ namespace SparkyStudios::Audio::Amplitude return static_cast(y); } - void DCRemovalFilterInstance::InitBuffer(AmUInt16 channels, AmUInt32 sampleRate) + void DCRemovalFilterInstance::InitializeBuffer(AmUInt16 channels, AmUInt32 sampleRate) { _bufferLength = static_cast(std::ceil(dynamic_cast(m_parent)->_length * sampleRate)); diff --git a/src/Sound/Filters/DCRemovalFilter.h b/src/DSP/Filters/DCRemovalFilter.h similarity index 77% rename from src/Sound/Filters/DCRemovalFilter.h rename to src/DSP/Filters/DCRemovalFilter.h index 831e3a07..70ff8b84 100644 --- a/src/Sound/Filters/DCRemovalFilter.h +++ b/src/DSP/Filters/DCRemovalFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_DCREMOVALFILTER_H -#define SS_AMPLITUDE_AUDIO_DCREMOVALFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_DC_REMOVAL_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_DC_REMOVAL_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,13 +29,13 @@ namespace SparkyStudios::Audio::Amplitude explicit DCRemovalFilterInstance(DCRemovalFilter* parent); ~DCRemovalFilterInstance() override; - void Process( - AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) override; + void Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) override; + protected: AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; private: - void InitBuffer(AmUInt16 channels, AmUInt32 sampleRate); + void InitializeBuffer(AmUInt16 channels, AmUInt32 sampleRate); AmReal32Buffer _buffer; AmReal32Buffer _totals; @@ -56,7 +56,7 @@ namespace SparkyStudios::Audio::Amplitude DCRemovalFilter(); - AmResult Init(AmReal32 length = 0.1f); + AmResult Initialize(AmReal32 length = 0.1f); FilterInstance* CreateInstance() override; @@ -67,4 +67,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_DCREMOVALFILTER_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_DC_REMOVAL_FILTER_H \ No newline at end of file diff --git a/src/Sound/Filters/DelayFilter.cpp b/src/DSP/Filters/DelayFilter.cpp similarity index 83% rename from src/Sound/Filters/DelayFilter.cpp rename to src/DSP/Filters/DelayFilter.cpp index 466c64b4..a7772fbb 100644 --- a/src/Sound/Filters/DelayFilter.cpp +++ b/src/DSP/Filters/DelayFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -25,7 +25,7 @@ namespace SparkyStudios::Audio::Amplitude , _delayStart(0.0f) {} - AmResult DelayFilter::Init(AmReal32 delay, AmReal32 decay, AmReal32 delayStart) + AmResult DelayFilter::Initialize(AmReal32 delay, AmReal32 decay, AmReal32 delayStart) { if (delay <= 0 || decay <= 0) return AM_ERROR_INVALID_PARAMETER; @@ -89,7 +89,7 @@ namespace SparkyStudios::Audio::Amplitude _bufferMaxLength = 0; _offset = 0; - Init(parent->GetParamCount()); + Initialize(parent->GetParamCount()); m_parameters[DelayFilter::ATTRIBUTE_DELAY] = parent->_delay; m_parameters[DelayFilter::ATTRIBUTE_DECAY] = parent->_decay; @@ -104,22 +104,26 @@ namespace SparkyStudios::Audio::Amplitude ampoolfree(MemoryPoolKind::Filtering, _buffer); } - void DelayFilterInstance::Process( - AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) + void DelayFilterInstance::Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) { - InitBuffer(channels, sampleRate); + const AmUInt16 channels = in.GetChannelCount(); - for (AmUInt64 f = 0; f < frames; f++) + InitializeBuffer(channels, sampleRate); + + for (AmUInt16 c = 0; c < channels; c++) { - for (AmUInt16 c = 0; c < channels; c++) + const auto& inChannel = in[c]; + auto& outChannel = out[c]; + + _offset = 0; + + for (AmUInt64 f = 0; f < frames; f++) { - _bufferOffset = _offset * channels + c; + _bufferOffset = c * _bufferLength + _offset; - const AmUInt64 o = f * channels + c; - buffer[o] = ProcessSample(buffer[o], c, sampleRate); + outChannel[f] = ProcessSample(inChannel[f], c, sampleRate); + _offset = (_offset + 1) % _bufferLength; } - - _offset = (_offset + 1) % _bufferLength; } } @@ -150,7 +154,7 @@ namespace SparkyStudios::Audio::Amplitude return static_cast(y); } - void DelayFilterInstance::InitBuffer(AmUInt16 channels, AmUInt32 sampleRate) + void DelayFilterInstance::InitializeBuffer(AmUInt16 channels, AmUInt32 sampleRate) { const auto maxSamples = static_cast(std::ceil(m_parameters[DelayFilter::ATTRIBUTE_DELAY] * static_cast(sampleRate))); diff --git a/src/Sound/Filters/DelayFilter.h b/src/DSP/Filters/DelayFilter.h similarity index 78% rename from src/Sound/Filters/DelayFilter.h rename to src/DSP/Filters/DelayFilter.h index 5b4fc4a3..bf613548 100644 --- a/src/Sound/Filters/DelayFilter.h +++ b/src/DSP/Filters/DelayFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_DELAY_FILTER_H -#define SS_AMPLITUDE_AUDIO_DELAY_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_DELAY_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_DELAY_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,12 +29,13 @@ namespace SparkyStudios::Audio::Amplitude explicit DelayFilterInstance(DelayFilter* parent); ~DelayFilterInstance() override; - void Process(AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) override; + void Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) override; + protected: AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; private: - void InitBuffer(AmUInt16 channels, AmUInt32 sampleRate); + void InitializeBuffer(AmUInt16 channels, AmUInt32 sampleRate); AmReal32Buffer _buffer; AmUInt32 _bufferLength; @@ -60,7 +61,7 @@ namespace SparkyStudios::Audio::Amplitude DelayFilter(); ~DelayFilter() override = default; - AmResult Init(AmReal32 delay, AmReal32 decay = 0.7f, AmReal32 delayStart = 0.0f); + AmResult Initialize(AmReal32 delay, AmReal32 decay = 0.7f, AmReal32 delayStart = 0.0f); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -79,8 +80,8 @@ namespace SparkyStudios::Audio::Amplitude protected: AmReal32 _delay; AmReal32 _decay; - AmReal32 _delayStart; // Set this to false to produce echo + AmReal32 _delayStart; // Set this to 0 to produce echo }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_DELAY_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_DELAY_FILTER_H diff --git a/src/Sound/Filters/EqualizerFilter.cpp b/src/DSP/Filters/EqualizerFilter.cpp similarity index 92% rename from src/Sound/Filters/EqualizerFilter.cpp rename to src/DSP/Filters/EqualizerFilter.cpp index 15277329..d0e943cd 100644 --- a/src/Sound/Filters/EqualizerFilter.cpp +++ b/src/DSP/Filters/EqualizerFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include namespace SparkyStudios::Audio::Amplitude @@ -136,7 +136,7 @@ namespace SparkyStudios::Audio::Amplitude EqualizerFilterInstance::EqualizerFilterInstance(EqualizerFilter* parent) : FFTFilterInstance(parent) { - Init(parent->GetParamCount()); + Initialize(parent->GetParamCount()); m_parameters[EqualizerFilter::ATTRIBUTE_BAND_1] = parent->_volume[EqualizerFilter::ATTRIBUTE_BAND_1 - EqualizerFilter::ATTRIBUTE_BAND_1]; @@ -161,9 +161,9 @@ namespace SparkyStudios::Audio::Amplitude { Comp2MagPhase(fft, frames / 2); - for (AmUInt32 p = 0; p < frames / 2; p++) + for (AmUInt32 p = 0, l = frames / 2; p < l; p++) { - const auto i = static_cast(std::floor(std::sqrt(p / (AmReal32)(frames / 2)) * (frames / 2))); + const auto i = static_cast(std::floor(std::sqrt(p / static_cast(frames / 2)) * (frames / 2))); AmInt32 p2 = (i / (frames / 16)); AmInt32 p1 = p2 - 1; @@ -177,13 +177,15 @@ namespace SparkyStudios::Audio::Amplitude if (p3 > 7) p3 = 7; - const AmReal32 v = static_cast((i % (frames / 16))) / (AmReal32)(frames / 16); + const AmReal32 v = static_cast(i % (frames / 16)) / static_cast(frames / 16); fft.re()[p] *= CatmullRom(v, m_parameters[p0 + 1], m_parameters[p1 + 1], m_parameters[p2 + 1], m_parameters[p3 + 1]); } - std::memset(fft.re() + frames / 2, 0, sizeof(AmReal32) * frames / 2); - std::memset(fft.im() + frames / 2, 0, sizeof(AmReal32) * frames / 2); + const auto halfSize = frames / 2; - MagPhase2Comp(fft, frames / 2); + std::memset(fft.re() + halfSize, 0, sizeof(AmReal32) * halfSize); + std::memset(fft.im() + halfSize, 0, sizeof(AmReal32) * halfSize); + + MagPhase2Comp(fft, halfSize); } } // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Sound/Filters/EqualizerFilter.h b/src/DSP/Filters/EqualizerFilter.h similarity index 91% rename from src/Sound/Filters/EqualizerFilter.h rename to src/DSP/Filters/EqualizerFilter.h index e56fe0bc..6891cd7a 100644 --- a/src/Sound/Filters/EqualizerFilter.h +++ b/src/DSP/Filters/EqualizerFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EQUALIZERFILTER_H -#define SS_AMPLITUDE_AUDIO_EQUALIZERFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_EQUALIZER_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_EQUALIZER_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,6 +29,7 @@ namespace SparkyStudios::Audio::Amplitude explicit EqualizerFilterInstance(EqualizerFilter* parent); ~EqualizerFilterInstance() override = default; + protected: void ProcessFFTChannel(SplitComplex& fft, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) override; }; @@ -82,4 +83,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EQUALIZERFILTER_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_EQUALIZER_FILTER_H \ No newline at end of file diff --git a/src/Sound/Filters/FFTFilter.cpp b/src/DSP/Filters/FFTFilter.cpp similarity index 91% rename from src/Sound/Filters/FFTFilter.cpp rename to src/DSP/Filters/FFTFilter.cpp index b4c1393c..cb873705 100644 --- a/src/Sound/Filters/FFTFilter.cpp +++ b/src/DSP/Filters/FFTFilter.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #define STFT_WINDOW_SIZE 256 // must be power of two #define STFT_WINDOW_HALF 128 @@ -27,9 +27,7 @@ namespace SparkyStudios::Audio::Amplitude AM_API_PRIVATE void hamming(AmReal32Buffer buffer) { for (int i = 0; i < STFT_WINDOW_SIZE; i++) - { buffer[i] = 0.54 - (0.46f * std::cos(2.0 * M_PI * (i / ((STFT_WINDOW_SIZE - 1) * 1.0)))); - } } FFTFilter::FFTFilter(const std::string& name) @@ -49,8 +47,8 @@ namespace SparkyStudios::Audio::Amplitude FFTFilterInstance::FFTFilterInstance(FFTFilter* parent) : FilterInstance(parent) { - InitFFT(); - Init(1); + InitializeFFT(); + Initialize(1); } FFTFilterInstance::~FFTFilterInstance() @@ -58,14 +56,16 @@ namespace SparkyStudios::Audio::Amplitude ampoolfree(MemoryPoolKind::Filtering, _temp); } - void FFTFilterInstance::InitFFT() + void FFTFilterInstance::InitializeFFT() { _temp = static_cast(ampoolmalloc(MemoryPoolKind::Filtering, STFT_WINDOW_SIZE * sizeof(AmReal32))); } - void FFTFilterInstance::ProcessChannel( - AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) + void FFTFilterInstance::ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) { + const auto& inChannel = in[channel]; + auto& outChannel = out[channel]; + AmUInt32 offset = 0; while (offset < frames) @@ -76,9 +76,7 @@ namespace SparkyStudios::Audio::Amplitude for (AmUInt32 i = 0; i < framesToProcess; i++) { - const AmUInt32 o = (offset + i) * channels + channel; - - _temp[i] = buffer[o]; + _temp[i] = inChannel[i + offset]; } if (framesToProcess < STFT_WINDOW_SIZE) @@ -92,21 +90,19 @@ namespace SparkyStudios::Audio::Amplitude fft.Forward(_temp, sc); - ProcessFFTChannel(sc, channel, STFT_WINDOW_HALF, channels, sampleRate); + ProcessFFTChannel(sc, channel, STFT_WINDOW_HALF, in.GetChannelCount(), sampleRate); fft.Backward(_temp, sc); } for (AmUInt32 i = 0; i < framesToProcess; i++) { - const AmUInt32 o = (offset + i) * channels + channel; - - const AmReal32 x = buffer[o]; + const AmReal32 x = inChannel[i + offset]; /* */ AmReal32 y = _temp[i]; y = x + (y - x) * m_parameters[0]; - buffer[o] = static_cast(y); + outChannel[i + offset] = static_cast(y); } offset += framesToProcess; diff --git a/src/Sound/Filters/FFTFilter.h b/src/DSP/Filters/FFTFilter.h similarity index 82% rename from src/Sound/Filters/FFTFilter.h rename to src/DSP/Filters/FFTFilter.h index 3d6e0b62..302deb18 100644 --- a/src/Sound/Filters/FFTFilter.h +++ b/src/DSP/Filters/FFTFilter.h @@ -14,13 +14,12 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_FFT_FILTER_H -#define SS_AMPLITUDE_AUDIO_FFT_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_FFT_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_FFT_FILTER_H +#include #include -#include - namespace SparkyStudios::Audio::Amplitude { class FFTFilterInstance; @@ -43,8 +42,8 @@ namespace SparkyStudios::Audio::Amplitude explicit FFTFilterInstance(FFTFilter* parent); ~FFTFilterInstance() override; - void ProcessChannel(AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) - override; + protected: + void ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) override; virtual void ProcessFFTChannel(SplitComplex& fft, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate); @@ -54,11 +53,11 @@ namespace SparkyStudios::Audio::Amplitude static void MagPhase2Comp(SplitComplex& fft, AmUInt32 samples); - void InitFFT(); + void InitializeFFT(); private: AmReal32Buffer _temp = nullptr; }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_FFT_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_FFT_FILTER_H diff --git a/src/Sound/Filters/FlangerFilter.cpp b/src/DSP/Filters/FlangerFilter.cpp similarity index 85% rename from src/Sound/Filters/FlangerFilter.cpp rename to src/DSP/Filters/FlangerFilter.cpp index 50dde27e..6218c066 100644 --- a/src/Sound/Filters/FlangerFilter.cpp +++ b/src/DSP/Filters/FlangerFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -24,7 +24,7 @@ namespace SparkyStudios::Audio::Amplitude , _frequency(10.0f) {} - AmResult FlangerFilter::Init(AmReal32 delay, AmReal32 frequency) + AmResult FlangerFilter::Initialize(AmReal32 delay, AmReal32 frequency) { if (delay <= 0 || frequency <= 0) return AM_ERROR_INVALID_PARAMETER; @@ -96,40 +96,41 @@ namespace SparkyStudios::Audio::Amplitude _offset = 0; _index = 0; - Init(parent->GetParamCount()); + Initialize(parent->GetParamCount()); m_parameters[FlangerFilter::ATTRIBUTE_DELAY] = parent->_delay; m_parameters[FlangerFilter::ATTRIBUTE_FREQUENCY] = parent->_frequency; } - void FlangerFilterInstance::Process( - AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) + void FlangerFilterInstance::Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) { - InitBuffer(channels, sampleRate); + InitBuffer(in.GetChannelCount(), sampleRate); - FilterInstance::Process(buffer, frames, bufferSize, channels, sampleRate); + FilterInstance::Process(in, out, frames, sampleRate); _offset += frames; _offset %= _bufferLength; } void FlangerFilterInstance::ProcessChannel( - AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) + const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) { + const auto channels = in.GetChannelCount(); const auto maxSamples = static_cast(std::ceil(m_parameters[FlangerFilter::ATTRIBUTE_DELAY] * static_cast(sampleRate))); const AmUInt64 o = channel * _bufferLength; const AmReal64 i = m_parameters[FlangerFilter::ATTRIBUTE_FREQUENCY] * M_PI * 2 / static_cast(sampleRate); + const auto& inChannel = in.GetChannel(channel); + auto& outChannel = out.GetChannel(channel); + for (AmUInt64 f = 0; f < frames; f++) { - const AmUInt64 s = f * channels + channel; - const auto delay = static_cast(std::floor(static_cast(maxSamples) * (1 + std::cos(_index))) / 2); _index += i; - const AmReal32 x = buffer[s]; + const AmReal32 x = inChannel[f]; /* */ AmReal32 y; _buffer[o + _offset % _bufferLength] = x; @@ -139,7 +140,7 @@ namespace SparkyStudios::Audio::Amplitude y = x + (y - x) * m_parameters[FlangerFilter::ATTRIBUTE_WET]; - buffer[s] = static_cast(y); + outChannel[f] = static_cast(y); } _offset -= frames; diff --git a/src/Sound/Filters/FlangerFilter.h b/src/DSP/Filters/FlangerFilter.h similarity index 79% rename from src/Sound/Filters/FlangerFilter.h rename to src/DSP/Filters/FlangerFilter.h index e53077d3..4677a46d 100644 --- a/src/Sound/Filters/FlangerFilter.h +++ b/src/DSP/Filters/FlangerFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_FLANGERFILTER_H -#define SS_AMPLITUDE_AUDIO_FLANGERFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_FLANGER_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_FLANGER_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,9 +29,10 @@ namespace SparkyStudios::Audio::Amplitude explicit FlangerFilterInstance(FlangerFilter* parent); ~FlangerFilterInstance() override; - void Process(AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) override; + void Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) override; - void ProcessChannel(AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) override; + protected: + void ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) override; private: void InitBuffer(AmUInt16 channels, AmUInt32 sampleRate); @@ -59,7 +60,7 @@ namespace SparkyStudios::Audio::Amplitude FlangerFilter(); ~FlangerFilter() override = default; - AmResult Init(AmReal32 delay, AmReal32 frequency); + AmResult Initialize(AmReal32 delay, AmReal32 frequency); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -81,4 +82,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_FLANGERFILTER_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_FLANGER_FILTER_H \ No newline at end of file diff --git a/src/Sound/Filters/FreeverbFilter.cpp b/src/DSP/Filters/FreeverbFilter.cpp similarity index 90% rename from src/Sound/Filters/FreeverbFilter.cpp rename to src/DSP/Filters/FreeverbFilter.cpp index 6aba6ccb..9d039e8d 100644 --- a/src/Sound/Filters/FreeverbFilter.cpp +++ b/src/DSP/Filters/FreeverbFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -96,7 +96,7 @@ namespace SparkyStudios::Audio::Amplitude FreeverbFilterInstance::FreeverbFilterInstance(FreeverbFilter* parent) : FilterInstance(parent) { - Init(parent->GetParamCount()); + Initialize(parent->GetParamCount()); _model = new Freeverb::ReverbModel(); @@ -113,8 +113,7 @@ namespace SparkyStudios::Audio::Amplitude delete _model; } - void FreeverbFilterInstance::Process( - AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) + void FreeverbFilterInstance::Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) { if (m_numParamsChanged > 0) { @@ -127,9 +126,6 @@ namespace SparkyStudios::Audio::Amplitude m_numParamsChanged = 0; } - auto* input = buffer; - auto* output = buffer; - - _model->ProcessReplace(input, input + (channels - 1), output, output + (channels - 1), frames, channels); + _model->ProcessReplace(in[0].begin(), in[1].begin(), out[0].begin(), out[1].begin(), frames, 1); } } // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Sound/Filters/FreeverbFilter.h b/src/DSP/Filters/FreeverbFilter.h similarity index 86% rename from src/Sound/Filters/FreeverbFilter.h rename to src/DSP/Filters/FreeverbFilter.h index d6ca2c81..657b681d 100644 --- a/src/Sound/Filters/FreeverbFilter.h +++ b/src/DSP/Filters/FreeverbFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_FREEVERBFILTER_H -#define SS_AMPLITUDE_AUDIO_FREEVERBFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_FREEVERB_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_FREEVERB_FILTER_H -#include +#include #include @@ -31,7 +31,7 @@ namespace SparkyStudios::Audio::Amplitude explicit FreeverbFilterInstance(FreeverbFilter* parent); ~FreeverbFilterInstance() override; - void Process(AmAudioSampleBuffer buffer, AmUInt64 frames, AmUInt64 bufferSize, AmUInt16 channels, AmUInt32 sampleRate) override; + void Process(const AudioBuffer& in, AudioBuffer& out, AmUInt64 frames, AmUInt32 sampleRate) override; private: Freeverb::ReverbModel* _model; @@ -80,4 +80,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_FREEVERBFILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_FREEVERB_FILTER_H diff --git a/src/Sound/Filters/LofiFilter.cpp b/src/DSP/Filters/LofiFilter.cpp similarity index 91% rename from src/Sound/Filters/LofiFilter.cpp rename to src/DSP/Filters/LofiFilter.cpp index 0a1a43a9..55d2379f 100644 --- a/src/Sound/Filters/LofiFilter.cpp +++ b/src/DSP/Filters/LofiFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include namespace SparkyStudios::Audio::Amplitude @@ -95,7 +95,7 @@ namespace SparkyStudios::Audio::Amplitude LofiFilterInstance::LofiFilterInstance(LofiFilter* parent) : FilterInstance(parent) { - Init(LofiFilter::ATTRIBUTE_LAST); + Initialize(LofiFilter::ATTRIBUTE_LAST); m_parameters[LofiFilter::ATTRIBUTE_SAMPLERATE] = parent->_sampleRate; m_parameters[LofiFilter::ATTRIBUTE_BITDEPTH] = parent->_bitDepth; @@ -112,7 +112,7 @@ namespace SparkyStudios::Audio::Amplitude if (_channelData[channel].m_samplesToSkip <= 0) { _channelData[channel].m_samplesToSkip += (sampleRate / m_parameters[LofiFilter::ATTRIBUTE_SAMPLERATE]) - 1; - AmReal32 q = std::pow(2.0f, m_parameters[LofiFilter::ATTRIBUTE_BITDEPTH]); + const AmReal32 q = std::pow(2.0f, m_parameters[LofiFilter::ATTRIBUTE_BITDEPTH]); _channelData[channel].m_sample = std::floor(q * sample) / q; } else @@ -120,7 +120,7 @@ namespace SparkyStudios::Audio::Amplitude _channelData[channel].m_samplesToSkip--; } - AmReal32 y = sample + (_channelData[channel].m_sample - sample) * m_parameters[LofiFilter::ATTRIBUTE_WET]; + const AmReal32 y = sample + (_channelData[channel].m_sample - sample) * m_parameters[LofiFilter::ATTRIBUTE_WET]; return static_cast(y); } diff --git a/src/Sound/Filters/LofiFilter.h b/src/DSP/Filters/LofiFilter.h similarity index 87% rename from src/Sound/Filters/LofiFilter.h rename to src/DSP/Filters/LofiFilter.h index fe78931d..156d9cec 100644 --- a/src/Sound/Filters/LofiFilter.h +++ b/src/DSP/Filters/LofiFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_LOFIFILTER_H -#define SS_AMPLITUDE_AUDIO_LOFIFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_LOFI_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_LOFI_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -35,10 +35,11 @@ namespace SparkyStudios::Audio::Amplitude explicit LofiFilterInstance(LofiFilter* parent); ~LofiFilterInstance() override = default; + protected: AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; private: - LofiChannelData _channelData[AM_MAX_CHANNELS]{}; + LofiChannelData _channelData[kAmMaxSupportedChannelCount]{}; }; class LofiFilter final : public Filter @@ -79,4 +80,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_LOFIFILTER_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_LOFI_FILTER_H \ No newline at end of file diff --git a/src/Sound/Filters/MonoPoleFilter.cpp b/src/DSP/Filters/MonoPoleFilter.cpp similarity index 95% rename from src/Sound/Filters/MonoPoleFilter.cpp rename to src/DSP/Filters/MonoPoleFilter.cpp index ee694a1d..6727fc0d 100644 --- a/src/Sound/Filters/MonoPoleFilter.cpp +++ b/src/DSP/Filters/MonoPoleFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -22,7 +22,7 @@ namespace SparkyStudios::Audio::Amplitude : FilterInstance(parent) , _previousSample(0.0f) { - Init(MonoPoleFilter::ATTRIBUTE_LAST); + Initialize(MonoPoleFilter::ATTRIBUTE_LAST); } AmAudioSample MonoPoleFilterInstance::ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) @@ -49,7 +49,7 @@ namespace SparkyStudios::Audio::Amplitude , _coefficient(0.0f) {} - AmResult MonoPoleFilter::Init(AmReal32 coefficient) + AmResult MonoPoleFilter::Initialize(AmReal32 coefficient) { if (coefficient < 0.0f || coefficient > 1.0f) return AM_ERROR_INVALID_PARAMETER; diff --git a/src/Sound/Filters/MonoPoleFilter.h b/src/DSP/Filters/MonoPoleFilter.h similarity index 87% rename from src/Sound/Filters/MonoPoleFilter.h rename to src/DSP/Filters/MonoPoleFilter.h index 93572b0d..d3439c96 100644 --- a/src/Sound/Filters/MonoPoleFilter.h +++ b/src/DSP/Filters/MonoPoleFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef _AM_SOUND_FILTERS_MONOPOLE_FILTER_H -#define _AM_SOUND_FILTERS_MONOPOLE_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_MONOPOLE_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_MONOPOLE_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,6 +29,7 @@ namespace SparkyStudios::Audio::Amplitude explicit MonoPoleFilterInstance(MonoPoleFilter* parent); ~MonoPoleFilterInstance() override = default; + protected: AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; private: @@ -50,7 +51,7 @@ namespace SparkyStudios::Audio::Amplitude MonoPoleFilter(); ~MonoPoleFilter() override = default; - AmResult Init(AmReal32 coefficient); + AmResult Initialize(AmReal32 coefficient); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -71,4 +72,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_SOUND_FILTERS_MONOPOLE_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_MONOPOLE_FILTER_H diff --git a/src/Sound/Filters/RobotizeFilter.cpp b/src/DSP/Filters/RobotizeFilter.cpp similarity index 90% rename from src/Sound/Filters/RobotizeFilter.cpp rename to src/DSP/Filters/RobotizeFilter.cpp index 036f4acc..02a01f37 100644 --- a/src/Sound/Filters/RobotizeFilter.cpp +++ b/src/DSP/Filters/RobotizeFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include namespace SparkyStudios::Audio::Amplitude @@ -25,7 +25,7 @@ namespace SparkyStudios::Audio::Amplitude , m_waveform(0) {} - AmResult RobotizeFilter::Init(AmReal32 frequency, AmInt32 waveform) + AmResult RobotizeFilter::Initialize(AmReal32 frequency, AmInt32 waveform) { m_frequency = frequency; m_waveform = waveform; @@ -86,7 +86,7 @@ namespace SparkyStudios::Audio::Amplitude RobotizeFilterInstance::RobotizeFilterInstance(RobotizeFilter* parent) : FilterInstance(parent) { - Init(RobotizeFilter::ATTRIBUTE_LAST); + Initialize(RobotizeFilter::ATTRIBUTE_LAST); _duration = 0.0; m_parameters[RobotizeFilter::ATTRIBUTE_FREQUENCY] = parent->m_frequency; @@ -100,25 +100,26 @@ namespace SparkyStudios::Audio::Amplitude } void RobotizeFilterInstance::ProcessChannel( - AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) + const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) { const auto period = static_cast(static_cast(sampleRate) / m_parameters[RobotizeFilter::ATTRIBUTE_FREQUENCY]); const auto start = static_cast(_duration * sampleRate) % period; + const auto& inChannel = in.GetChannel(channel); + auto& outChannel = out.GetChannel(channel); + for (AmUInt64 f = 0; f < frames; f++) { - const AmUInt64 s = f * channels + channel; - - const AmReal32 x = buffer[s]; + const AmReal32 x = inChannel[f]; /* */ AmReal32 y; - const AmReal32 wPos = static_cast((start + s) % period) / static_cast(period); + const AmReal32 wPos = static_cast((start + f) % period) / static_cast(period); y = x * (GenerateWaveform(static_cast(m_parameters[RobotizeFilter::ATTRIBUTE_WAVEFORM]), wPos) + 0.5f); y = x + (y - x) * m_parameters[RobotizeFilter::ATTRIBUTE_WET]; - buffer[s] = static_cast(y); + outChannel[f] = static_cast(y); } } diff --git a/src/Sound/Filters/RobotizeFilter.h b/src/DSP/Filters/RobotizeFilter.h similarity index 83% rename from src/Sound/Filters/RobotizeFilter.h rename to src/DSP/Filters/RobotizeFilter.h index a0d6da28..02719d2f 100644 --- a/src/Sound/Filters/RobotizeFilter.h +++ b/src/DSP/Filters/RobotizeFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_ROBOTIZE_FILTER_H -#define SS_AMPLITUDE_AUDIO_ROBOTIZE_FILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_ROBOTIZE_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_ROBOTIZE_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -30,7 +30,8 @@ namespace SparkyStudios::Audio::Amplitude void AdvanceFrame(AmTime deltaTime) override; - void ProcessChannel(AmAudioSampleBuffer buffer, AmUInt16 channel, AmUInt64 frames, AmUInt16 channels, AmUInt32 sampleRate) override; + protected: + void ProcessChannel(const AudioBuffer& in, AudioBuffer& out, AmUInt16 channel, AmUInt64 frames, AmUInt32 sampleRate) override; private: static AmReal32 GenerateWaveform(AmInt32 waveform, AmReal32 p); @@ -67,7 +68,7 @@ namespace SparkyStudios::Audio::Amplitude RobotizeFilter(); - AmResult Init(AmReal32 frequency, AmInt32 waveform); + AmResult Initialize(AmReal32 frequency, AmInt32 waveform); [[nodiscard]] AmUInt32 GetParamCount() const override; @@ -89,4 +90,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_ROBOTIZE_FILTER_H +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_ROBOTIZE_FILTER_H diff --git a/src/Sound/Filters/WaveShaperFilter.cpp b/src/DSP/Filters/WaveShaperFilter.cpp similarity index 95% rename from src/Sound/Filters/WaveShaperFilter.cpp rename to src/DSP/Filters/WaveShaperFilter.cpp index 58334771..c508ca1f 100644 --- a/src/Sound/Filters/WaveShaperFilter.cpp +++ b/src/DSP/Filters/WaveShaperFilter.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include namespace SparkyStudios::Audio::Amplitude @@ -43,7 +43,7 @@ namespace SparkyStudios::Audio::Amplitude if (index >= ATTRIBUTE_LAST) return ""; - AmString names[] = { "Wet", "Amount" }; + static constexpr const char* names[] = { "Wet", "Amount" }; return names[index]; } @@ -78,7 +78,7 @@ namespace SparkyStudios::Audio::Amplitude WaveShaperFilterInstance::WaveShaperFilterInstance(WaveShaperFilter* parent) : FilterInstance(parent) { - Init(parent->GetParamCount()); + Initialize(parent->GetParamCount()); m_parameters[WaveShaperFilter::ATTRIBUTE_AMOUNT] = parent->_amount; } diff --git a/src/Sound/Filters/WaveShaperFilter.h b/src/DSP/Filters/WaveShaperFilter.h similarity index 88% rename from src/Sound/Filters/WaveShaperFilter.h rename to src/DSP/Filters/WaveShaperFilter.h index 8fffbf62..2b797f9c 100644 --- a/src/Sound/Filters/WaveShaperFilter.h +++ b/src/DSP/Filters/WaveShaperFilter.h @@ -14,10 +14,10 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_WAVESHARPERFILTER_H -#define SS_AMPLITUDE_AUDIO_WAVESHARPERFILTER_H +#ifndef _AM_IMPLEMENTATION_DSP_FILTERS_WAVE_SHARPER_FILTER_H +#define _AM_IMPLEMENTATION_DSP_FILTERS_WAVE_SHARPER_FILTER_H -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -29,6 +29,7 @@ namespace SparkyStudios::Audio::Amplitude explicit WaveShaperFilterInstance(WaveShaperFilter* parent); ~WaveShaperFilterInstance() override = default; + protected: AmAudioSample ProcessSample(AmAudioSample sample, AmUInt16 channel, AmUInt32 sampleRate) override; }; @@ -68,4 +69,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_WAVESHARPERFILTER_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_DSP_FILTERS_WAVE_SHARPER_FILTER_H \ No newline at end of file diff --git a/src/Mixer/Resampler.cpp b/src/DSP/Resampler.cpp similarity index 98% rename from src/Mixer/Resampler.cpp rename to src/DSP/Resampler.cpp index e03a8e65..2815fbb3 100644 --- a/src/Mixer/Resampler.cpp +++ b/src/DSP/Resampler.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include namespace SparkyStudios::Audio::Amplitude { diff --git a/src/DSP/Resamplers/DefaultResampler.cpp b/src/DSP/Resamplers/DefaultResampler.cpp new file mode 100644 index 00000000..46c7dbf3 --- /dev/null +++ b/src/DSP/Resamplers/DefaultResampler.cpp @@ -0,0 +1,364 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include +#include + +namespace SparkyStudios::Audio::Amplitude +{ + // The value below was chosen empirically as a tradeoff between execution time + // and filter rolloff wrt. cutoff frequency. + constexpr AmUInt64 kTransitionBandwidthRatio = 13; + + bool DefaultResamplerInstance::IsConversionSupported(AmUInt64 source, AmUInt64 destination) + { + AMPLITUDE_ASSERT(source > 0 && destination > 0); + + // Determines whether sample rates are supported based upon whether our + // maximum filter length is big enough to hold the corresponding + // interpolation filter. + const AmInt64 maxRate = std::max(source, destination) / FindGCD(source, destination); + AmUInt64 filterLength = maxRate * kTransitionBandwidthRatio; + filterLength += filterLength % 2; + + return filterLength <= kAmMaxSupportedFrameCount; + } + + DefaultResamplerInstance::DefaultResamplerInstance() + : _upRate(0) + , _downRate(0) + , _timeModuloUpRate(0) + , _lastProcessedSample(0) + , _channelCount(0) + , _coefficientsPerPhase(0) + , _transposedFilterCoefficients(kAmMaxSupportedFrameCount, 1) + , _temporaryFilterCoefficients(kAmMaxSupportedFrameCount, 1) + , _state(kAmMaxSupportedFrameCount, kAmMaxSupportedChannelCount) + { + _state.Clear(); + } + + bool DefaultResamplerInstance::Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) + { + // See "Digital Signal Processing", 4th Edition, Prolakis and Manolakis, + // Pearson, Chapter 11 (specifically Figures 11.5.10 and 11.5.13). + + AMPLITUDE_ASSERT(input.GetChannelCount() == _channelCount); + AMPLITUDE_ASSERT(output.GetChannelCount() == _channelCount); + + // AMPLITUDE_ASSERT(output.GetFrameCount() >= GetExpectedOutputFrames(inputFrames)); + // AMPLITUDE_ASSERT(output.GetFrameCount() <= GetMaxOutputLength(inputFrames)); + + output.Clear(); + + if (IsIdentity()) + { + output = input; + return true; + } + + AmUInt64 inputSample = _lastProcessedSample; + AmUInt64 outputSample = 0; + + const auto& filterCoeffs = _transposedFilterCoefficients[0]; + + while (inputSample < inputFrames && outputSample < outputFrames) + { + AmUInt64 filterIndex = _timeModuloUpRate * _coefficientsPerPhase; + AmUInt64 offsetInputIndex = inputSample - _coefficientsPerPhase + 1; + const AmInt64 offset = -static_cast(offsetInputIndex); + + if (offset > 0) + { + // We will need to draw data from the _state buffer. + const AmInt64 stateFrameCount = static_cast(_coefficientsPerPhase - 1); + AmInt64 stateIndex = stateFrameCount - offset; + + while (stateIndex < stateFrameCount) + { + for (AmUInt64 channel = 0; channel < _channelCount; ++channel) + output[channel][outputSample] += _state[channel][stateIndex] * filterCoeffs[filterIndex]; + + stateIndex++; + filterIndex++; + } + + // Move along by offset samples up as far as input. + offsetInputIndex += offset; + } + + // We now move back to where inputSample "points". + while (offsetInputIndex <= inputSample) + { + for (AmUInt64 channel = 0; channel < _channelCount; ++channel) + output[channel][outputSample] += input[channel][offsetInputIndex] * filterCoeffs[filterIndex]; + + offsetInputIndex++; + filterIndex++; + } + + outputSample++; + + _timeModuloUpRate += _downRate; + // Advance the input pointer. + inputSample += _timeModuloUpRate / _upRate; + // Decide which phase of the polyphase filter to use next. + _timeModuloUpRate %= _upRate; + } + + AMPLITUDE_ASSERT(inputSample >= inputFrames || outputSample >= outputFrames); + _lastProcessedSample = AM_MAX(inputSample, inputFrames) - inputFrames; + + // Take care of the state buffer. + if (const AmInt64 remainingSamples = static_cast(_coefficientsPerPhase) - 1 - static_cast(inputFrames); + remainingSamples > 0) + { + for (AmUInt64 channel = 0; channel < _channelCount; ++channel) + { + // Copy end of the state buffer to the beginning. + auto& stateChannel = _state[channel]; + AMPLITUDE_ASSERT(static_cast(stateChannel.size()) >= remainingSamples); + std::copy_n(stateChannel.end() - remainingSamples, remainingSamples, stateChannel.begin()); + + // Then copy input to the end of the buffer. + std::copy_n(input[channel].begin(), inputFrames, stateChannel.end() - inputFrames); + } + } + else + { + for (AmUInt64 channel = 0; channel < _channelCount; ++channel) + { + AMPLITUDE_ASSERT(_coefficientsPerPhase > 0U); + AMPLITUDE_ASSERT(input[channel].size() > _coefficientsPerPhase - 1); + + // Copy the last of the input samples into the state buffer. + std::copy_n(input[channel].end() - (_coefficientsPerPhase - 1), _coefficientsPerPhase - 1, _state[channel].begin()); + } + } + + inputFrames = inputSample; + outputFrames = outputSample; + + return true; + } + + AmUInt64 DefaultResamplerInstance::GetMaxOutputLength(AmUInt64 inputLength) const + { + if (IsIdentity()) + return inputLength; + + AMPLITUDE_ASSERT(_downRate > 0 && _upRate > 0); + + // The + 1 takes care of the case where: + // (_timeModuloUpRate + _upRate * _lastProcessedSample) < ((inputLength * _upRate) % _downRate) + // The output length will be equal to the return value or the return value -1. + return (inputLength * _upRate) / _downRate + 1; + } + + AmUInt64 DefaultResamplerInstance::GetExpectedOutputFrames(AmUInt64 inputLength) const + { + if (IsIdentity()) + return inputLength; + + const AmUInt64 maxLength = GetMaxOutputLength(inputLength); + if ((_timeModuloUpRate + _upRate * _lastProcessedSample) >= ((inputLength * _upRate) % _downRate)) + return maxLength - 1; + + return maxLength; + } + + AmUInt64 DefaultResamplerInstance::GetRequiredInputFrames(AmUInt64 outputLength) const + { + if (IsIdentity()) + return outputLength; + + return (outputLength * _downRate) / _upRate; + } + + void DefaultResamplerInstance::Initialize(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) + { + // Convert sampling rates to be relatively prime. + AMPLITUDE_ASSERT(sampleRateIn > 0); + AMPLITUDE_ASSERT(sampleRateOut > 0); + AMPLITUDE_ASSERT(channelCount > 0); + + const AmInt64 gcd = FindGCD(sampleRateOut, sampleRateIn); + const AmUInt64 destination = static_cast(sampleRateOut / gcd); + const AmUInt64 source = static_cast(sampleRateIn / gcd); + + // Obtain the size of the _state before _coefficientsPerPhase is updated in GenerateInterpolatingFilter(). + const AmUInt64 oldStateSize = _coefficientsPerPhase > 0 ? _coefficientsPerPhase - 1 : 0; + if ((destination != _upRate) || (source != _downRate)) + { + _upRate = destination; + _downRate = source; + + _sampleRateIn = sampleRateIn; + _sampleRateOut = sampleRateOut; + + if (IsIdentity()) + { + _channelCount = channelCount; + return; + } + + // Create transposed multirate filters from sincs. + GenerateInterpolatingFilter(sampleRateIn); + + // Reset the time variable as it may be longer than the new filter length if + // we switched from upsampling to downsampling. + _timeModuloUpRate = 0; + } + + // Update the state buffer. + if (_channelCount != channelCount) + { + _channelCount = channelCount; + InitializeStateBuffer(oldStateSize); + } + } + + void DefaultResamplerInstance::Reset() + { + _timeModuloUpRate = 0; + _lastProcessedSample = 0; + _state.Clear(); + } + + void DefaultResamplerInstance::SetSampleRate(AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) + { + Initialize(_channelCount, sampleRateIn, sampleRateOut); + } + + void DefaultResamplerInstance::Clear() + { + Reset(); + + _upRate = 0; + _downRate = 0; + _channelCount = 0; + _coefficientsPerPhase = 0; + _transposedFilterCoefficients.Clear(); + _temporaryFilterCoefficients.Clear(); + + _sampleRateIn = 0; + _sampleRateOut = 0; + } + + void DefaultResamplerInstance::InitializeStateBuffer(AmUInt64 oldFrameCount) + { + // Update the state buffer if it is null or if the number of coefficients per phase in the polyphase filter has changed. + if (IsIdentity() || _channelCount == 0) + return; + + // If the state buffer is to be kept. For example in the case of a change + // in either source or destination sampling rate, maintaining the old state + // buffers contents allows a glitch free transition. + const AmUInt64 newFrameCount = _coefficientsPerPhase > 0 ? _coefficientsPerPhase - 1 : 0; + if (oldFrameCount != newFrameCount) + { + const AmUInt64 minSize = std::min(newFrameCount, oldFrameCount); + const AmUInt64 maxSize = std::max(newFrameCount, oldFrameCount); + + for (AmUInt64 channel = 0; channel < _channelCount; ++channel) + { + auto& state_channel = _state[channel]; + AMPLITUDE_ASSERT(state_channel.begin() + maxSize < state_channel.end()); + std::fill(state_channel.begin() + minSize, state_channel.begin() + maxSize, 0.0f); + } + } + } + + void DefaultResamplerInstance::GenerateInterpolatingFilter(AmUInt64 sampleRate) + { + // See "Digital Signal Processing", 4th Edition, Prolakis and Manolakis, + // Pearson, Chapter 11 (specifically Figures 11.5.10 and 11.5.13). + const AmUInt64 maxRate = std::max(_upRate, _downRate); + const AmReal32 cutoffFrequency = static_cast(sampleRate) / static_cast(2 * maxRate); + + AmUInt64 filterLength = maxRate * kTransitionBandwidthRatio; + filterLength += filterLength % 2; + + auto* filterChannel = &_temporaryFilterCoefficients[0]; + filterChannel->clear(); + + GenerateSincFilter(cutoffFrequency, static_cast(sampleRate), filterLength, filterChannel); + + // Pad out the filter length so that it can be arranged in polyphase fashion. + const AmUInt64 transposedLength = filterLength + maxRate - (filterLength % maxRate); + _coefficientsPerPhase = transposedLength / maxRate; + + ArrangeFilterAsPolyphase(filterLength, *filterChannel); + } + + void DefaultResamplerInstance::ArrangeFilterAsPolyphase(AmUInt64 filterLength, const AudioBufferChannel& filter) + { + // Coefficients are transposed and flipped. + // Suppose _upRate is 3, and the input number of coefficients is 10, + // h[0], ..., h[9]. + // Then the _transposedFilterCoefficients buffer will look like this: + // h[9], h[6], h[3], h[0], flipped phase 0 coefs. + // 0, h[7], h[4], h[1], flipped phase 1 coefs (zero-padded). + // 0, h[8], h[5], h[2], flipped phase 2 coefs (zero-padded). + _transposedFilterCoefficients.Clear(); + auto& transposedCoefficientsChannel = _transposedFilterCoefficients[0]; + + for (AmUInt64 i = 0; i < _upRate; ++i) + { + for (AmUInt64 j = 0; j < _coefficientsPerPhase; ++j) + { + if (j * _upRate + i >= filterLength) + continue; + + const AmUInt64 coeffIndex = (_coefficientsPerPhase - 1 - j) + i * _coefficientsPerPhase; + transposedCoefficientsChannel[coeffIndex] = filter[j * _upRate + i]; + } + } + } + + void DefaultResamplerInstance::GenerateSincFilter( + AmReal32 cutoffFrequency, AmUInt64 sampleRate, AmUInt64 filterLength, AudioBufferChannel* filter) + { + AMPLITUDE_ASSERT(sampleRate > 0.0f); + const AmReal32 angularCutoffFrequency = 2.0f * AM_PI32 * cutoffFrequency / sampleRate; + + const size_t half_filter_length = filterLength / 2; + GenerateHannWindow(true, filterLength, filter); + auto* filterChannel = &filter[0]; + + for (size_t i = 0; i < filterLength; ++i) + { + if (i == half_filter_length) + { + (*filterChannel)[half_filter_length] *= angularCutoffFrequency; + } + else + { + const AmReal32 denominator = static_cast(i) - (static_cast(filterLength) / 2.0f); + AMPLITUDE_ASSERT(std::abs(denominator) > kEpsilon); + (*filterChannel)[i] *= std::sin(angularCutoffFrequency * denominator) / denominator; + } + } + + // Normalize. + const AmReal32 normalizingFactor = + static_cast(_upRate) / std::accumulate(filterChannel->begin(), filterChannel->end(), 0.0f); + ScalarMultiply(filterChannel->begin(), filterChannel->begin(), normalizingFactor, filterLength); + } +} // namespace SparkyStudios::Audio::Amplitude diff --git a/src/DSP/Resamplers/DefaultResampler.h b/src/DSP/Resamplers/DefaultResampler.h new file mode 100644 index 00000000..95d836b6 --- /dev/null +++ b/src/DSP/Resamplers/DefaultResampler.h @@ -0,0 +1,226 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifndef _AM_IMPLEMENTATION_DSP_RESAMPLERS_DEFAULT_RESAMPLER_H +#define _AM_IMPLEMENTATION_DSP_RESAMPLERS_DEFAULT_RESAMPLER_H + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + class DefaultResamplerInstance final : public ResamplerInstance + { + friend class AudioConverter; + + public: + /** + * @brief Checks if the given conversion is supported. + * + * @param source The source frequency in Hz. + * @param destination The destination frequency in Hz. + * + * @return @c true if the conversion is supported, @c false otherwise. + */ + static bool IsConversionSupported(AmUInt64 source, AmUInt64 destination); + + /** + * @brief Constructs a new @c Resampler. + */ + DefaultResamplerInstance(); + + /** + * @brief Computes the maximum length of the output buffer from the given + * input length, knowing the source and destination frequencies. The actual + * output length will be either the returned value or one less. + * + * @param inputLength The length of the input buffer. + * + * @return The maximum length of the output buffer. + */ + [[nodiscard]] AmUInt64 GetMaxOutputLength(AmUInt64 inputLength) const; + + /** + * @copydoc ResamplerInstance::Process + */ + bool Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) override; + + /** + * @copydoc ResamplerInstance::GetExpectedOutputFrames + */ + [[nodiscard]] AmUInt64 GetExpectedOutputFrames(AmUInt64 inputLength) const override; + + /** + * @copydoc ResamplerInstance::GetRequiredInputFrames + */ + [[nodiscard]] AmUInt64 GetRequiredInputFrames(AmUInt64 outputLength) const override; + + /** + * @copydoc ResamplerInstance::Initialize + */ + void Initialize(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) override; + + /** + * @copydoc ResamplerInstance::Reset + */ + void Reset() override; + + /** + * @copydoc ResamplerInstance::SetSampleRate + */ + void SetSampleRate(AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) override; + + /** + * @copydoc ResamplerInstance::GetSampleRateIn + */ + [[nodiscard]] AM_INLINE AmUInt32 GetSampleRateIn() const override + { + return _sampleRateIn; + } + + /** + * @copydoc ResamplerInstance::GetSampleRateOut + */ + [[nodiscard]] AM_INLINE AmUInt32 GetSampleRateOut() const override + { + return _sampleRateOut; + } + + /** + * @copydoc ResamplerInstance::GetChannelCount + */ + [[nodiscard]] AM_INLINE AmUInt16 GetChannelCount() const override + { + return _channelCount; + } + + /** + * @copydoc ResamplerInstance::GetInputLatency + */ + [[nodiscard]] AM_INLINE AmUInt64 GetInputLatency() const override + { + return 0; + } + + /** + * @copydoc ResamplerInstance::GetOutputLatency + */ + [[nodiscard]] AM_INLINE AmUInt64 GetOutputLatency() const override + { + return 0; + } + + /** + * @copydoc ResamplerInstance::Clear + */ + void Clear() override; + + private: + /** + * @brief Initializes the @c _state buffer. Called when the resampler is configured + * or when the resampler is reset. + * + * @param oldFrameCount Number of frames in the @c _state buffer prior to the + * most recent call to @c GenerateInterpolatingFilter. + */ + void InitializeStateBuffer(AmUInt64 oldFrameCount); + + /** + * @brief Generates a windowed sinc to act as the interpolating/anti-aliasing filter. + * + * @param sampleRate The target sample rate. + */ + void GenerateInterpolatingFilter(AmUInt64 sampleRate); + + /** + * @brief Arranges the antialiasing filter coefficients in polyphase filter format. + * + * @param filterLength The number of samples in the filter buffer. + * @param filter The buffer containing the filter coefficients. + */ + void ArrangeFilterAsPolyphase(AmUInt64 filterLength, const AudioBufferChannel& filter); + + /** + * @brief Generates Hann windowed sinc function anti aliasing filters. + * + * @param cutoffFrequency Transition band (-3dB) frequency of the filter. + * @param sampleRate The target sample rate. + * @param filterLength The number of samples in the filter buffer. + * @param filter The buffer containing the filter coefficients. + */ + void GenerateSincFilter(AmReal32 cutoffFrequency, AmUInt64 sampleRate, AmUInt64 filterLength, AudioBufferChannel* filter); + + /** + * @brief Checks if the resampler is configured with the same source and destination frequencies. + * + * @return @c true if the resampler is configured with the same source and destination frequencies, @c false otherwise. + */ + [[nodiscard]] AM_INLINE bool IsIdentity() const + { + return _upRate == _downRate; + } + + // Rate of the interpolator section of the rational sampling rate converter. + AmUInt64 _upRate; + + // Rate of the decimator section of the rational sampling rate converter. + AmUInt64 _downRate; + + // Time variable for the polyphase filter. + AmUInt64 _timeModuloUpRate; + + // Marks the last processed sample of the input. + AmUInt64 _lastProcessedSample; + + // Number of channels in the AudioBuffer processed. + AmUInt64 _channelCount; + + // Number of filter coefficients in each phase of the polyphase filter. + AmUInt64 _coefficientsPerPhase; + + // Filter coefficients stored in polyphase form. + AudioBuffer _transposedFilterCoefficients; + + // Filter coefficients in planar form, used for calculating the transposed filter. + AudioBuffer _temporaryFilterCoefficients; + + // Buffer holding the samples of input required between input buffers. + AudioBuffer _state; + + // Source and destination sample rates. + AmUInt32 _sampleRateIn = 0; + AmUInt32 _sampleRateOut = 0; + }; + + class DefaultResampler final : public Resampler + { + public: + DefaultResampler() + : Resampler("default") + {} + + ResamplerInstance* CreateInstance() override + { + return ampoolnew(MemoryPoolKind::Filtering, DefaultResamplerInstance); + } + + void DestroyInstance(ResamplerInstance* instance) override + { + ampooldelete(MemoryPoolKind::Filtering, DefaultResamplerInstance, (DefaultResamplerInstance*)instance); + } + }; +} // namespace SparkyStudios::Audio::Amplitude + +#endif // _AM_IMPLEMENTATION_DSP_RESAMPLERS_DEFAULT_RESAMPLER_H diff --git a/src/Math/FaceBSPTree.h b/src/Math/FaceBSPTree.h index fc465c67..4f3a6ee1 100644 --- a/src/Math/FaceBSPTree.h +++ b/src/Math/FaceBSPTree.h @@ -14,8 +14,8 @@ #pragma once -#ifndef _AM_MATH_FACE_BSP_H -#define _AM_MATH_FACE_BSP_H +#ifndef _AM_IMPLEMENTATION_MATH_FACE_BSP_H +#define _AM_IMPLEMENTATION_MATH_FACE_BSP_H #include @@ -68,4 +68,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_MATH_FACE_BSP_H \ No newline at end of file +#endif // _AM_IMPLEMENTATION_MATH_FACE_BSP_H \ No newline at end of file diff --git a/src/Mixer/Amplimix.cpp b/src/Mixer/Amplimix.cpp index 4d5d574a..30ef5a32 100644 --- a/src/Mixer/Amplimix.cpp +++ b/src/Mixer/Amplimix.cpp @@ -64,161 +64,10 @@ namespace SparkyStudios::Audio::Amplitude bool m_locked = false; }; -#if defined(AM_SIMD_INTRINSICS) - constexpr AmUInt32 kProcessedFramesCount = AmAudioFrame::size; -#else - constexpr AmUInt32 kProcessedFramesCount = 1; -#endif // AM_SIMD_INTRINSICS + constexpr AmUInt32 kProcessedFramesCount = GetSimdBlockSize(); static void OnSoundDestroyed(AmplimixImpl* mixer, AmplimixLayerImpl* layer); - static void* ma_malloc(size_t sz, void*) - { - return ampoolmalloc(MemoryPoolKind::Amplimix, sz); - } - - static void* ma_realloc(void* p, size_t sz, void*) - { - return ampoolrealloc(MemoryPoolKind::Amplimix, p, sz); - } - - static void ma_free(void* p, void*) - { - ampoolfree(MemoryPoolKind::Amplimix, p); - } - - static ma_result ma_resampling_backend_get_heap_size_ls(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) - { - AM_UNUSED(pConfig); - AM_UNUSED(pUserData); - - *pHeapSizeInBytes = 0; - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_init_ls( - void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend) - { - AM_UNUSED(pHeap); - - auto* pResampler = Resampler::Construct("libsamplerate"); - const auto* pMixerLayer = static_cast(pUserData); - - const AmUInt64 maxFramesIn = pMixerLayer->end - pMixerLayer->start; - pResampler->Init(pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut, maxFramesIn); - - *ppBackend = pResampler; - - return MA_SUCCESS; - } - - static void ma_resampling_backend_uninit_ls( - void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) - { - AM_UNUSED(pUserData); - AM_UNUSED(pAllocationCallbacks); - - auto* pResampler = static_cast(pBackend); - pResampler->Clear(); - - Resampler::Destruct("libsamplerate", pResampler); - } - - static ma_result ma_resampling_backend_process_ls( - void* pUserData, - ma_resampling_backend* pBackend, - const void* pFramesIn, - ma_uint64* pFrameCountIn, - void* pFramesOut, - ma_uint64* pFrameCountOut) - { - AM_UNUSED(pUserData); - auto* pResampler = static_cast(pBackend); - - if (pResampler == nullptr) - return MA_INVALID_ARGS; - - if (pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - { - std::memcpy(pFramesOut, pFramesIn, *pFrameCountIn * pResampler->GetChannelCount() * sizeof(AmAudioSample)); - return MA_SUCCESS; - } - - const bool result = pResampler->Process( - static_cast(pFramesIn), *pFrameCountIn, static_cast(pFramesOut), - *pFrameCountOut); - - return result ? MA_SUCCESS : MA_ERROR; - } - - static ma_result ma_resampling_backend_set_rate_ls( - void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) - { - AM_UNUSED(pUserData); - auto* pResampler = static_cast(pBackend); - - if (pResampler->GetSampleRateIn() != sampleRateIn || pResampler->GetSampleRateOut() != sampleRateOut) - pResampler->SetSampleRate(sampleRateIn, sampleRateOut); - - return MA_SUCCESS; - } - - static ma_uint64 ma_resampling_backend_get_input_latency_ls(void* pUserData, const ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - const auto* pResampler = static_cast(pBackend); - - return pResampler->GetInputLatency(); - } - - static ma_uint64 ma_resampling_backend_get_output_latency_ls(void* pUserData, const ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - const auto* pResampler = static_cast(pBackend); - - return pResampler->GetOutputLatency(); - } - - static ma_result ma_resampling_backend_get_required_input_frame_count_ls( - void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) - { - AM_UNUSED(pUserData); - const auto* pResampler = static_cast(pBackend); - - // Sample rate is the same, so ratio is 1:1 - if (pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - *pInputFrameCount = outputFrameCount; - else - *pInputFrameCount = pResampler->GetRequiredInputFrameCount(outputFrameCount); - - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_get_expected_output_frame_count_ls( - void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) - { - AM_UNUSED(pUserData); - const auto* pResampler = static_cast(pBackend); - - // Sample rate is the same, so ratio is 1:1 - if (pResampler->GetSampleRateIn() == pResampler->GetSampleRateOut()) - *pOutputFrameCount = inputFrameCount; - else - *pOutputFrameCount = pResampler->GetExpectedOutputFrameCount(inputFrameCount); - - return MA_SUCCESS; - } - - static ma_result ma_resampling_backend_reset_ls(void* pUserData, ma_resampling_backend* pBackend) - { - AM_UNUSED(pUserData); - auto* pResampler = static_cast(pBackend); - - pResampler->Reset(); - - return MA_SUCCESS; - } - static AmVec2 LRGain(AmReal32 gain, AmReal32 pan) { // Clamp pan to its valid range of -1.0f to 1.0f inclusive @@ -410,36 +259,18 @@ namespace SparkyStudios::Audio::Amplitude AMPLIMIX_STORE(&layer->flag, ePSF_MIN); } - static void MixMono(AmUInt64 index, const AmAudioFrame& gain, const SoundChunk* in, AmAudioFrameBuffer out) + static void MixMono(AmUInt64 index, const AmAudioFrame& gain, const AudioBufferChannel& in, AudioBufferChannel& out) { #if defined(AM_SIMD_INTRINSICS) - out[index] = xsimd::fma(in->buffer[index], gain, out[index]); + const auto x = xsimd::load_aligned(&in[index]); + const auto y = xsimd::load_aligned(&out[index]); + + xsimd::store_aligned(&out[index], xsimd::fma(x, gain, y)); #else - out[index] = in->buffer[index] * gain + out[index]; + out[index] += in[index] * gain; #endif // AM_SIMD_INTRINSICS } - static void MixStereo( - AmUInt64 index, const AmAudioFrame& lGain, const AmAudioFrame& rGain, const SoundChunk* in, AmAudioFrameBuffer out) - { - MixMono(index + 0, lGain, in, out); - MixMono(index + 1, rGain, in, out); - } - - // Setup MiniAudio allocation callbacks for this frame - static ma_allocation_callbacks gAllocationCallbacks = { nullptr, ma_malloc, ma_realloc, ma_free }; - - static ma_resampling_backend_vtable gResamplerVTable = { ma_resampling_backend_get_heap_size_ls, - ma_resampling_backend_init_ls, - ma_resampling_backend_uninit_ls, - ma_resampling_backend_process_ls, - ma_resampling_backend_set_rate_ls, - ma_resampling_backend_get_input_latency_ls, - ma_resampling_backend_get_output_latency_ls, - ma_resampling_backend_get_required_input_frame_count_ls, - ma_resampling_backend_get_expected_output_frame_count_ls, - ma_resampling_backend_reset_ls }; - AmplimixImpl::AmplimixImpl(AmReal32 masterGain) : _initialized(false) , _commandsStack() @@ -451,6 +282,7 @@ namespace SparkyStudios::Audio::Amplitude , _remainingFrames(0) , _pipeline(nullptr) , _device() + , _scratchBuffer(kAmMaxSupportedFrameCount, kAmMaxSupportedChannelCount) { AMPLIMIX_STORE(&_masterGain, masterGain); } @@ -458,6 +290,7 @@ namespace SparkyStudios::Audio::Amplitude AmplimixImpl::~AmplimixImpl() { Deinit(); // Ensures Deinit is called + _scratchBuffer.Clear(); } bool AmplimixImpl::Init(const EngineConfigDefinition* config) @@ -578,34 +411,24 @@ namespace SparkyStudios::Audio::Amplitude _device.mDeviceOutputFormat = deviceOutputFormat; } - AmUInt64 AmplimixImpl::Mix(AmVoidPtr mixBuffer, AmUInt64 frameCount) + AmUInt64 AmplimixImpl::Mix(AudioBuffer** outBuffer, AmUInt64 frameCount) { + if (outBuffer != nullptr) + // Enforce the output buffer to be null before calling Mix + *outBuffer = nullptr; + if (!_initialized || amEngine->GetState() == nullptr || amEngine->GetState()->stopping || amEngine->GetState()->paused) return 0; AmplimixMutexLocker lock(this); - const auto numChannels = static_cast(_device.mRequestedOutputChannels); - - auto buffer = static_cast(mixBuffer); - std::memset(buffer, 0, frameCount * numChannels * sizeof(AmAudioSample)); - - // output remaining frames in buffer before mixing new ones - const AmUInt64 frames = frameCount; - - // dynamically sized buffer - SoundChunk* align = SoundChunk::CreateChunk(frames, numChannels, MemoryPoolKind::Amplimix); - -#if defined(AM_SIMD_INTRINSICS) - // aSize in Vc::int16_v and multiple of kProcessedFramesCountHalf - const AmUInt64 aSize = frames / align->samplesPerVector; + // clear the output buffer + _scratchBuffer.Clear(); // determine remaining number of frames - _remainingFrames = aSize * align->samplesPerVector - frames; +#if defined(AM_SIMD_INTRINSICS) + _remainingFrames = AM_VALUE_ALIGN(frameCount, GetSimdBlockSize()) - frameCount; #else - // aSize in AmInt16 - const AmUInt64 aSize = frames * numChannels; - _remainingFrames = 0; // Should not have remaining frames without SIMD optimization #endif // AM_SIMD_INTRINSICS @@ -613,37 +436,38 @@ namespace SparkyStudios::Audio::Amplitude bool hasMixedAtLeastOneLayer = false; for (auto&& layer : _layers) { - if (ShouldMix(&layer)) - { - UpdatePitch(&layer); + if (!ShouldMix(&layer)) + continue; - hasMixedAtLeastOneLayer = true; - MixLayer(&layer, align->buffer, aSize, frames); + UpdatePitch(&layer); - // If we have mixed more frames than required, move back the cursor - if (_remainingFrames) - { - AmUInt64 cursor = AMPLIMIX_LOAD(&layer.cursor); - cursor -= _remainingFrames; - AMPLIMIX_STORE(&layer.cursor, cursor); - } + hasMixedAtLeastOneLayer = true; + MixLayer(&layer, &_scratchBuffer, frameCount); + +#if defined(AM_SIMD_ALIGNMENT) + // If we have mixed more frames than required, move back the cursor + if (_remainingFrames > 0) + { + AmUInt64 cursor = AMPLIMIX_LOAD(&layer.cursor); + cursor -= _remainingFrames; + AMPLIMIX_STORE(&layer.cursor, cursor); } +#endif // AM_SIMD_ALIGNMENT } - if (!hasMixedAtLeastOneLayer) - goto Cleanup; - - // copy frames, leaving possible remainder - std::memcpy(buffer, reinterpret_cast(align->buffer), frames * numChannels * sizeof(AmAudioSample)); - - Cleanup: - SoundChunk::DestroyChunk(align); - lock.Unlock(); ExecuteCommands(); - return frameCount; + if (hasMixedAtLeastOneLayer) + { + if (outBuffer != nullptr) + *outBuffer = &_scratchBuffer; + + return frameCount; + } + + return 0; } AmUInt32 AmplimixImpl::Play( @@ -706,8 +530,8 @@ namespace SparkyStudios::Audio::Amplitude // atomically set cursor to start position based on given argument AMPLIMIX_STORE(&lay->cursor, lay->start); - // Initialize the resampler - ma_data_converter_uninit(&lay->dataConverter, &gAllocationCallbacks); + // Initialize the converter + lay->dataConverter = ampoolnew(MemoryPoolKind::Amplimix, AudioConverter); const auto soundChannels = static_cast(sound->format.GetNumChannels()); const auto reqChannels = static_cast(_device.mRequestedOutputChannels); @@ -715,38 +539,15 @@ namespace SparkyStudios::Audio::Amplitude const AmUInt32 soundSampleRate = sound->format.GetSampleRate(); const AmUInt32 reqSampleRate = _device.mRequestedOutputSampleRate; - ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - - converterConfig.formatIn = ma_format_f32; - converterConfig.formatOut = ma_format_from_amplitude(_device.mDeviceOutputFormat); + AudioConverter::Settings converterSettings{}; + converterSettings.m_sourceChannelCount = soundChannels; + converterSettings.m_targetChannelCount = reqChannels; + converterSettings.m_sourceSampleRate = soundSampleRate; + converterSettings.m_targetSampleRate = reqSampleRate; - converterConfig.channelsIn = soundChannels; - converterConfig.channelsOut = reqChannels; - converterConfig.channelMixMode = ma_channel_mix_mode_rectangular; - - converterConfig.sampleRateIn = soundSampleRate; - converterConfig.sampleRateOut = reqSampleRate; - - converterConfig.resampling.algorithm = ma_resample_algorithm_custom; - converterConfig.resampling.pBackendUserData = lay; - converterConfig.resampling.pBackendVTable = &gResamplerVTable; - - converterConfig.allowDynamicSampleRate = MA_TRUE; - converterConfig.calculateLFEFromSpatialChannels = MA_TRUE; - converterConfig.ditherMode = ma_dither_mode_rectangle; - - ma_channel_map_init_standard( - ma_standard_channel_map_default, converterConfig.pChannelMapIn, soundChannels, converterConfig.channelsIn); - - ma_channel_map_init_standard( - ma_standard_channel_map_default, converterConfig.pChannelMapOut, reqChannels, converterConfig.channelsOut); - - if (ma_data_converter_init(&converterConfig, &gAllocationCallbacks, &lay->dataConverter) != MA_SUCCESS) + if (!lay->dataConverter->Configure(converterSettings)) { - ma_data_converter_uninit(&lay->dataConverter, &gAllocationCallbacks); - amLogError("Cannot process frames. Unable to initialize the samples data converter."); - return 0; } @@ -1011,7 +812,7 @@ namespace SparkyStudios::Audio::Amplitude } } - void AmplimixImpl::MixLayer(AmplimixLayerImpl* layer, AmAudioFrameBuffer buffer, AmUInt64 bufferSize, AmUInt64 samples) + void AmplimixImpl::MixLayer(AmplimixLayerImpl* layer, AudioBuffer* buffer, AmUInt64 frameCount) { if (layer->snd == nullptr) { @@ -1037,29 +838,33 @@ namespace SparkyStudios::Audio::Amplitude // atomically load left and right gain const AmVec2 g = AMPLIMIX_LOAD(&layer->gain); const AmReal32 gain = AMPLIMIX_LOAD(&_masterGain); + + AmReal32 l = g.X, r = g.Y; + if (_device.mRequestedOutputChannels == PlaybackOutputChannels::Mono) + l = r = (l + r) * AM_InvSqrtF(2); + #if defined(AM_SIMD_INTRINSICS) - const auto mxGain = xsimd::zip_hi(xsimd::batch(g.X * gain), xsimd::batch(g.Y * gain)); - const auto& lGain = mxGain; - const auto& rGain = mxGain; + auto lGain = xsimd::batch(l * gain); + auto rGain = xsimd::batch(r * gain); #else - const auto lGain = g.X * gain; - const auto rGain = g.Y * gain; + const auto lGain = l * gain; + const auto rGain = r * gain; #endif // AM_SIMD_INTRINSICS // loop state - const bool loop = (flag == ePSF_LOOP); + const bool loop = flag == ePSF_LOOP; const AmUInt16 soundChannels = layer->snd->format.GetNumChannels(); const AmReal32 sampleRateRatio = AMPLIMIX_LOAD(&layer->sampleRateRatio); - AmUInt64 outSamples = samples; - AmUInt64 inSamples = samples; + AmUInt64 outSamples = frameCount; + AmUInt64 inSamples = frameCount; if (sampleRateRatio != 1.0f) - ma_data_converter_get_required_input_frame_count(&layer->dataConverter, outSamples, &inSamples); + inSamples = layer->dataConverter->GetRequiredInputFrameCount(outSamples) - layer->dataConverter->GetInputLatency(); #if defined(AM_SIMD_INTRINSICS) - inSamples = AM_VALUE_ALIGN(inSamples - ma_data_converter_get_input_latency(&layer->dataConverter), kProcessedFramesCount); + inSamples = AM_VALUE_ALIGN(inSamples, kProcessedFramesCount); #endif // AM_SIMD_INTRINSICS SoundChunk* in = SoundChunk::CreateChunk(inSamples, soundChannels, MemoryPoolKind::Amplimix); @@ -1093,9 +898,7 @@ namespace SparkyStudios::Audio::Amplitude if (readLen == 0) break; - std::memcpy( - reinterpret_cast(in->buffer) + ((inSamples - c) * soundChannels), layer->snd->chunk->buffer, - readLen * layer->snd->format.GetFrameSize()); + AudioBuffer::Copy(*layer->snd->chunk->buffer, 0, *in->buffer, inSamples - c, readLen); c -= readLen; } @@ -1103,45 +906,29 @@ namespace SparkyStudios::Audio::Amplitude else { // Compute offset - const AmUInt64 offset = (cursor % layer->snd->length) * soundChannels; + const AmUInt64 offset = cursor % layer->snd->length; const AmUInt64 remaining = layer->snd->chunk->frames - cursor; if (cursor < layer->snd->chunk->frames && remaining < inSamples) { - const AmUInt64 size = remaining * layer->snd->format.GetFrameSize(); - - std::memcpy(in->buffer, reinterpret_cast(layer->snd->chunk->buffer) + offset, size); - - std::memcpy( - reinterpret_cast(in->buffer) + (remaining * soundChannels), layer->snd->chunk->buffer, - in->size - size); + AudioBuffer::Copy(*layer->snd->chunk->buffer, offset, *in->buffer, 0, remaining); + AudioBuffer::Copy(*layer->snd->chunk->buffer, 0, *in->buffer, remaining, in->frames - remaining); } else { - std::memcpy(in->buffer, reinterpret_cast(layer->snd->chunk->buffer) + offset, in->size); + AudioBuffer::Copy(*layer->snd->chunk->buffer, offset, *in->buffer, 0, in->frames); } } - if (ma_data_converter_process_pcm_frames(&layer->dataConverter, in->buffer, &inSamples, out->buffer, &outSamples) != MA_SUCCESS) - { - SoundChunk::DestroyChunk(out); - SoundChunk::DestroyChunk(in); - - amLogError("Cannot process frames. Unable to convert the audio input."); - - return; - } + layer->dataConverter->Process(*in->buffer, inSamples, *out->buffer, outSamples); if (outSamples > 0 && flag >= ePSF_PLAY) { // Cache cursor AmUInt64 oldCursor = cursor; - const auto sampleRate = static_cast(std::ceil(layer->snd->format.GetSampleRate() / sampleRateRatio)); - - _pipeline->Process( - reinterpret_cast(out->buffer), reinterpret_cast(out->buffer), out->frames, - out->size, reqChannels, sampleRate, layer); + // Execute Pipeline + _pipeline->Process(layer, *out->buffer, *out->buffer); /* */ AmReal32 position = cursor; const AmUInt64 start = layer->start; @@ -1150,7 +937,7 @@ namespace SparkyStudios::Audio::Amplitude const auto step = static_cast(inSamples) / static_cast(outSamples); // regular playback - for (AmUInt64 i = 0; i < bufferSize; i += reqChannels) + for (AmUInt64 i = 0; i < outSamples; i += kProcessedFramesCount) { position = AM_CLAMP(position, start, end); @@ -1169,7 +956,8 @@ namespace SparkyStudios::Audio::Amplitude } else { - ma_data_converter_reset(&layer->dataConverter); + // reset data converter + layer->dataConverter->Reset(); // stop playback break; @@ -1180,11 +968,12 @@ namespace SparkyStudios::Audio::Amplitude { case PlaybackOutputChannels::Mono: // lGain is always equal to rGain on mono - MixMono(i, lGain, out, buffer); + MixMono(i, lGain, out->buffer->GetChannel(0), buffer->GetChannel(0)); break; case PlaybackOutputChannels::Stereo: - MixStereo(i, lGain, rGain, out, buffer); + MixMono(i, lGain, out->buffer->GetChannel(0), buffer->GetChannel(0)); + MixMono(i, rGain, out->buffer->GetChannel(1), buffer->GetChannel(1)); break; default: @@ -1198,7 +987,7 @@ namespace SparkyStudios::Audio::Amplitude cursor += inSamples; #if defined(AM_SIMD_INTRINSICS) - cursor = AM_VALUE_ALIGN(cursor, kProcessedFramesCount); + // cursor = AM_VALUE_ALIGN(cursor, kProcessedFramesCount); #endif // AM_SIMD_INTRINSICS cursor = AM_CLAMP(cursor, layer->start, layer->end); @@ -1268,19 +1057,27 @@ namespace SparkyStudios::Audio::Amplitude const AmReal32 pitch = AMPLIMIX_LOAD(&layer->pitch); const AmReal32 speed = AMPLIMIX_LOAD(&layer->userPlaySpeed); - const AmReal32 oldSpeed = AMPLIMIX_LOAD(&layer->playSpeed); + /* */ AmReal32 currentSpeed = AMPLIMIX_LOAD(&layer->playSpeed); const AmReal32 playSpeed = AM_MAX(pitch * speed, 0.001f); - if (playSpeed != oldSpeed) + if (currentSpeed != playSpeed) { + currentSpeed = AM_Lerp(currentSpeed, 0.75f, playSpeed); + const AmReal32 basePitch = static_cast(layer->snd->format.GetSampleRate()) / static_cast(_device.mRequestedOutputSampleRate); - const AmReal32 sampleRateRatio = basePitch * playSpeed; + const AmReal32 sampleRateRatio = basePitch * currentSpeed; - AMPLIMIX_STORE(&layer->playSpeed, playSpeed); + AMPLIMIX_STORE(&layer->targetPlaySpeed, playSpeed); AMPLIMIX_STORE(&layer->sampleRateRatio, sampleRateRatio); - ma_data_converter_set_rate_ratio(&layer->dataConverter, sampleRateRatio); + const AmUInt64 t = 1000; + const AmUInt64 s = (AmUInt64)(sampleRateRatio * t); + + AMPLITUDE_ASSERT(s != 0); + layer->dataConverter->SetSampleRate(s, t); + + AMPLIMIX_STORE(&layer->playSpeed, currentSpeed); } } @@ -1304,7 +1101,7 @@ namespace SparkyStudios::Audio::Amplitude void AmplimixLayerImpl::Reset() { - ma_data_converter_uninit(&dataConverter, &gAllocationCallbacks); + ampooldelete(MemoryPoolKind::Amplimix, AudioConverter, dataConverter); } AmUInt32 AmplimixLayerImpl::GetId() const diff --git a/src/Mixer/Amplimix.h b/src/Mixer/Amplimix.h index 703420d3..bc48fb73 100644 --- a/src/Mixer/Amplimix.h +++ b/src/Mixer/Amplimix.h @@ -14,18 +14,18 @@ #pragma once -#ifndef _AM_MIXER_MIXER_H -#define _AM_MIXER_MIXER_H +#ifndef _AM_IMPLEMENTATION_MIXER_AMPLIMIX_H +#define _AM_IMPLEMENTATION_MIXER_AMPLIMIX_H #include #include #include #include +#include #include -#include +#include -#include #include #include @@ -73,10 +73,11 @@ namespace SparkyStudios::Audio::Amplitude _Atomic(AmReal32) occlusion; // occlusion factor _Atomic(AmReal32) userPlaySpeed; // user-defined sound playback speed - _Atomic(AmReal32) playSpeed; // computed (real) sound playback speed + _Atomic(AmReal32) playSpeed; // current sound playback speed + _Atomic(AmReal32) targetPlaySpeed; // computed (real) sound playback speed _Atomic(AmReal32) sampleRateRatio; // sample rate ratio - ma_data_converter dataConverter; // miniaudio resampler & channel converter + AudioConverter* dataConverter; // miniaudio resampler & channel converter /** * @brief Resets the layer. @@ -149,7 +150,7 @@ namespace SparkyStudios::Audio::Amplitude return _initialized; } - AmUInt64 Mix(AmVoidPtr mixBuffer, AmUInt64 frameCount) override; + AmUInt64 Mix(AudioBuffer** outBuffer, AmUInt64 frameCount) override; AmUInt32 Play( SoundData* sound, PlayStateFlag flag, AmReal32 gain, AmReal32 pan, AmReal32 pitch, AmReal32 speed, AmUInt32 id, AmUInt32 layer); @@ -194,9 +195,9 @@ namespace SparkyStudios::Audio::Amplitude void PushCommand(const MixerCommand& command); - [[nodiscard]] const ProcessorPipeline* GetPipeline() const; + [[nodiscard]] const Pipeline* GetPipeline() const; - [[nodiscard]] ProcessorPipeline* GetPipeline(); + [[nodiscard]] Pipeline* GetPipeline(); [[nodiscard]] AM_INLINE const DeviceDescription& GetDeviceDescription() const override { @@ -207,7 +208,7 @@ namespace SparkyStudios::Audio::Amplitude private: void ExecuteCommands(); - void MixLayer(AmplimixLayerImpl* layer, AmAudioFrameBuffer buffer, AmUInt64 bufferSize, AmUInt64 samples); + void MixLayer(AmplimixLayerImpl* layer, AudioBuffer* buffer, AmUInt64 frameCount); AmplimixLayerImpl* GetLayer(AmUInt32 layer); bool ShouldMix(AmplimixLayerImpl* layer); void UpdatePitch(AmplimixLayerImpl* layer); @@ -226,10 +227,12 @@ namespace SparkyStudios::Audio::Amplitude AmplimixLayerImpl _layers[kAmplimixLayersCount]; AmUInt64 _remainingFrames; - AmUniquePtr _pipeline = nullptr; + AmUniquePtr _pipeline = nullptr; DeviceDescription _device; + + AudioBuffer _scratchBuffer; }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_MIXER_MIXER_H +#endif // _AM_IMPLEMENTATION_MIXER_AMPLIMIX_H diff --git a/src/Mixer/ProcessorPipeline.cpp b/src/Mixer/Pipeline.cpp similarity index 53% rename from src/Mixer/ProcessorPipeline.cpp rename to src/Mixer/Pipeline.cpp index 2c7e2725..fa7dadd3 100644 --- a/src/Mixer/ProcessorPipeline.cpp +++ b/src/Mixer/Pipeline.cpp @@ -13,17 +13,15 @@ // limitations under the License. #include -#include - -#include +#include namespace SparkyStudios::Audio::Amplitude { - ProcessorPipeline::ProcessorPipeline() + Pipeline::Pipeline() : _processors() {} - ProcessorPipeline::~ProcessorPipeline() + Pipeline::~Pipeline() { for (const auto& processor : _processors) ampooldelete(MemoryPoolKind::Amplimix, SoundProcessorInstance, processor); @@ -31,53 +29,36 @@ namespace SparkyStudios::Audio::Amplitude _processors.clear(); } - void ProcessorPipeline::Append(SoundProcessorInstance* processor) + void Pipeline::Append(SoundProcessorInstance* processor) { _processors.push_back(processor); } - void ProcessorPipeline::Insert(SoundProcessorInstance* processor, AmSize index) + void Pipeline::Insert(SoundProcessorInstance* processor, AmSize index) { if (index >= _processors.size()) - { _processors.push_back(processor); - } else - { _processors.insert(_processors.begin() + index, processor); - } } - void ProcessorPipeline::Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) + void Pipeline::Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) { - AmConstAudioSampleBuffer cIn = in; + AMPLITUDE_ASSERT(in.GetChannelCount() == out.GetChannelCount()); + AMPLITUDE_ASSERT(in.GetFrameCount() == out.GetFrameCount()); + + const AudioBuffer* cIn = ∈ for (auto&& p : _processors) { - p->Process(out, cIn, frames, bufferSize, channels, sampleRate, layer); - cIn = out; + p->Process(layer, *cIn, out); + cIn = &out; } } - void ProcessorPipeline::Cleanup(const AmplimixLayer* layer) + void Pipeline::Cleanup(const AmplimixLayer* layer) { for (auto&& p : _processors) p->Cleanup(layer); } - - AmSize ProcessorPipeline::GetOutputBufferSize(AmUInt64 frames, AmSize bufferSize, AmUInt16 channels, AmUInt32 sampleRate) - { - AmSize outputSize = 0; - for (auto&& p : _processors) - outputSize = AM_MAX(p->GetOutputBufferSize(frames, bufferSize, channels, sampleRate), outputSize); - - return outputSize; - } } // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Mixer/SoundProcessors/ClipProcessor.h b/src/Mixer/Pipeline/ClipProcessor.h similarity index 61% rename from src/Mixer/SoundProcessors/ClipProcessor.h rename to src/Mixer/Pipeline/ClipProcessor.h index 762d2f65..16388ff8 100644 --- a/src/Mixer/SoundProcessors/ClipProcessor.h +++ b/src/Mixer/Pipeline/ClipProcessor.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_CLIP_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_CLIP_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_CLIP_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_CLIP_PROCESSOR_H #include @@ -26,19 +26,21 @@ namespace SparkyStudios::Audio::Amplitude class ClipProcessorInstance : public SoundProcessorInstance { public: - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { - const AmSize length = frames * channels; + const AmSize length = in.GetFrameCount(); + const AmSize channels = in.GetChannelCount(); - for (AmSize i = 0; i < length; i++) - out[i] = in[i] <= -1.65f ? -0.9862875f : in[i] >= 1.65f ? 0.9862875f : 0.87f * in[i] - 0.1f * in[i] * in[i] * in[i]; + for (AmSize c = 0; c < channels; c++) + { + const auto& inChannel = in[c]; + auto& outChannel = out[c]; + + for (AmSize i = 0; i < length; i++) + outChannel[i] = inChannel[i] <= -1.65f ? -0.9862875f + : inChannel[i] >= 1.65f ? 0.9862875f + : 0.87f * inChannel[i] - 0.1f * inChannel[i] * inChannel[i] * inChannel[i]; + } } }; @@ -61,4 +63,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_CLIP_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_CLIP_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/EffectProcessor.h b/src/Mixer/Pipeline/EffectProcessor.h similarity index 72% rename from src/Mixer/SoundProcessors/EffectProcessor.h rename to src/Mixer/Pipeline/EffectProcessor.h index dbd85d88..aaedeafa 100644 --- a/src/Mixer/SoundProcessors/EffectProcessor.h +++ b/src/Mixer/Pipeline/EffectProcessor.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EFFECT_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_EFFECT_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_EFFECT_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_EFFECT_PROCESSOR_H #include @@ -24,24 +24,17 @@ namespace SparkyStudios::Audio::Amplitude class EffectProcessorInstance final : public SoundProcessorInstance { public: - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { const EffectInstance* effect = layer->GetEffect(); - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, in.GetFrameCount()); if (effect == nullptr) return; - effect->GetFilter()->Process(out, frames, bufferSize, channels, sampleRate); + effect->GetFilter()->Process(in, out, in.GetFrameCount(), layer->GetSoundFormat().GetSampleRate()); } }; @@ -64,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EFFECT_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_EFFECT_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/EnvironmentProcessor.h b/src/Mixer/Pipeline/EnvironmentProcessor.h similarity index 81% rename from src/Mixer/SoundProcessors/EnvironmentProcessor.h rename to src/Mixer/Pipeline/EnvironmentProcessor.h index 6e37a706..1235cfc0 100644 --- a/src/Mixer/SoundProcessors/EnvironmentProcessor.h +++ b/src/Mixer/Pipeline/EnvironmentProcessor.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_ENVIRONMENT_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_ENVIRONMENT_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_ENVIRONMENT_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_ENVIRONMENT_PROCESSOR_H #include @@ -29,14 +29,7 @@ namespace SparkyStudios::Audio::Amplitude class EnvironmentProcessorInstance final : public SoundProcessorInstance { public: - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { if (const Entity& entity = layer->GetEntity(); entity.Valid()) { @@ -70,23 +63,22 @@ namespace SparkyStudios::Audio::Amplitude _environmentFilters[environment][layer->GetId()] = effect->CreateInstance(); } - SoundChunk* scratch = SoundChunk::CreateChunk(frames, channels, MemoryPoolKind::Amplimix); - std::memcpy(reinterpret_cast(scratch->buffer), in, bufferSize); + SoundChunk* scratch = SoundChunk::CreateChunk(in.GetFrameCount(), in.GetChannelCount(), MemoryPoolKind::Amplimix); + AudioBuffer::Copy(in, 0, *scratch->buffer, 0, in.GetFrameCount()); FilterInstance* filterInstance = _environmentFilters[environment][layer->GetId()]->GetFilter(); - filterInstance->SetFilterParameter(0, amount); - filterInstance->Process( - reinterpret_cast(scratch->buffer), frames, bufferSize, channels, sampleRate); + filterInstance->SetParameter(0, amount); + filterInstance->Process(*scratch->buffer, out, in.GetFrameCount(), layer->GetSoundFormat().GetSampleRate()); - std::memcpy(out, reinterpret_cast(scratch->buffer), bufferSize); + AudioBuffer::Copy(*scratch->buffer, 0, out, 0, out.GetFrameCount()); SoundChunk::DestroyChunk(scratch); return; } } - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, out.GetFrameCount()); } void Cleanup(const AmplimixLayer* layer) override @@ -137,4 +129,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_ENVIRONMENT_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_ENVIRONMENT_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/ObstructionProcessor.h b/src/Mixer/Pipeline/ObstructionProcessor.h similarity index 65% rename from src/Mixer/SoundProcessors/ObstructionProcessor.h rename to src/Mixer/Pipeline/ObstructionProcessor.h index 0a0c8396..2868e6b0 100644 --- a/src/Mixer/SoundProcessors/ObstructionProcessor.h +++ b/src/Mixer/Pipeline/ObstructionProcessor.h @@ -14,13 +14,13 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_OBSTRUCTION_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_OBSTRUCTION_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_OBSTRUCTION_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_OBSTRUCTION_PROCESSOR_H #include #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -41,19 +41,16 @@ namespace SparkyStudios::Audio::Amplitude _filter.DestroyInstance(filter); } - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { const float obstruction = layer->GetObstruction(); - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, out.GetFrameCount()); + + const auto frames = out.GetFrameCount(); + const auto channels = out.GetChannelCount(); + const auto sampleRate = layer->GetSoundFormat().GetSampleRate(); // Nothing to do if no obstruction if (obstruction < kEpsilon) @@ -62,8 +59,8 @@ namespace SparkyStudios::Audio::Amplitude _lpfCurve.SetStart({ 0, sampleRate / 2.0f }); _lpfCurve.SetEnd({ 1, sampleRate / 2000.0f }); - const auto& lpfCurve = amEngine->GetObstructionLowPassCurve(); - const auto& gainCurve = amEngine->GetObstructionGainCurve(); + const auto& lpfCurve = Engine::GetInstance()->GetObstructionLowPassCurve(); + const auto& gainCurve = Engine::GetInstance()->GetObstructionGainCurve(); if (const AmReal32 lpf = lpfCurve.Get(obstruction); lpf > 0) { @@ -71,42 +68,25 @@ namespace SparkyStudios::Audio::Amplitude if (!_obstructionFilters.contains(id)) { - _filter.InitLowPass(std::ceil(_lpfCurve.Get(lpf)), 0.5f); + _filter.InitializeLowPass(std::ceil(_lpfCurve.Get(lpf)), 0.5f); _obstructionFilters[id] = _filter.CreateInstance(); } // Update the filter coefficients - _obstructionFilters[id]->SetFilterParameter(BiquadResonantFilter::ATTRIBUTE_FREQUENCY, _lpfCurve.Get(lpf)); + _obstructionFilters[id]->SetParameter(BiquadResonantFilter::ATTRIBUTE_FREQUENCY, _lpfCurve.Get(lpf)); // Apply Low Pass Filter - _obstructionFilters[id]->Process(out, frames, bufferSize, channels, sampleRate); + _obstructionFilters[id]->Process(in, out, frames, sampleRate); } const AmReal32 gain = gainCurve.Get(obstruction); // Apply Gain - const AmSize length = frames * channels; - -#if defined(AM_SIMD_INTRINSICS) - const AmSize end = AmAudioFrame::size * (length / AmAudioFrame::size); - const auto factor = xsimd::batch(gain); - - for (AmSize i = 0; i < end; i += AmAudioFrame::size) - { - const auto bin = xsimd::load_aligned(&out[i]); - xsimd::store_aligned(&out[i], xsimd::mul(bin, factor)); - } - - for (AmSize i = end; i < length; i++) - { - out[i] = out[i] * gain; - } -#else - for (AmSize i = 0; i < length; i++) + for (AmSize c = 0; c < channels; c++) { - out[i] = out[i] * gain; + auto& outChannel = out[c]; + ScalarMultiply(outChannel.begin(), outChannel.begin(), gain, frames); } -#endif // AM_SIMD_INTRINSICS } void Cleanup(const AmplimixLayer* layer) override @@ -144,4 +124,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_OBSTRUCTION_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_OBSTRUCTION_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/OcclusionProcessor.h b/src/Mixer/Pipeline/OcclusionProcessor.h similarity index 73% rename from src/Mixer/SoundProcessors/OcclusionProcessor.h rename to src/Mixer/Pipeline/OcclusionProcessor.h index d849a42b..c21c5f65 100644 --- a/src/Mixer/SoundProcessors/OcclusionProcessor.h +++ b/src/Mixer/Pipeline/OcclusionProcessor.h @@ -14,13 +14,13 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_OCCLUSION_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_OCCLUSION_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_OCCLUSION_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_OCCLUSION_PROCESSOR_H #include #include -#include +#include namespace SparkyStudios::Audio::Amplitude { @@ -60,23 +60,20 @@ namespace SparkyStudios::Audio::Amplitude ~OcclusionProcessorInstance() override { - for (auto& [soundId, filter] : _occlusionFilters) + for (const auto& filter : _occlusionFilters | std::views::values) _filter.DestroyInstance(filter); } - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { const AmReal32 occlusion = layer->GetOcclusion(); - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, out.GetFrameCount()); + + const auto frames = out.GetFrameCount(); + const auto channels = out.GetChannelCount(); + const auto sampleRate = layer->GetSoundFormat().GetSampleRate(); const AmUInt64 soundId = layer->GetId(); @@ -84,7 +81,8 @@ namespace SparkyStudios::Audio::Amplitude const Entity entity = layer->GetEntity(); // Compute the relative listener/source direction in spherical angles. - AmVec3 direction = GetRelativeDirection(listener.GetLocation(), listener.GetOrientation().GetQuaternion(), layer->GetLocation()); + AmVec3 direction = + GetRelativeDirection(listener.GetLocation(), listener.GetOrientation().GetQuaternion(), layer->GetLocation()); const SphericalPosition listenerDirection = SphericalPosition::FromWorldSpace(direction); const AmReal32 listenerDirectivity = CalculateDirectivity(listener.GetDirectivity(), listener.GetDirectivitySharpness(), listenerDirection); @@ -102,49 +100,32 @@ namespace SparkyStudios::Audio::Amplitude const AmReal32 coefficient = CalculateOcclusionFilterCoefficient(listenerDirectivity * soundDirectivity, _currentOcclusions[soundId]); - const auto& lpfCurve = amEngine->GetOcclusionCoefficientCurve(); - const auto& gainCurve = amEngine->GetOcclusionGainCurve(); + const auto& lpfCurve = Engine::GetInstance()->GetOcclusionCoefficientCurve(); + const auto& gainCurve = Engine::GetInstance()->GetOcclusionGainCurve(); if (const AmReal32 lpf = lpfCurve.Get(coefficient); lpf > 0) { if (!_occlusionFilters.contains(soundId)) { - _filter.Init(lpf); + _filter.Initialize(lpf); _occlusionFilters[soundId] = _filter.CreateInstance(); } // Update the filter coefficients - _occlusionFilters[soundId]->SetFilterParameter(MonoPoleFilter::ATTRIBUTE_COEFFICIENT, AM_CLAMP(coefficient, 0.0f, 1.0f)); + _occlusionFilters[soundId]->SetParameter(MonoPoleFilter::ATTRIBUTE_COEFFICIENT, AM_CLAMP(lpf, 0.0f, 1.0f)); // Apply Low Pass Filter - _occlusionFilters[soundId]->Process(out, frames, bufferSize, channels, sampleRate); + _occlusionFilters[soundId]->Process(in, out, frames, sampleRate); } const AmReal32 gain = gainCurve.Get(_currentOcclusions[soundId]); // Apply Gain - const AmSize length = frames * channels; - -#if defined(AM_SIMD_INTRINSICS) - const AmSize end = AmAudioFrame::size * (length / AmAudioFrame::size); - const auto factor = xsimd::batch(gain); - - for (AmSize i = 0; i < end; i += AmAudioFrame::size) - { - const auto bin = xsimd::load_aligned(&out[i]); - xsimd::store_aligned(&out[i], xsimd::mul(bin, factor)); - } - - for (AmSize i = end; i < length; i++) - { - out[i] = out[i] * gain; - } -#else - for (AmSize i = 0; i < length; i++) + for (AmSize c = 0; c < channels; c++) { - out[i] = out[i] * gain; + auto& outChannel = out[c]; + ScalarMultiply(outChannel.begin(), outChannel.begin(), gain, frames); } -#endif // AM_SIMD_INTRINSICS } void Cleanup(const AmplimixLayer* layer) override @@ -183,4 +164,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_OCCLUSION_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_OCCLUSION_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/PassThroughProcessor.h b/src/Mixer/Pipeline/PassThroughProcessor.h similarity index 74% rename from src/Mixer/SoundProcessors/PassThroughProcessor.h rename to src/Mixer/Pipeline/PassThroughProcessor.h index 709470e4..74492401 100644 --- a/src/Mixer/SoundProcessors/PassThroughProcessor.h +++ b/src/Mixer/Pipeline/PassThroughProcessor.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_PASS_THROUGH_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_PASS_THROUGH_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_PASS_THROUGH_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_PASS_THROUGH_PROCESSOR_H #include @@ -24,17 +24,10 @@ namespace SparkyStudios::Audio::Amplitude class PassThroughProcessorInstance : public SoundProcessorInstance { public: - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, out.GetFrameCount()); } }; @@ -57,4 +50,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_PASS_THROUGH_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_PASS_THROUGH_PROCESSOR_H diff --git a/src/Mixer/SoundProcessors/SilenceProcessor.h b/src/Mixer/Pipeline/SilenceProcessor.h similarity index 75% rename from src/Mixer/SoundProcessors/SilenceProcessor.h rename to src/Mixer/Pipeline/SilenceProcessor.h index 4d2f070c..0de61512 100644 --- a/src/Mixer/SoundProcessors/SilenceProcessor.h +++ b/src/Mixer/Pipeline/SilenceProcessor.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_SILENCE_PROCESSOR_H -#define SS_AMPLITUDE_AUDIO_SILENCE_PROCESSOR_H +#ifndef _AM_IMPLEMENTATION_MIXER_PIPELINE_SILENCE_PROCESSOR_H +#define _AM_IMPLEMENTATION_MIXER_PIPELINE_SILENCE_PROCESSOR_H #include @@ -24,16 +24,9 @@ namespace SparkyStudios::Audio::Amplitude class SilenceProcessorInstance : public SoundProcessorInstance { public: - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override + void Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) override { - std::memset(out, 0, bufferSize); + out.Clear(); } }; @@ -56,4 +49,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_SILENCE_PROCESSOR_H +#endif // _AM_IMPLEMENTATION_MIXER_PIPELINE_SILENCE_PROCESSOR_H diff --git a/src/Mixer/ProcessorPipeline.h b/src/Mixer/ProcessorPipeline.h deleted file mode 100644 index d20e81b7..00000000 --- a/src/Mixer/ProcessorPipeline.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2021-present Sparky Studios. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#ifndef SS_AMPLITUDE_AUDIO_MIXERPIPELINE_H -#define SS_AMPLITUDE_AUDIO_MIXERPIPELINE_H - -#include -#include - -namespace SparkyStudios::Audio::Amplitude -{ - class ProcessorPipeline : public SoundProcessorInstance - { - public: - ProcessorPipeline(); - ~ProcessorPipeline() override; - - void Append(SoundProcessorInstance* processor); - - void Insert(SoundProcessorInstance* processor, AmSize index); - - void Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) override; - - void Cleanup(const AmplimixLayer* layer) override; - - AmSize GetOutputBufferSize(AmUInt64 frames, AmSize bufferSize, AmUInt16 channels, AmUInt32 sampleRate) override; - - private: - std::vector _processors; - }; - - class ProcessorPipelineFactory final : public SoundProcessor - { - public: - ProcessorPipelineFactory() - : SoundProcessor("Pipeline") - {} - - AM_INLINE SoundProcessorInstance* CreateInstance() override - { - return ampoolnew(MemoryPoolKind::Amplimix, ProcessorPipeline); - } - - void DestroyInstance(SoundProcessorInstance* instance) override - { - ampooldelete(MemoryPoolKind::Amplimix, ProcessorPipeline, (ProcessorPipeline*)instance); - } - }; -} // namespace SparkyStudios::Audio::Amplitude - -#endif // SS_AMPLITUDE_AUDIO_MIXERPIPELINE_H diff --git a/src/Mixer/RealChannel.h b/src/Mixer/RealChannel.h index 8419107c..2c2f0566 100644 --- a/src/Mixer/RealChannel.h +++ b/src/Mixer/RealChannel.h @@ -14,8 +14,8 @@ #pragma once -#ifndef _AM_IMPLEMENTATION_MIXER_H -#define _AM_IMPLEMENTATION_MIXER_H +#ifndef _AM_IMPLEMENTATION_MIXER_REAL_CHANNEL_H +#define _AM_IMPLEMENTATION_MIXER_REAL_CHANNEL_H #include @@ -228,4 +228,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_IMPLEMENTATION_MIXER_H +#endif // _AM_IMPLEMENTATION_MIXER_REAL_CHANNEL_H diff --git a/src/Mixer/Resamplers/LibsamplerateResampler.h b/src/Mixer/Resamplers/LibsamplerateResampler.h index 2c2c83ed..d1377a22 100644 --- a/src/Mixer/Resamplers/LibsamplerateResampler.h +++ b/src/Mixer/Resamplers/LibsamplerateResampler.h @@ -26,34 +26,44 @@ namespace SparkyStudios::Audio::Amplitude class LibsamplerateResamplerInstance final : public ResamplerInstance { public: - void Init(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut, AmUInt64 frameCount) override + void Initialize(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) override { - _resampler = src_new(SRC_SINC_BEST_QUALITY, channelCount, nullptr); + _resampler.resize(channelCount); + for (AmUInt16 c = 0; c < channelCount; c++) + _resampler[c] = src_new(SRC_SINC_BEST_QUALITY, 1, nullptr); _numChannels = channelCount; - _frameCount = frameCount; _sampleRateIn = sampleRateIn; _sampleRateOut = sampleRateOut; _sampleRatio = static_cast(sampleRateOut) / static_cast(sampleRateIn); } - bool Process(AmConstAudioSampleBuffer input, AmUInt64& inputFrames, AmAudioSampleBuffer output, AmUInt64& outputFrames) override + bool Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) override { + AMPLITUDE_ASSERT(input.GetChannelCount() == _numChannels); + AMPLITUDE_ASSERT(output.GetChannelCount() == _numChannels); + SRC_DATA data; - data.data_in = input; - data.data_out = output; - data.end_of_input = 0; - data.input_frames = inputFrames; - data.output_frames = outputFrames; - data.src_ratio = _sampleRatio; + for (AmUInt16 c = 0; c < _numChannels; c++) + { + data.data_in = input[c].begin(); + data.data_out = output[c].begin(); + data.end_of_input = 0; + data.input_frames = inputFrames; + data.output_frames = outputFrames; + data.src_ratio = _sampleRatio; + + int err = src_process(_resampler[c], &data); - int err = src_process(_resampler, &data); + if (err != 0) + return false; + } inputFrames = data.input_frames_used; outputFrames = data.output_frames_gen; - return err == 0; + return true; } void SetSampleRate(AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) override @@ -63,7 +73,8 @@ namespace SparkyStudios::Audio::Amplitude _sampleRatio = static_cast(sampleRateOut) / static_cast(sampleRateIn); - src_set_ratio(_resampler, _sampleRatio); + for (AmUInt16 c = 0; c < _numChannels; c++) + src_set_ratio(_resampler[c], _sampleRatio); } [[nodiscard]] AmUInt32 GetSampleRateIn() const override @@ -81,14 +92,14 @@ namespace SparkyStudios::Audio::Amplitude return _numChannels; } - [[nodiscard]] AmUInt64 GetRequiredInputFrameCount(AmUInt64 outputFrameCount) const override + [[nodiscard]] AmUInt64 GetRequiredInputFrames(AmUInt64 outputFrameCount) const override { - return std::ceil(static_cast(outputFrameCount) / _sampleRatio); + return (AmUInt64)(static_cast(outputFrameCount) / _sampleRatio); } - [[nodiscard]] AmUInt64 GetExpectedOutputFrameCount(AmUInt64 inputFrameCount) const override + [[nodiscard]] AmUInt64 GetExpectedOutputFrames(AmUInt64 inputFrameCount) const override { - return std::ceil(_sampleRatio * static_cast(inputFrameCount)); + return (AmUInt64)(_sampleRatio * static_cast(inputFrameCount)); } [[nodiscard]] AmUInt64 GetInputLatency() const override @@ -103,27 +114,29 @@ namespace SparkyStudios::Audio::Amplitude void Reset() override { - src_reset(_resampler); + for (AmUInt16 c = 0; c < _numChannels; c++) + src_reset(_resampler[c]); } void Clear() override { - if (_resampler == nullptr) + if (_resampler.empty()) return; - src_delete(_resampler); - _resampler = nullptr; + for (AmUInt16 c = 0; c < _numChannels; c++) + src_delete(_resampler[c]); + + _resampler.clear(); } private: AmUInt16 _numChannels = 0; - AmUInt64 _frameCount = 0; AmUInt32 _sampleRateIn = 0; AmUInt32 _sampleRateOut = 0; AmReal64 _sampleRatio = 0.0; - SRC_STATE* _resampler = nullptr; + std::vector _resampler; }; class LibsamplerateResampler final : public Resampler diff --git a/src/Mixer/Resamplers/R8BrainResampler.h b/src/Mixer/Resamplers/R8BrainResampler.h index 848d8801..9dadba5b 100644 --- a/src/Mixer/Resamplers/R8BrainResampler.h +++ b/src/Mixer/Resamplers/R8BrainResampler.h @@ -25,27 +25,32 @@ namespace SparkyStudios::Audio::Amplitude { class R8BrainResamplerInstance final : public ResamplerInstance { - void Init(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut, AmUInt64 frameCount) override + void Initialize(AmUInt16 channelCount, AmUInt32 sampleRateIn, AmUInt32 sampleRateOut) override { _numChannels = channelCount; - _frameCount = frameCount; _resamplers.resize(channelCount); SetSampleRate(sampleRateIn, sampleRateOut); } - bool Process(AmConstAudioSampleBuffer input, AmUInt64& inputFrames, AmAudioSampleBuffer output, AmUInt64& outputFrames) override + bool Process(const AudioBuffer& input, AmUInt64& inputFrames, AudioBuffer& output, AmUInt64& outputFrames) override { + AMPLITUDE_ASSERT(input.GetChannelCount() == _numChannels); + AMPLITUDE_ASSERT(output.GetChannelCount() == _numChannels); + const AmUniquePtr input64( static_cast(ampoolmalloc(MemoryPoolKind::SoundData, inputFrames * sizeof(AmReal64)))); for (AmUInt16 c = 0; c < _numChannels; c++) { + const auto& inChannel = input[c]; + auto& outChannel = output[c]; + r8b::CDSPResampler24* resampler = _resamplers[c].get(); for (AmUInt64 i = 0; i < inputFrames; i++) - input64.get()[i] = static_cast(input[i * _numChannels + c]); + input64.get()[i] = static_cast(inChannel[i]); AmReal64Buffer output64 = nullptr; const AmUInt64 processedFrames = resampler->process(input64.get(), static_cast(inputFrames), output64); @@ -56,7 +61,7 @@ namespace SparkyStudios::Audio::Amplitude return false; for (AmUInt64 i = 0; i < outputFrames; i++) - output[i * _numChannels + c] = static_cast(output64[i]); + outChannel[i] = static_cast(output64[i]); } return true; @@ -73,7 +78,7 @@ namespace SparkyStudios::Audio::Amplitude if (_resamplers[c] != nullptr) _resamplers[c].reset(); - _resamplers[c] = std::make_unique(sampleRateIn, sampleRateOut, _frameCount); + _resamplers[c] = std::make_unique(sampleRateIn, sampleRateOut, kAmMaxSupportedFrameCount); } } @@ -92,7 +97,7 @@ namespace SparkyStudios::Audio::Amplitude return _numChannels; } - [[nodiscard]] AmUInt64 GetRequiredInputFrameCount(AmUInt64 outputFrameCount) const override + [[nodiscard]] AmUInt64 GetRequiredInputFrames(AmUInt64 outputFrameCount) const override { if (_resamplers.empty()) return 0; @@ -100,7 +105,7 @@ namespace SparkyStudios::Audio::Amplitude return _resamplers[0]->getInputRequiredForOutput(outputFrameCount); } - [[nodiscard]] AmUInt64 GetExpectedOutputFrameCount(AmUInt64 inputFrameCount) const override + [[nodiscard]] AmUInt64 GetExpectedOutputFrames(AmUInt64 inputFrameCount) const override { return std::ceil(_sampleRatio * static_cast(inputFrameCount)); } @@ -141,7 +146,6 @@ namespace SparkyStudios::Audio::Amplitude private: AmUInt16 _numChannels = 0; - AmUInt64 _frameCount = 0; AmUInt32 _sampleRateIn = 0; AmUInt32 _sampleRateOut = 0; diff --git a/src/Mixer/SoundData.cpp b/src/Mixer/SoundData.cpp index 3819943a..d0a00bd4 100644 --- a/src/Mixer/SoundData.cpp +++ b/src/Mixer/SoundData.cpp @@ -40,7 +40,7 @@ namespace SparkyStudios::Audio::Amplitude SoundChunk* SoundChunk::CreateChunk(AmUInt64 frames, AmUInt16 channels, MemoryPoolKind pool) { #if defined(AM_SIMD_INTRINSICS) - const AmUInt64 alignedFrames = AM_VALUE_ALIGN(frames, AmAudioFrame::size); + const AmUInt64 alignedFrames = AM_VALUE_ALIGN(frames, GetSimdBlockSize()); #else const AmUInt64 alignedFrames = frames; #endif // AM_SIMD_INTRINSICS @@ -51,25 +51,8 @@ namespace SparkyStudios::Audio::Amplitude chunk->frames = alignedFrames; chunk->length = alignedLength; chunk->size = alignedLength * sizeof(AmReal32); -#if defined(AM_SIMD_INTRINSICS) - chunk->samplesPerVector = AmAudioFrame::size / channels; -#endif // AM_SIMD_INTRINSICS chunk->memoryPool = pool; -#if defined(AM_SIMD_INTRINSICS) - chunk->buffer = static_cast(ampoolmalign(pool, chunk->size, AM_SIMD_ALIGNMENT)); -#else - chunk->buffer = static_cast(ampoolmalloc(pool, chunk->size)); -#endif // AM_SIMD_INTRINSICS - - if (chunk->buffer == nullptr) - { - amLogError("Failed to allocate memory for sound chunk."); - - ampooldelete(MemoryPoolKind::SoundData, SoundChunk, chunk); - return nullptr; - } - - std::memset(chunk->buffer, 0, chunk->size); + chunk->buffer = ampoolnew(pool, AudioBuffer, chunk->frames, channels); return chunk; } @@ -84,7 +67,8 @@ namespace SparkyStudios::Audio::Amplitude if (buffer == nullptr) return; - ampoolfree(memoryPool, buffer); + buffer->Clear(); + ampooldelete(memoryPool, AudioBuffer, buffer); buffer = nullptr; } diff --git a/src/Mixer/SoundData.h b/src/Mixer/SoundData.h index b2c6fffe..56d18a3a 100644 --- a/src/Mixer/SoundData.h +++ b/src/Mixer/SoundData.h @@ -17,6 +17,7 @@ #ifndef _AM_IMPLEMENTATION_MIXER_SOUND_DATA_H #define _AM_IMPLEMENTATION_MIXER_SOUND_DATA_H +#include #include #include @@ -32,11 +33,7 @@ namespace SparkyStudios::Audio::Amplitude AmUInt64 frames; AmSize size; - AmAudioFrameBuffer buffer; - -#if defined(AM_SIMD_INTRINSICS) - AmUInt64 samplesPerVector; -#endif // AM_SIMD_INTRINSICS + AudioBuffer* buffer; MemoryPoolKind memoryPool; diff --git a/src/Mixer/SoundProcessor.cpp b/src/Mixer/SoundProcessor.cpp index 236844b5..38ecfc40 100644 --- a/src/Mixer/SoundProcessor.cpp +++ b/src/Mixer/SoundProcessor.cpp @@ -171,63 +171,61 @@ namespace SparkyStudios::Audio::Amplitude _wet = AM_CLAMP(wet, 0, 1); } - void ProcessorMixer::Process( - AmAudioSampleBuffer out, - AmConstAudioSampleBuffer in, - AmUInt64 frames, - AmSize bufferSize, - AmUInt16 channels, - AmUInt32 sampleRate, - const AmplimixLayer* layer) + void ProcessorMixer::Process(const AmplimixLayer* layer, const AudioBuffer& in, AudioBuffer& out) { if (_dryProcessor == nullptr || _wetProcessor == nullptr) { - if (out != in) - std::memcpy(out, in, bufferSize); + if (&out != &in) + AudioBuffer::Copy(in, 0, out, 0, out.GetFrameCount()); return; } + const AmSize frames = out.GetFrameCount(); + const AmSize channels = out.GetChannelCount(); + const auto dryOut = SoundChunk::CreateChunk(frames, channels, MemoryPoolKind::Amplimix); const auto wetOut = SoundChunk::CreateChunk(frames, channels, MemoryPoolKind::Amplimix); - std::memcpy(dryOut->buffer, in, bufferSize); - std::memcpy(wetOut->buffer, in, bufferSize); + AudioBuffer::Copy(in, 0, *dryOut->buffer, 0, frames); + AudioBuffer::Copy(in, 0, *wetOut->buffer, 0, frames); + + _dryProcessor->Process(layer, in, *dryOut->buffer); + _wetProcessor->Process(layer, in, *wetOut->buffer); - _dryProcessor->Process(reinterpret_cast(dryOut->buffer), in, frames, bufferSize, channels, sampleRate, layer); - _wetProcessor->Process(reinterpret_cast(wetOut->buffer), in, frames, bufferSize, channels, sampleRate, layer); + AmSize remaining = frames; #if defined(AM_SIMD_INTRINSICS) - const AmSize length = frames / dryOut->samplesPerVector; - const AmSize end = length * dryOut->samplesPerVector; - const AmSize remaining = (end - frames) * channels; + const AmSize end = GetNumSimdChunks(frames); + constexpr AmSize blockSize = GetSimdBlockSize(); + + remaining = remaining - end; const auto dry = xsimd::batch(_dry); const auto wet = xsimd::batch(_wet); +#endif // AM_SIMD_INTRINSICS - for (AmSize i = 0; i < length; i++) + for (AmSize c = 0; c < channels; c++) { - const auto& bd = dryOut->buffer[i]; - const auto& bw = wetOut->buffer[i]; - - xsimd::store_aligned(&out[i * AmAudioFrame::size], xsimd::fma(bd, dry, (bw - bd) * wet)); - } + const auto& dryChannel = (*dryOut->buffer)[c]; + const auto& wetChannel = (*wetOut->buffer)[c]; + auto& outChannel = out[c]; - for (AmSize i = 0; i < remaining; i++) - { - const auto& bd = reinterpret_cast(dryOut->buffer)[i + end]; - const auto& bw = reinterpret_cast(wetOut->buffer)[i + end]; +#if defined(AM_SIMD_INTRINSICS) + for (AmSize i = 0; i < end; i += blockSize) + { + const auto& bd = xsimd::load_aligned(&dryChannel[i]); + const auto& bw = xsimd::load_aligned(&wetChannel[i]); - out[i + end] = bd * _dry + (bw - bd) * _wet; - } -#else - const AmSize length = frames * channels; + xsimd::store_aligned(&outChannel[i], xsimd::fma(bd, dry, (bw - bd) * wet)); + } +#endif // AM_SIMD_INTRINSICS - for (AmSize i = 0; i < length; i++) - { - out[i] = dryOut->buffer[i] * _dry + (wetOut->buffer[i] - dryOut->buffer[i]) * _wet; + for (AmSize i = frames - remaining; i < frames; i++) + { + outChannel[i] = dryChannel[i] * _dry + (wetChannel[i] - dryChannel[i]) * _wet; + } } -#endif // AM_SIMD_INTRINSICS SoundChunk::DestroyChunk(dryOut); SoundChunk::DestroyChunk(wetOut); diff --git a/src/Sound/Attenuation.h b/src/Sound/Attenuation.h index 7d3a6d42..ff5e584f 100644 --- a/src/Sound/Attenuation.h +++ b/src/Sound/Attenuation.h @@ -17,10 +17,10 @@ #ifndef _AM_IMPLEMENTATION_SOUND_ATTENUATION_H #define _AM_IMPLEMENTATION_SOUND_ATTENUATION_H +#include #include #include -#include #include "attenuation_definition_generated.h" diff --git a/src/Sound/AttenuationShapes.h b/src/Sound/AttenuationShapes.h index 178256cd..98d5f6c4 100644 --- a/src/Sound/AttenuationShapes.h +++ b/src/Sound/AttenuationShapes.h @@ -14,8 +14,8 @@ #pragma once -#ifndef _AM_SOUND_ATTENUATION_SHAPES_H -#define _AM_SOUND_ATTENUATION_SHAPES_H +#ifndef _AM_IMPLEMENTATION_SOUND_ATTENUATION_SHAPES_H +#define _AM_IMPLEMENTATION_SOUND_ATTENUATION_SHAPES_H #include @@ -113,4 +113,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // _AM_SOUND_ATTENUATION_SHAPES_H +#endif // _AM_IMPLEMENTATION_SOUND_ATTENUATION_SHAPES_H diff --git a/src/Sound/Collection.cpp b/src/Sound/Collection.cpp index 41051775..5e00543f 100644 --- a/src/Sound/Collection.cpp +++ b/src/Sound/Collection.cpp @@ -16,6 +16,7 @@ #include +#include #include #include #include diff --git a/src/Sound/Collection.h b/src/Sound/Collection.h index 9dbc6f17..f1bfc643 100644 --- a/src/Sound/Collection.h +++ b/src/Sound/Collection.h @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -29,7 +28,6 @@ namespace SparkyStudios::Audio::Amplitude { - class CollectionImpl final : public Collection , public SoundObjectImpl @@ -44,7 +42,7 @@ namespace SparkyStudios::Audio::Amplitude CollectionImpl(); /** - * @bref Destroys the collection asset and all related resources. + * @brief Destroys the collection asset and all related resources. */ ~CollectionImpl() override; diff --git a/src/Sound/Effect.cpp b/src/Sound/Effect.cpp index 2e113c13..6b4846bf 100644 --- a/src/Sound/Effect.cpp +++ b/src/Sound/Effect.cpp @@ -62,7 +62,7 @@ namespace SparkyStudios::Audio::Amplitude // Update effect parameters for (auto&& instance : _instances) for (AmSize i = 0, l = _parameters.size(); i < l; ++i) - instance->GetFilter()->SetFilterParameter(i, _parameters[i].GetValue()); + instance->GetFilter()->SetParameter(i, _parameters[i].GetValue()); } bool EffectImpl::LoadDefinition(const EffectDefinition* definition, EngineInternalState* state) @@ -99,7 +99,7 @@ namespace SparkyStudios::Audio::Amplitude // First initialization of filter parameters for (AmSize i = 0, l = _parent->_parameters.size(); i < l; ++i) - _filterInstance->SetFilterParameter(i, _parent->_parameters[i].GetValue()); + _filterInstance->SetParameter(i, _parent->_parameters[i].GetValue()); } EffectInstanceImpl::~EffectInstanceImpl() diff --git a/src/Sound/Effect.h b/src/Sound/Effect.h index cfb31a35..ca39f492 100644 --- a/src/Sound/Effect.h +++ b/src/Sound/Effect.h @@ -17,7 +17,9 @@ #ifndef _AM_IMPLEMENTATION_SOUND_EFFECT_H #define _AM_IMPLEMENTATION_SOUND_EFFECT_H +#include #include +#include #include diff --git a/src/Sound/Faders/ConstantFader.h b/src/Sound/Faders/ConstantFader.h index ef8d4dad..8a623c91 100644 --- a/src/Sound/Faders/ConstantFader.h +++ b/src/Sound/Faders/ConstantFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_CONSTANT_FADER_H -#define SS_AMPLITUDE_AUDIO_CONSTANT_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_CONSTANT_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_CONSTANT_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_CONSTANT_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_CONSTANT_FADER_H diff --git a/src/Sound/Faders/EaseFader.h b/src/Sound/Faders/EaseFader.h index f4b52a78..b8e37bb9 100644 --- a/src/Sound/Faders/EaseFader.h +++ b/src/Sound/Faders/EaseFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EASE_FADER_H -#define SS_AMPLITUDE_AUDIO_EASE_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_EASE_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_EASE_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EASE_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_EASE_FADER_H diff --git a/src/Sound/Faders/EaseInFader.h b/src/Sound/Faders/EaseInFader.h index b7487f4d..2b81a9cd 100644 --- a/src/Sound/Faders/EaseInFader.h +++ b/src/Sound/Faders/EaseInFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EASE_IN_FADER_H -#define SS_AMPLITUDE_AUDIO_EASE_IN_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_EASE_IN_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_EASE_IN_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EASE_IN_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_EASE_IN_FADER_H diff --git a/src/Sound/Faders/EaseInOutFader.h b/src/Sound/Faders/EaseInOutFader.h index 54249604..323f2bc1 100644 --- a/src/Sound/Faders/EaseInOutFader.h +++ b/src/Sound/Faders/EaseInOutFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EASE_INOUT_FADER_H -#define SS_AMPLITUDE_AUDIO_EASE_INOUT_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_EASE_INOUT_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_EASE_INOUT_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EASE_INOUT_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_EASE_INOUT_FADER_H diff --git a/src/Sound/Faders/EaseOutFader.h b/src/Sound/Faders/EaseOutFader.h index 9cbb478d..710eb107 100644 --- a/src/Sound/Faders/EaseOutFader.h +++ b/src/Sound/Faders/EaseOutFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EASE_OUT_FADER_H -#define SS_AMPLITUDE_AUDIO_EASE_OUT_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_EASE_OUT_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_EASE_OUT_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EASE_OUT_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_EASE_OUT_FADER_H diff --git a/src/Sound/Faders/ExponentialFader.h b/src/Sound/Faders/ExponentialFader.h index a3fdcd69..be3d8c39 100644 --- a/src/Sound/Faders/ExponentialFader.h +++ b/src/Sound/Faders/ExponentialFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_EXPONENTIAL_FADER_H -#define SS_AMPLITUDE_AUDIO_EXPONENTIAL_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_EXPONENTIAL_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_EXPONENTIAL_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_EXPONENTIAL_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_EXPONENTIAL_FADER_H diff --git a/src/Sound/Faders/LinearFader.h b/src/Sound/Faders/LinearFader.h index fd5dbb4c..adad1e24 100644 --- a/src/Sound/Faders/LinearFader.h +++ b/src/Sound/Faders/LinearFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_LINEAR_FADER_H -#define SS_AMPLITUDE_AUDIO_LINEAR_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_LINEAR_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_LINEAR_FADER_H #include #include @@ -57,4 +57,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_LINEAR_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_LINEAR_FADER_H diff --git a/src/Sound/Faders/SCurveFader.h b/src/Sound/Faders/SCurveFader.h index a52869c0..1d7d8d03 100644 --- a/src/Sound/Faders/SCurveFader.h +++ b/src/Sound/Faders/SCurveFader.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_S_CURVE_FADER_H -#define SS_AMPLITUDE_AUDIO_S_CURVE_FADER_H +#ifndef _AM_IMPLEMENTATION_SOUND_FADERS_S_CURVE_FADER_H +#define _AM_IMPLEMENTATION_SOUND_FADERS_S_CURVE_FADER_H #include #include @@ -81,4 +81,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_S_CURVE_FADER_H +#endif // _AM_IMPLEMENTATION_SOUND_FADERS_S_CURVE_FADER_H diff --git a/src/Sound/Schedulers/RandomScheduler.h b/src/Sound/Schedulers/RandomScheduler.h index 467db85a..42bd22bd 100644 --- a/src/Sound/Schedulers/RandomScheduler.h +++ b/src/Sound/Schedulers/RandomScheduler.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SPARK_AUDIO_RANDOM_SCHEDULER_H -#define SPARK_AUDIO_RANDOM_SCHEDULER_H +#ifndef _AM_IMPLEMENTATION_SCHEDULERS_RANDOM_SCHEDULER_H +#define _AM_IMPLEMENTATION_SCHEDULERS_RANDOM_SCHEDULER_H #include @@ -43,4 +43,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SPARK_AUDIO_RANDOM_SCHEDULER_H +#endif // _AM_IMPLEMENTATION_SCHEDULERS_RANDOM_SCHEDULER_H diff --git a/src/Sound/Schedulers/SequenceScheduler.h b/src/Sound/Schedulers/SequenceScheduler.h index f4252c8f..1c3ca98a 100644 --- a/src/Sound/Schedulers/SequenceScheduler.h +++ b/src/Sound/Schedulers/SequenceScheduler.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SPARK_AUDIO_CONSECUTIVE_SCHEDULER_H -#define SPARK_AUDIO_CONSECUTIVE_SCHEDULER_H +#ifndef _AM_IMPLEMENTATION_SCHEDULERS_CONSECUTIVE_SCHEDULER_H +#define _AM_IMPLEMENTATION_SCHEDULERS_CONSECUTIVE_SCHEDULER_H #include @@ -45,4 +45,4 @@ namespace SparkyStudios::Audio::Amplitude }; } // namespace SparkyStudios::Audio::Amplitude -#endif // SPARK_AUDIO_CONSECUTIVE_SCHEDULER_H +#endif // _AM_IMPLEMENTATION_SCHEDULERS_CONSECUTIVE_SCHEDULER_H diff --git a/src/Sound/Sound.cpp b/src/Sound/Sound.cpp index 0f381e20..bee85852 100644 --- a/src/Sound/Sound.cpp +++ b/src/Sound/Sound.cpp @@ -89,7 +89,7 @@ namespace SparkyStudios::Audio::Amplitude { _soundData = SoundChunk::CreateChunk(_format.GetFramesCount(), _format.GetNumChannels()); - if (_decoder->Load(reinterpret_cast(_soundData->buffer)) != _format.GetFramesCount()) + if (_decoder->Load(_soundData->buffer) != _format.GetFramesCount()) { SoundChunk::DestroyChunk(_soundData); amLogError("Could not load a sound instance. Unable to read data from the parent sound."); @@ -405,20 +405,20 @@ namespace SparkyStudios::Audio::Amplitude const AmUInt16 channels = _parent->_format.GetNumChannels(); - AmUInt64 l = frames, o = offset, r = 0; - auto b = reinterpret_cast(data->chunk->buffer); + AmUInt64 l = frames, o = offset, r = 0, s = 0; + AudioBuffer* b = data->chunk->buffer; bool needFill = true; do { - const AmUInt64 n = _decoder->Stream(b, o, l); + const AmUInt64 n = _decoder->Stream(b, s, o, l); r += n; // If we reached the end of the file but looping is enabled, then // seek back to the beginning of the file and fill the remaining part of the buffer. if (needFill = n < l && _parent->_loop && _decoder->Seek(0); needFill) { - b += n * channels; + s += n; l -= n; o = 0; } diff --git a/src/Sound/Sound.h b/src/Sound/Sound.h index 89ba0d77..dba82761 100644 --- a/src/Sound/Sound.h +++ b/src/Sound/Sound.h @@ -33,6 +33,7 @@ namespace SparkyStudios::Audio::Amplitude class CollectionImpl; class EffectInstance; class RealChannel; + class SoundChunk; /** * @brief Describes the place where a Sound belongs to. diff --git a/src/Utils/Freeverb/ReverbModel.cpp b/src/Utils/Freeverb/ReverbModel.cpp index 9bb624da..827e4489 100644 --- a/src/Utils/Freeverb/ReverbModel.cpp +++ b/src/Utils/Freeverb/ReverbModel.cpp @@ -87,7 +87,7 @@ namespace Freeverb } void ReverbModel::ProcessReplace( - AmAudioSampleBuffer inputL, AmAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip) + AmConstAudioSampleBuffer inputL, AmConstAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip) { AmReal32 outL, outR, input; @@ -132,7 +132,7 @@ namespace Freeverb } void ReverbModel::ProcessMix( - AmAudioSampleBuffer inputL, AmAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip) + AmConstAudioSampleBuffer inputL, AmConstAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip) { AmReal32 outL, outR, input; diff --git a/src/Utils/Freeverb/ReverbModel.h b/src/Utils/Freeverb/ReverbModel.h index 55d3e231..49285f9f 100644 --- a/src/Utils/Freeverb/ReverbModel.h +++ b/src/Utils/Freeverb/ReverbModel.h @@ -38,9 +38,9 @@ namespace Freeverb void Mute(); void ProcessMix( - AmAudioSampleBuffer inputL, AmAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip); + AmConstAudioSampleBuffer inputL, AmConstAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip); void ProcessReplace( - AmAudioSampleBuffer inputL, AmAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip); + AmConstAudioSampleBuffer inputL, AmConstAudioSampleBuffer inputR, AmAudioSampleBuffer outputL, AmAudioSampleBuffer outputR, AmUInt64 frames, AmUInt32 skip); void SetRoomSize(AmReal32 value); AmReal32 GetRoomSize() const; void SetDamp(AmReal32 value); diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp new file mode 100644 index 00000000..a780c678 --- /dev/null +++ b/src/Utils/Utils.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + static void InterleaveStereo( + AmSize length, const AmReal32* channel0, const AmReal32* channel1, AmUInt64 inOffset, AmReal32* out, AmUInt64 outOffset) + { + AmSize remaining = length; + +#if defined(AM_SIMD_INTRINSICS) + const AmSize end = GetNumSimdChunks(length); + constexpr AmSize blockSize = GetSimdBlockSize(); + remaining = remaining - end; + + for (AmSize i = 0; i < end; i += blockSize) + { + auto c0 = xsimd::load_aligned(&channel0[i + inOffset]); + auto c1 = xsimd::load_aligned(&channel1[i + inOffset]); + + const AmSize k = 2 * (i + outOffset); + xsimd::store_aligned(&out[k], xsimd::zip_lo(c0, c1)); + xsimd::store_aligned(&out[k + blockSize], xsimd::zip_hi(c0, c1)); + } +#endif // AM_SIMD_INTRINSICS + + for (AmSize i = length - remaining; i < length; ++i) + { + const AmSize k = 2 * (i + outOffset); + out[k] = channel0[i + inOffset]; + out[k + 1] = channel1[i + inOffset]; + } + } + + void Interleave(const AudioBuffer* in, AmUInt64 inOffset, AmReal32* out, AmUInt64 outOffset, AmInt32 numSamples, AmInt32 numChannels) + { + if (numChannels == 1) + { + std::memcpy(out + outOffset, in->GetChannel(0).begin() + inOffset, numSamples * sizeof(AmReal32)); + return; + } + else if (numChannels == 2) + { + InterleaveStereo(numSamples, in->GetChannel(0).begin(), in->GetChannel(1).begin(), inOffset, out, outOffset); + return; + } + + for (AmInt32 j = 0; j < numChannels; ++j) + { + const auto& channel = in->GetChannel(j); + for (AmInt32 i = 0; i < numSamples; ++i) + out[(i + outOffset) * numChannels + j] = channel[i + inOffset]; + } + } +} // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Utils/Utils.h b/src/Utils/Utils.h index 2811eb70..3c500f81 100644 --- a/src/Utils/Utils.h +++ b/src/Utils/Utils.h @@ -14,8 +14,8 @@ #pragma once -#ifndef SS_AMPLITUDE_AUDIO_UTILS_H -#define SS_AMPLITUDE_AUDIO_UTILS_H +#ifndef _AM_IMPLEMENTATION_UTILS_UTILS_H +#define _AM_IMPLEMENTATION_UTILS_UTILS_H #include #include @@ -35,13 +35,35 @@ namespace SparkyStudios::Audio::Amplitude typedef AmAudioFrame* AmAudioFrameBuffer; + AM_INLINE constexpr AmSize GetSimdBlockSize() + { +#if defined(AM_SIMD_INTRINSICS) + return AmAudioFrame::size; +#else + return 1; +#endif // AM_SIMD_INTRINSICS + } + + AM_INLINE AmSize GetNumChunks(AmSize size, AmSize blockSize) + { + return size - size % blockSize; + } + + AM_INLINE AmSize GetNumSimdChunks(AmSize size) + { + constexpr AmSize blockSize = GetSimdBlockSize(); + + return GetNumChunks(size, blockSize); + } + AM_INLINE void Sum( AmAudioSample* AM_RESTRICT result, const AmAudioSample* AM_RESTRICT a, const AmAudioSample* AM_RESTRICT b, const AmSize len) { #if defined(AM_SIMD_INTRINSICS) - const AmSize end = AmAudioFrame::size * (len / AmAudioFrame::size); + const AmSize end = GetNumSimdChunks(len); + constexpr AmSize blockSize = GetSimdBlockSize(); - for (AmSize i = 0; i < end; i += AmAudioFrame::size) + for (AmSize i = 0; i < end; i += blockSize) { const auto ba = xsimd::load_aligned(&a[i]); const auto bb = xsimd::load_aligned(&b[i]); @@ -49,27 +71,22 @@ namespace SparkyStudios::Audio::Amplitude auto res = xsimd::add(ba, bb); res.store_aligned(&result[i]); } - - for (AmSize i = end; i < len; i++) - { - result[i] = a[i] + b[i]; - } #else - const AmSize end4 = 4 * (len / 4); + const AmSize end = GetNumChunks(len, 4); - for (AmSize i = 0; i < end4; i += 4) + for (AmSize i = 0; i < end; i += 4) { result[i + 0] = a[i + 0] + b[i + 0]; result[i + 1] = a[i + 1] + b[i + 1]; result[i + 2] = a[i + 2] + b[i + 2]; result[i + 3] = a[i + 3] + b[i + 3]; } +#endif - for (AmSize i = end4; i < len; ++i) + for (AmSize i = end; i < len; ++i) { result[i] = a[i] + b[i]; } -#endif } AM_INLINE void ComplexMultiplyAccumulate( @@ -82,9 +99,10 @@ namespace SparkyStudios::Audio::Amplitude const AmSize len) { #if defined(AM_SIMD_INTRINSICS) - const AmSize end4 = AmAudioFrame::size * (len / AmAudioFrame::size); + const AmSize end = GetNumSimdChunks(len); + constexpr AmSize blockSize = GetSimdBlockSize(); - for (AmSize i = 0; i < end4; i += AmAudioFrame::size) + for (AmSize i = 0; i < end; i += blockSize) { const auto ra = xsimd::load_aligned(&reA[i]); const auto rb = xsimd::load_aligned(&reB[i]); @@ -102,16 +120,10 @@ namespace SparkyStudios::Audio::Amplitude imag = xsimd::fma(ia, rb, imag); imag.store_aligned(&im[i]); } - - for (AmSize i = end4; i < len; ++i) - { - re[i] += reA[i] * reB[i] - imA[i] * imB[i]; - im[i] += reA[i] * imB[i] + imA[i] * reB[i]; - } #else - const AmSize end4 = 4 * (len / 4); + const AmSize end = GetNumChunks(len, 4); - for (AmSize i = 0; i < end4; i += 4) + for (AmSize i = 0; i < end; i += 4) { re[i + 0] += reA[i + 0] * reB[i + 0] - imA[i + 0] * imB[i + 0]; re[i + 1] += reA[i + 1] * reB[i + 1] - imA[i + 1] * imB[i + 1]; @@ -123,13 +135,13 @@ namespace SparkyStudios::Audio::Amplitude im[i + 2] += reA[i + 2] * imB[i + 2] + imA[i + 2] * reB[i + 2]; im[i + 3] += reA[i + 3] * imB[i + 3] + imA[i + 3] * reB[i + 3]; } +#endif - for (AmSize i = end4; i < len; ++i) + for (AmSize i = end; i < len; ++i) { re[i] += reA[i] * reB[i] - imA[i] * imB[i]; im[i] += reA[i] * imB[i] + imA[i] * reB[i]; } -#endif } AM_INLINE void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b) @@ -167,6 +179,94 @@ namespace SparkyStudios::Audio::Amplitude { return is3D ? static_cast(std::pow(static_cast(order) + 1.f, 2.f)) : order * 2 + 1; } + + template + AM_INLINE AmSize FindNextAlignedArrayIndex(AmSize length, AmSize memoryAlignmentBytes) + { + constexpr AmSize sizeOfT = sizeof(T); + + const AmSize size = sizeOfT * length; + const AmSize unalignedSize = size % memoryAlignmentBytes; + const AmSize bytesToNextAligned = unalignedSize == 0 ? 0 : memoryAlignmentBytes - unalignedSize; + + return (size + bytesToNextAligned) / sizeOfT; + } + + AM_INLINE void Deinterleave( + const AmReal32* in, AmUInt64 inOffset, AmReal32* out, AmUInt64 outOffset, AmInt32 numSamples, AmInt32 numChannels) + { + if (numChannels == 1) + { + std::memcpy(out + outOffset, in + inOffset, numSamples * sizeof(AmReal32)); + return; + } + + for (AmInt32 i = 0; i < numSamples; ++i) + for (AmInt32 j = 0; j < numChannels; ++j) + out[j * numSamples + i + outOffset] = in[(i + inOffset) * numChannels + j]; + } + + void Interleave(const AudioBuffer* in, AmUInt64 inOffset, AmReal32* out, AmUInt64 outOffset, AmInt32 numSamples, AmInt32 numChannels); + + AM_INLINE void ScalarMultiply(const AmReal32* input, AmReal32* output, AmReal32 scalar, AmSize length) + { + AmSize remaining = length; + +#if defined(AM_SIMD_INTRINSICS) + const AmSize end = GetNumSimdChunks(length); + constexpr AmSize blockSize = GetSimdBlockSize(); + remaining = remaining - end; + + const auto& bb = xsimd::batch(scalar); + + for (AmSize i = 0; i < end; i += blockSize) + { + auto ba = xsimd::load_aligned(input + i); + + auto res = xsimd::mul(ba, bb); + res.store_aligned(output + i); + } +#endif + + for (AmSize i = length - remaining; i < length; i++) + output[i] = input[i] * scalar; + } + + AM_INLINE void PointwiseMultiply(const AmReal32* inputA, const AmReal32* inputB, AmReal32* output, AmSize length) + { + AmSize remaining = length; + +#if defined(AM_SIMD_INTRINSICS) + const AmSize end = GetNumSimdChunks(length); + constexpr AmSize blockSize = GetSimdBlockSize(); + remaining = remaining - end; + + for (AmSize i = 0; i < end; i += blockSize) + { + const auto ba = xsimd::load_aligned(inputA + i); + const auto bb = xsimd::load_aligned(inputB + i); + + auto res = xsimd::mul(ba, bb); + res.store_aligned(output + i); + } +#endif + + for (AmSize i = length - remaining; i < length; i++) + output[i] = inputA[i] * inputB[i]; + } + + AM_INLINE void GenerateHannWindow(bool fullWindow, AmSize windowLength, AudioBufferChannel* buffer) + { + AMPLITUDE_ASSERT(buffer != nullptr); + AMPLITUDE_ASSERT(windowLength <= buffer->size()); + + const AmReal32 fullWindowScalingFactor = (2.0f * AM_PI32) / (static_cast(windowLength) - 1.0f); + const AmReal32 halfWindowScalingFactor = (2.0f * AM_PI32) / (2.0f * static_cast(windowLength) - 1.0f); + const AmReal32 scalingFactor = (fullWindow) ? fullWindowScalingFactor : halfWindowScalingFactor; + + for (AmSize i = 0; i < windowLength; ++i) + (*buffer)[i] = 0.5f * (1.0f - std::cos(scalingFactor * static_cast(i))); + } } // namespace SparkyStudios::Audio::Amplitude -#endif // SS_AMPLITUDE_AUDIO_UTILS_H +#endif // _AM_IMPLEMENTATION_UTILS_UTILS_H diff --git a/tools/amir/main.cpp b/tools/amir/main.cpp index 578c8a95..5d9a73c3 100644 --- a/tools/amir/main.cpp +++ b/tools/amir/main.cpp @@ -292,25 +292,26 @@ static int process(const AmOsString& inFileName, const AmOsString& outFileName, if (irLength == 0) irLength = totalFrames; - AmAlignedReal32Buffer buffer; - buffer.Init(totalFrames * 2); - - decoder->Load(buffer.GetBuffer()); + AudioBuffer buffer(totalFrames, 2); + decoder->Load(&buffer); HRIRSphereVertex vertex; vertex.m_Position = position; vertex.m_LeftIR.resize(totalFrames); vertex.m_RightIR.resize(totalFrames); + const auto& leftChannel = buffer[0]; + const auto& rightChannel = buffer[1]; + for (AmUInt32 j = 0; j < totalFrames; ++j) { - vertex.m_LeftIR[j] = buffer[j * 2 + 0]; - vertex.m_RightIR[j] = buffer[j * 2 + 1]; + vertex.m_LeftIR[j] = leftChannel[j]; + vertex.m_RightIR[j] = rightChannel[j]; } vertices.push_back(vertex); - buffer.Release(); + buffer.Clear(); wavCodec->DestroyDecoder(decoder); } }