diff --git a/include/AudioData.h b/include/AudioData.h index d74a0bebdc7..af777b7660f 100644 --- a/include/AudioData.h +++ b/include/AudioData.h @@ -72,38 +72,135 @@ enum class AudioDataLayout }; +namespace detail { + +//! Based on BOOST_STRONG_TYPEDEF +template +struct alignas(T) StrongTypedef +{ +public: + using TagType = decltype(tag); + using Type = T; + + static_assert(std::is_enum_v || std::is_integral_v); + static_assert(!std::is_const_v, "Use `const StrongTypedef` instead of `StrongTypedef`"); + static_assert(std::is_trivially_copyable_v); + + static constexpr TagType Tag = tag; + + constexpr StrongTypedef() = default; + constexpr /* implicit */ StrongTypedef(Type value) : m_t{value} {}; + constexpr StrongTypedef(const StrongTypedef& value) = default; + + constexpr operator const Type&() const { return m_t; } + constexpr operator Type&() { return m_t; } + + constexpr auto operator==(StrongTypedef rhs) const noexcept -> bool { return m_t == rhs.m_t; } + constexpr auto operator<(StrongTypedef rhs) const noexcept -> bool { return m_t < rhs.m_t; } + +private: + Type m_t; +}; + +static_assert(std::is_trivially_copyable_v>, + "Trivially copyable for no performance penalty"); +static_assert(sizeof(StrongTypedef<123, float>) == sizeof(float), + "Strong typedef must be same size as the type it wraps"); +static_assert(alignof(StrongTypedef<123, float>) == alignof(float), + "Strong typedef must have same alignment as the type it wraps"); + +//! If `T` is const, returns `const U`, else returns `U` +template +using copy_const_t = std::conditional_t, std::add_const_t, U>; + +} // namespace detail + + /** - * An alias for an unbounded floating point array. + * A strongly typed fundamental data type for audio that ensures + * the audio data layout is respected. * - * Can be used as a replacement for `float*` parameters when you want to document the - * data layout of the audio data. + * Use `const SampleType` for immutable data rather than `SampleType`. + * `const SampleType*` and `SampleType*` are intended as substitutes for `const T*` and `T*` respectively. */ -template, bool> = true> -using AudioDataPtr = T*; +template, bool> = true> +using SampleType = detail::StrongTypedef; template -using SplitAudioDataPtr = AudioDataPtr; +using SplitSampleType = SampleType; + +template +using InterleavedSampleType = SampleType; + + +namespace detail { +//! Metafunction that converts a raw audio data type to a `SampleType` equivalent +template, bool> = true> +using wrap_sample_t = detail::copy_const_t>>; + +template +struct unwrap_sample; + +template +struct unwrap_sample> +{ + using type = U; +}; + +template +struct unwrap_sample> +{ + using type = const U; +}; + +//! Metafunction that converts `SampleType` to the corresponding raw audio data type template -using InterleavedAudioDataPtr = AudioDataPtr; +using unwrap_sample_t = typename unwrap_sample::type; + +} // namespace detail /** - * A simple (samples, size) pair for storing audio data of a particular layout. - * All data is contiguous in memory. + * A span for storing audio data of a particular layout. * - * NOTE: More information is still needed to correctly interpret this audio data: - * - For Split layout, the frame count is needed - * - For Interleaved layout, the channel count is needed + * All data is contiguous in memory. + * The size should be equal to the frame count * the channel count. */ -template, bool> = true> -using AudioData = Span; +template +using AudioBuffer = Span>; template -using SplitAudioData = AudioData; +using SplitAudioBuffer = AudioBuffer; template -using InterleavedAudioData = AudioData; +using InterleavedAudioBuffer = AudioBuffer; + + +//! Cast a raw audio samples pointer (i.e. `float*`) to `SampleType*` +template>> +inline auto audio_cast(S* samples1) -> T +{ + static_assert(std::is_pointer_v, "T must be a pointer"); + return reinterpret_cast(samples1); +} + +//! Cast `SampleType*` to a raw audio samples pointer (i.e. `float*`) +template +inline auto audio_cast(SampleType>* samples) -> T +{ + static_assert(std::is_pointer_v, "T must be a pointer"); + return reinterpret_cast(samples); +} + +//! Cast `const SampleType*` to a raw audio samples pointer (i.e. `const float*`) +template +inline auto audio_cast(const SampleType>>* samples) -> T +{ + static_assert(std::is_pointer_v, "T must be a pointer"); + static_assert(std::is_const_v>, "Cannot remove const"); + return reinterpret_cast(samples); +} } // namespace lmms diff --git a/include/DspEffectLibrary.h b/include/DspEffectLibrary.h index 348c7076509..e6bdc2f2f99 100644 --- a/include/DspEffectLibrary.h +++ b/include/DspEffectLibrary.h @@ -86,9 +86,9 @@ namespace lmms::DspEffectLibrary rightFX().setGain(gain); } - void nextSample(SampleFrame & in) + void nextSample(SampleFrame& in) { - nextSample(in.left(), in.right()); + nextSample(in.leftRef(), in.rightRef()); } void nextSample( sample_t& inLeft, sample_t& inRight ) diff --git a/include/PluginPinConnector.h b/include/PluginPinConnector.h index 098705c0130..1bd438ff117 100644 --- a/include/PluginPinConnector.h +++ b/include/PluginPinConnector.h @@ -116,7 +116,7 @@ class LMMS_EXPORT PluginPinConnector * `in` : track channels from LMMS core (currently just the main track channel pair) * `out` : plugin input channels in Split form */ - void routeToPlugin(f_cnt_t frames, CoreAudioData in, SplitAudioData out) const; + void routeToPlugin(f_cnt_t frames, CoreAudioBuffer in, SplitAudioBuffer out) const; /* * Routes audio from plugin outputs to LMMS track channels according to the plugin pin connector configuration. @@ -128,7 +128,7 @@ class LMMS_EXPORT PluginPinConnector * `in` : plugin output channels in Split form * `inOut` : track channels from/to LMMS core (inplace processing) */ - void routeFromPlugin(f_cnt_t frames, SplitAudioData in, CoreAudioDataMut inOut) const; + void routeFromPlugin(f_cnt_t frames, SplitAudioBuffer in, CoreAudioBufferMut inOut) const; /** * SerializingObject implementation diff --git a/include/SampleFrame.h b/include/SampleFrame.h index 60993a2a2f5..525cd7d523c 100644 --- a/include/SampleFrame.h +++ b/include/SampleFrame.h @@ -53,42 +53,42 @@ class SampleFrame { } - InterleavedAudioDataPtr data() + InterleavedSampleType* data() { return m_samples.data(); } - InterleavedAudioDataPtr data() const + const InterleavedSampleType* data() const { return m_samples.data(); } - sample_t& left() + sample_t& leftRef() { return m_samples[0]; } - const sample_t& left() const + sample_t left() const { return m_samples[0]; } - void setLeft(const sample_t& value) + void setLeft(sample_t value) { m_samples[0] = value; } - sample_t& right() + sample_t& rightRef() { return m_samples[1]; } - const sample_t& right() const + sample_t right() const { return m_samples[1]; } - void setRight(const sample_t& value) + void setRight(sample_t value) { m_samples[1] = value; } @@ -98,7 +98,7 @@ class SampleFrame return m_samples[index]; } - const sample_t& operator[](size_t index) const + sample_t operator[](size_t index) const { return m_samples[index]; } @@ -110,8 +110,8 @@ class SampleFrame SampleFrame& operator+=(const SampleFrame& other) { - auto & l = left(); - auto & r = right(); + auto& l = leftRef(); + auto& r = rightRef(); l += other.left(); r += other.right(); @@ -139,8 +139,8 @@ class SampleFrame void operator*=(const SampleFrame& other) { - left() *= other.left(); - right() *= other.right(); + leftRef() *= other.left(); + rightRef() *= other.right(); } sample_t sumOfSquaredAmplitudes() const @@ -168,10 +168,10 @@ class SampleFrame void clamp(sample_t low, sample_t high) { - auto & l = left(); + auto& l = leftRef(); l = std::clamp(l, low, high); - auto & r = right(); + auto& r = rightRef(); r = std::clamp(r, low, high); } @@ -186,7 +186,7 @@ class SampleFrame } private: - std::array m_samples; + std::array, DEFAULT_CHANNELS> m_samples; }; inline void zeroSampleFrames(SampleFrame* buffer, size_t frames) @@ -209,7 +209,7 @@ inline SampleFrame getAbsPeakValues(SampleFrame* buffer, size_t frames) return peaks; } -inline void copyToSampleFrames(SampleFrame* target, InterleavedAudioDataPtr source, size_t frames) +inline void copyToSampleFrames(SampleFrame* target, const InterleavedSampleType* source, size_t frames) { for (size_t i = 0; i < frames; ++i) { @@ -218,7 +218,7 @@ inline void copyToSampleFrames(SampleFrame* target, InterleavedAudioDataPtr target, const SampleFrame* source, size_t frames) +inline void copyFromSampleFrames(InterleavedSampleType* target, const SampleFrame* source, size_t frames) { for (size_t i = 0; i < frames; ++i) { @@ -227,8 +227,36 @@ inline void copyFromSampleFrames(InterleavedAudioDataPtr target, const Sa } } -using CoreAudioData = Span; -using CoreAudioDataMut = Span; +using CoreAudioBuffer = Span; +using CoreAudioBufferMut = Span; + +//! Cast a raw audio samples pointer `sample_t*` to `SampleFrame*` +template<> +inline auto audio_cast(sample_t* samples) -> SampleFrame* +{ + return reinterpret_cast(samples); +} + +//! Cast a raw audio samples pointer `const float*` to `const SampleFrame*` +template<> +inline auto audio_cast(const sample_t* samples) -> const SampleFrame* +{ + return reinterpret_cast(samples); +} + +//! Cast a raw audio samples pointer `InterleavedSampleType*` to `SampleFrame*` +template<> +inline auto audio_cast(InterleavedSampleType* samples) -> SampleFrame* +{ + return reinterpret_cast(samples); +} + +//! Cast a raw audio samples pointer `const InterleavedSampleType*` to `const SampleFrame*` +template<> +inline auto audio_cast(const InterleavedSampleType* samples) -> const SampleFrame* +{ + return reinterpret_cast(samples); +} } // namespace lmms diff --git a/include/lmms_basics.h b/include/lmms_basics.h index f4a45d5d769..5bf26951150 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -74,26 +74,46 @@ constexpr const char* UI_CTRL_KEY = "Ctrl"; #endif - -//! Stand-in for C++20's std::span +/** + * Simple minimally functional stand-in for C++20's std::span + * + * TODO C++20: Use std::span instead + */ template -struct Span +class Span { - T* ptr; - std::size_t size; - - constexpr auto operator[](std::size_t idx) const -> const T& { return ptr[idx]; } - constexpr auto operator[](std::size_t idx) -> T& { return ptr[idx]; } - - constexpr auto begin() const -> const T* { return ptr; } - constexpr auto begin() -> T* { return ptr; } - constexpr auto end() const -> const T* { return ptr + size; } - constexpr auto end() -> T* { return ptr + size; } +public: + Span() = default; + Span(T* data, std::size_t size) + : m_data{data} + , m_size{size} + { + } + + constexpr auto data() const -> T* { return m_data; } + constexpr auto size() const -> std::size_t { return m_size; } + constexpr auto size_bytes() const -> std::size_t { return m_size * sizeof(T); } + + constexpr auto operator[](std::size_t idx) const -> const T& { return m_data[idx]; } + constexpr auto operator[](std::size_t idx) -> T& { return m_data[idx]; } + + constexpr auto begin() const -> const T* { return m_data; } + constexpr auto begin() -> T* { return m_data; } + constexpr auto end() const -> const T* { return m_data + m_size; } + constexpr auto end() -> T* { return m_data + m_size; } + +private: + T* m_data = nullptr; + std::size_t m_size = 0; }; -// Stand-in for C++23's std::unreachable -// Taken from https://en.cppreference.com/w/cpp/utility/unreachable +/** + * Stand-in for C++23's std::unreachable + * Taken from https://en.cppreference.com/w/cpp/utility/unreachable + * + * TODO C++23: Use std::unreachable instead + */ [[noreturn]] inline void unreachable() { #if defined(_MSC_VER) && !defined(__clang__) // MSVC diff --git a/plugins/SlicerT/SlicerT.cpp b/plugins/SlicerT/SlicerT.cpp index 3b060258401..6841b0cc21f 100644 --- a/plugins/SlicerT/SlicerT.cpp +++ b/plugins/SlicerT/SlicerT.cpp @@ -120,8 +120,8 @@ void SlicerT::playNote(NotePlayHandle* handle, SampleFrame* workingBuffer) SRC_STATE* resampleState = playbackState->resamplingState(); SRC_DATA resampleData; - resampleData.data_in = (m_originalSample.data() + noteFrame)->data(); - resampleData.data_out = (workingBuffer + offset)->data(); + resampleData.data_in = audio_cast((m_originalSample.data() + noteFrame)->data()); + resampleData.data_out = audio_cast((workingBuffer + offset)->data()); resampleData.input_frames = noteLeft * m_originalSample.sampleSize(); resampleData.output_frames = frames; resampleData.src_ratio = speedRatio; diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 1fb0b71b5c1..d005b3206b1 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -247,8 +247,8 @@ void Effect::resample( int _i, const SampleFrame* _src_buf, } m_srcData[_i].input_frames = _frames; m_srcData[_i].output_frames = Engine::audioEngine()->framesPerPeriod(); - m_srcData[_i].data_in = const_cast(_src_buf[0].data()); - m_srcData[_i].data_out = _dst_buf[0].data (); + m_srcData[_i].data_in = audio_cast(_src_buf[0].data()); + m_srcData[_i].data_out = audio_cast(_dst_buf[0].data()); m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; m_srcData[_i].end_of_input = 0; diff --git a/src/core/PluginPinConnector.cpp b/src/core/PluginPinConnector.cpp index 04e0e25c60b..6ba01412720 100644 --- a/src/core/PluginPinConnector.cpp +++ b/src/core/PluginPinConnector.cpp @@ -118,20 +118,20 @@ void PluginPinConnector::setDefaultConnections() } void PluginPinConnector::routeToPlugin(f_cnt_t frames, - CoreAudioData in, SplitAudioData out) const + CoreAudioBuffer in, SplitAudioBuffer out) const { // Ignore all unused track channels for better performance const auto inSizeConstrained = m_trackChannelsUpperBound / 2; - assert(inSizeConstrained <= in.size); + assert(inSizeConstrained <= in.size()); // Zero the output buffer - std::fill_n(out.ptr, out.size, 0.f); + std::fill(out.begin(), out.end(), 0.f); std::uint8_t outChannel = 0; - for (f_cnt_t outSampleIdx = 0; outSampleIdx < out.size; outSampleIdx += frames, ++outChannel) + for (f_cnt_t outSampleIdx = 0; outSampleIdx < out.size(); outSampleIdx += frames, ++outChannel) { mix_ch_t numRouted = 0; // counter for # of in channels routed to the current out channel - sample_t* outPtr = &out[outSampleIdx]; + SplitSampleType* outPtr = &out[outSampleIdx]; for (std::uint8_t inChannelPairIdx = 0; inChannelPairIdx < inSizeConstrained; ++inChannelPairIdx) { @@ -191,13 +191,13 @@ void PluginPinConnector::routeToPlugin(f_cnt_t frames, } void PluginPinConnector::routeFromPlugin(f_cnt_t frames, - SplitAudioData in, CoreAudioDataMut inOut) const + SplitAudioBuffer in, CoreAudioBufferMut inOut) const { assert(frames <= DEFAULT_BUFFER_SIZE); // Ignore all unused track channels for better performance const auto inOutSizeConstrained = m_trackChannelsUpperBound / 2; - assert(inOutSizeConstrained <= inOut.size); + assert(inOutSizeConstrained <= inOut.size()); for (std::uint8_t outChannelPairIdx = 0; outChannelPairIdx < inOutSizeConstrained; ++outChannelPairIdx) { @@ -205,15 +205,15 @@ void PluginPinConnector::routeFromPlugin(f_cnt_t frames, const auto outChannel = static_cast(outChannelPairIdx * 2); const auto mixInputs = [&](std::uint8_t outChannel, std::uint8_t outChannelOffset) { - WorkingBuffer.fill(0); // used as buffer out + WorkingBuffer.fill(0.f); // used as buffer out // Counter for # of in channels routed to the current out channel mix_ch_t numRouted = 0; std::uint8_t inChannel = 0; - for (f_cnt_t inSampleIdx = 0; inSampleIdx < in.size; inSampleIdx += frames, ++inChannel) + for (f_cnt_t inSampleIdx = 0; inSampleIdx < in.size(); inSampleIdx += frames, ++inChannel) { - const sample_t* inPtr = &in[inSampleIdx]; + const SplitSampleType* inPtr = &in[inSampleIdx]; if (m_out.enabled(outChannel + outChannelOffset, inChannel)) { diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index f16ae11d2db..ade7adabb36 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -365,9 +365,9 @@ bool RemotePlugin::process( const SampleFrame * _in_buf, SampleFrame * _out_buf if (m_splitChannels) { // NOTE: VST plugins always use split channels - const auto trackChannels = CoreAudioData{&_in_buf, 1}; - const auto pluginInput = SplitAudioData { - m_audioBuffer.get(), + const auto trackChannels = CoreAudioBuffer{&_in_buf, 1}; + const auto pluginInput = SplitAudioBuffer { + audio_cast*>(m_audioBuffer.get()), static_cast(frames * m_pinConnector.in().channelCount()) }; @@ -375,12 +375,12 @@ bool RemotePlugin::process( const SampleFrame * _in_buf, SampleFrame * _out_buf } else if (inputsClamped == DEFAULT_CHANNELS) { - auto target = m_audioBuffer.get(); + auto target = audio_cast*>(m_audioBuffer.get()); copyFromSampleFrames(target, _in_buf, frames); } else { - auto o = reinterpret_cast(m_audioBuffer.get()); + auto o = audio_cast(m_audioBuffer.get()); for( ch_cnt_t ch = 0; ch < inputsClamped; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) @@ -408,9 +408,10 @@ bool RemotePlugin::process( const SampleFrame * _in_buf, SampleFrame * _out_buf if (m_splitChannels) { // NOTE: VST plugins always use split channels - const auto trackChannels = CoreAudioDataMut{&_out_buf, 1}; - const auto pluginOutput = SplitAudioData { - m_audioBuffer.get() + (frames * m_pinConnector.in().channelCount()), + const auto trackChannels = CoreAudioBufferMut{&_out_buf, 1}; + const auto pluginOutput = SplitAudioBuffer { + audio_cast*>( + m_audioBuffer.get() + (frames * m_pinConnector.in().channelCount())), static_cast(frames * m_pinConnector.out().channelCount()) }; @@ -418,7 +419,8 @@ bool RemotePlugin::process( const SampleFrame * _in_buf, SampleFrame * _out_buf } else if (outputsClamped == DEFAULT_CHANNELS) { - auto source = m_audioBuffer.get() + m_pinConnector.in().channelCount() * frames; + auto source = audio_cast*>( + m_audioBuffer.get() + m_pinConnector.in().channelCount() * frames); copyToSampleFrames(_out_buf, source, frames); } else