diff --git a/include/AudioBufferView.h b/include/AudioBufferView.h index 56fff10faf8..f0c0b0864f6 100644 --- a/include/AudioBufferView.h +++ b/include/AudioBufferView.h @@ -27,10 +27,13 @@ #define LMMS_AUDIO_BUFFER_VIEW_H #include +#include +#include #include #include #include "LmmsTypes.h" +#include "SampleFrame.h" namespace lmms { @@ -41,45 +44,45 @@ inline constexpr auto DynamicChannelCount = static_cast(-1); namespace detail { -// For static channel count -template +// For buffer views with static channel count +template class BufferViewData { public: constexpr BufferViewData() = default; constexpr BufferViewData(const BufferViewData&) = default; - constexpr BufferViewData(SampleT* data, proc_ch_t channels, f_cnt_t frames) noexcept + constexpr BufferViewData(T* data, [[maybe_unused]] proc_ch_t channels, f_cnt_t frames) noexcept : m_data{data} , m_frames{frames} { assert(channels == channelCount); } - constexpr BufferViewData(SampleT* data, f_cnt_t frames) noexcept + constexpr BufferViewData(T* data, f_cnt_t frames) noexcept : m_data{data} , m_frames{frames} { } - constexpr auto data() const noexcept -> SampleT* { return m_data; } - constexpr auto channels() const noexcept -> proc_ch_t { return channelCount; } + constexpr auto data() const noexcept -> T* { return m_data; } + static constexpr auto channels() noexcept -> proc_ch_t { return channelCount; } constexpr auto frames() const noexcept -> f_cnt_t { return m_frames; } protected: - SampleT* m_data = nullptr; + T* m_data = nullptr; f_cnt_t m_frames = 0; }; -// For dynamic channel count -template -class BufferViewData +// For buffer views with dynamic channel count +template +class BufferViewData { public: constexpr BufferViewData() = default; constexpr BufferViewData(const BufferViewData&) = default; - constexpr BufferViewData(SampleT* data, proc_ch_t channels, f_cnt_t frames) noexcept + constexpr BufferViewData(T* data, proc_ch_t channels, f_cnt_t frames) noexcept : m_data{data} , m_channels{channels} , m_frames{frames} @@ -87,42 +90,223 @@ class BufferViewData assert(channels != DynamicChannelCount); } - constexpr auto data() const noexcept -> SampleT* { return m_data; } + constexpr auto data() const noexcept -> T* { return m_data; } constexpr auto channels() const noexcept -> proc_ch_t { return m_channels; } constexpr auto frames() const noexcept -> f_cnt_t { return m_frames; } protected: - SampleT* m_data = nullptr; + T* m_data = nullptr; proc_ch_t m_channels = 0; f_cnt_t m_frames = 0; }; +// For interleaved frame iterators with static channel count +template +class InterleavedFrameIteratorData +{ +public: + constexpr InterleavedFrameIteratorData() = default; + constexpr InterleavedFrameIteratorData(const InterleavedFrameIteratorData&) = default; + + constexpr explicit InterleavedFrameIteratorData(T* data) noexcept + : m_data{data} + { + } + + static constexpr auto channels() noexcept -> proc_ch_t { return channelCount; } + +protected: + T* m_data = nullptr; +}; + +// For interleaved frame iterators with dynamic channel count +template +class InterleavedFrameIteratorData +{ +public: + constexpr InterleavedFrameIteratorData() = default; + constexpr InterleavedFrameIteratorData(const InterleavedFrameIteratorData&) = default; + + constexpr InterleavedFrameIteratorData(T* data, proc_ch_t channels) noexcept + : m_data{data} + , m_channels{channels} + { + } + + constexpr auto channels() const noexcept -> proc_ch_t { return m_channels; } + +protected: + T* m_data = nullptr; + proc_ch_t m_channels = 0; +}; + +// Allows for iterating over the frames of `InterleavedBufferView` +template +class InterleavedFrameIterator : public InterleavedFrameIteratorData +{ + using Base = InterleavedFrameIteratorData; + +public: + using iterator_concept = std::random_access_iterator_tag; + using value_type = T*; + using difference_type = std::ptrdiff_t; + + using Base::Base; + + constexpr auto operator*() const noexcept -> value_type { return this->m_data; } + + constexpr auto operator[](difference_type frames) const noexcept -> value_type + { + return this->m_data[frames * Base::channels()]; + } + + constexpr auto operator++() noexcept -> InterleavedFrameIterator& + { + this->m_data += Base::channels(); + return *this; + } + + constexpr auto operator++(int) noexcept -> InterleavedFrameIterator + { + auto temp = *this; + ++(*this); + return temp; + } + + constexpr auto operator--() noexcept -> InterleavedFrameIterator& + { + this->m_data -= Base::channels(); + return *this; + } + + constexpr auto operator--(int) noexcept -> InterleavedFrameIterator + { + auto temp = *this; + --(*this); + return temp; + } + + constexpr auto operator+=(difference_type channels) noexcept -> InterleavedFrameIterator& + { + this->m_data += channels * Base::channels(); + return *this; + } + + constexpr auto operator-=(difference_type channels) noexcept -> InterleavedFrameIterator& + { + this->m_data -= channels * Base::channels(); + return *this; + } + + friend constexpr auto operator+(InterleavedFrameIterator iter, difference_type frames) noexcept + -> InterleavedFrameIterator + { + if constexpr (channelCount == DynamicChannelCount) + { + return {iter.m_data + frames * Base::channels(), Base::channels()}; + } + else + { + return InterleavedFrameIterator{iter.m_data + frames * Base::channels()}; + } + } + + friend constexpr auto operator+(difference_type frames, InterleavedFrameIterator iter) noexcept + -> InterleavedFrameIterator + { + return iter + frames; + } + + constexpr auto operator-(difference_type frames) const noexcept -> InterleavedFrameIterator + { + if constexpr (channelCount == DynamicChannelCount) + { + return {this->m_data - frames * Base::channels(), Base::channels()}; + } + else + { + return InterleavedFrameIterator{this->m_data - frames * Base::channels()}; + } + } + + constexpr auto operator-(InterleavedFrameIterator other) const noexcept -> difference_type + { + return this->m_data - other.m_data; + } + + constexpr auto operator<=>(InterleavedFrameIterator other) const noexcept + { + return this->m_data <=> other.m_data; + } + + constexpr auto operator==(InterleavedFrameIterator other) const noexcept -> bool + { + return this->m_data == other.m_data; + } + + constexpr auto operator<=>(T* sentinel) const noexcept + { + return this->m_data <=> sentinel; + } + + constexpr auto operator==(T* sentinel) const noexcept -> bool + { + return this->m_data == sentinel; + } + + constexpr auto base() const noexcept -> T* { return this->m_data; } +}; + +static_assert(std::random_access_iterator>); + +template +inline constexpr bool OneOf = (std::is_same_v || ...); + } // namespace detail +//! Recognized sample types, either const or non-const +template +concept SampleType = detail::OneOf, + float, + double, + std::int8_t, + std::uint8_t, + std::int16_t, + std::uint16_t, + std::int32_t, + std::uint32_t, + std::int64_t, + std::uint64_t +>; + + /** * Non-owning view for multi-channel interleaved audio data * * TODO C++23: Use std::mdspan? */ -template -class InterleavedBufferView : public detail::BufferViewData +template +class InterleavedBufferView : public detail::BufferViewData { - using Base = detail::BufferViewData; + using Base = detail::BufferViewData; + + using FrameIter = detail::InterleavedFrameIterator; + using ConstFrameIter = detail::InterleavedFrameIterator; public: using Base::Base; //! Contruct const from mutable (static channel count) - template requires (std::is_const_v && channelCount != DynamicChannelCount) - constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept + template requires (std::is_const_v && channelCount != DynamicChannelCount) + constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept : Base{other.data(), other.frames()} { } //! Contruct const from mutable (dynamic channel count) - template requires (std::is_const_v && channelCount == DynamicChannelCount) - constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept + template requires (std::is_const_v && channelCount == DynamicChannelCount) + constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept : Base{other.data(), other.channels(), other.frames()} { } @@ -130,14 +314,38 @@ class InterleavedBufferView : public detail::BufferViewData requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) - constexpr InterleavedBufferView(InterleavedBufferView other) noexcept + constexpr InterleavedBufferView(InterleavedBufferView other) noexcept : Base{other.data(), otherChannels, other.frames()} { } + //! Construct from std::span + InterleavedBufferView(std::span buffer) noexcept + requires (std::is_same_v, float> && channelCount == 2) + : Base{reinterpret_cast(buffer.data()), buffer.size()} + { + } + + //! Construct from std::span + InterleavedBufferView(std::span buffer) noexcept + requires (std::is_same_v && channelCount == 2) + : Base{reinterpret_cast(buffer.data()), buffer.size()} + { + } + constexpr auto empty() const noexcept -> bool { - return !this->m_data || this->channels() == 0 || this->m_frames == 0; + return !this->m_data || Base::channels() == 0 || this->m_frames == 0; + } + + constexpr auto dataSizeBytes() const noexcept -> std::size_t + { + return Base::channels() * this->m_frames * sizeof(T); + } + + constexpr auto dataView() noexcept -> std::span + { + return std::span{this->m_data, this->m_frames * Base::channels()}; } //! @return the frame at the given index @@ -145,11 +353,11 @@ class InterleavedBufferView : public detail::BufferViewData{framePtr(index), this->channels()}; + return std::span{framePtr(index), Base::channels()}; } else { - return std::span{framePtr(index), this->channels()}; + return std::span{framePtr(index), Base::channels()}; } } @@ -157,20 +365,92 @@ class InterleavedBufferView : public detail::BufferViewData SampleT* + constexpr auto framePtr(f_cnt_t index) const noexcept -> T* { assert(index < this->m_frames); - return this->m_data + index * this->channels(); + return this->m_data + index * Base::channels(); } /** * @return pointer to the frame at the given index. * The size of the frame is `channels()`. */ - constexpr auto operator[](f_cnt_t index) const noexcept -> SampleT* + constexpr auto operator[](f_cnt_t index) const noexcept -> T* { return framePtr(index); } + + //! @returns a subview at the given frame offset `offset` with a frame count of `frames` + constexpr auto subspan(f_cnt_t offset, f_cnt_t frames) const -> InterleavedBufferView + { + assert(offset <= this->m_frames); + assert(offset + frames <= this->m_frames); + if constexpr (channelCount == DynamicChannelCount) + { + return {this->m_data + offset * Base::channels(), Base::channels(), frames}; + } + else + { + return {this->m_data + offset * Base::channels(), frames}; + } + } + + //! @returns a const view over the frames. Iterates in chunks containing `channels()` elements. + constexpr auto framesView() const noexcept -> std::ranges::subrange + { + const T* end = this->m_data + Base::channels() * this->m_frames; + if constexpr (channelCount == DynamicChannelCount) + { + return std::ranges::subrange{ConstFrameIter{this->m_data, Base::channels()}, end}; + } + else + { + return std::ranges::subrange{ConstFrameIter{this->m_data}, end}; + } + } + + //! @returns a view over the frames. Iterates in chunks containing `channels()` elements. + constexpr auto framesView() noexcept -> std::ranges::subrange + { + T* end = this->m_data + Base::channels() * this->m_frames; + if constexpr (channelCount == DynamicChannelCount) + { + return std::ranges::subrange{FrameIter{this->m_data, Base::channels()}, end}; + } + else + { + return std::ranges::subrange{FrameIter{this->m_data}, end}; + } + } + + auto sampleFrameAt(f_cnt_t index) noexcept -> SampleFrame& + requires (std::is_same_v && channelCount == 2) + { + assert(index < this->m_frames); + return reinterpret_cast(this->m_data)[index]; + } + + auto sampleFrameAt(f_cnt_t index) const noexcept -> const SampleFrame& + requires (std::is_same_v && channelCount == 2) + { + assert(index < this->m_frames); + return reinterpret_cast(this->m_data)[index]; + } + + auto toSampleFrames() noexcept -> std::span + requires (std::is_same_v && channelCount == 2) + { + return {reinterpret_cast(this->m_data), this->m_frames}; + } + + auto toSampleFrames() const noexcept -> std::span + requires (std::is_same_v && channelCount == 2) + { + return {reinterpret_cast(this->m_data), this->m_frames}; + } + + //! Use to distinguish between `InterleavedBufferView` and `PlanarBufferView` when using `AudioBufferView` + static constexpr bool Interleaved = true; }; // Check that the std::span-like space optimization works @@ -181,29 +461,29 @@ static_assert(sizeof(InterleavedBufferView) == sizeof(void*) + sizeof( /** * Non-owning view for multi-channel non-interleaved audio data * - * The data type is `SampleT* const*` which is a 2D array accessed as data[channel][frame index] + * The data type is `T* const*` which is a 2D array accessed as data[channel][frame index] * where each channel's buffer contains `frames()` frames. * * TODO C++23: Use std::mdspan? */ -template -class PlanarBufferView : public detail::BufferViewData +template +class PlanarBufferView : public detail::BufferViewData { - using Base = detail::BufferViewData; + using Base = detail::BufferViewData; public: using Base::Base; //! Contruct const from mutable (static channel count) - template requires (std::is_const_v && channelCount != DynamicChannelCount) - constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept + template requires (std::is_const_v && channelCount != DynamicChannelCount) + constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept : Base{other.data(), other.frames()} { } //! Contruct const from mutable (dynamic channel count) - template requires (std::is_const_v && channelCount == DynamicChannelCount) - constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept + template requires (std::is_const_v && channelCount == DynamicChannelCount) + constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept : Base{other.data(), other.channels(), other.frames()} { } @@ -211,25 +491,25 @@ class PlanarBufferView : public detail::BufferViewData requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) - constexpr PlanarBufferView(PlanarBufferView other) noexcept + constexpr PlanarBufferView(PlanarBufferView other) noexcept : Base{other.data(), otherChannels, other.frames()} { } constexpr auto empty() const noexcept -> bool { - return !this->m_data || this->channels() == 0 || this->m_frames == 0; + return !this->m_data || Base::channels() == 0 || this->m_frames == 0; } //! @return the buffer of the given channel - constexpr auto buffer(proc_ch_t channel) const noexcept -> std::span + constexpr auto buffer(proc_ch_t channel) const noexcept -> std::span { return {bufferPtr(channel), this->m_frames}; } //! @return the buffer of the given channel template requires (channelCount != DynamicChannelCount) - constexpr auto buffer() const noexcept -> std::span + constexpr auto buffer() const noexcept -> std::span { return {bufferPtr(), this->m_frames}; } @@ -238,9 +518,9 @@ class PlanarBufferView : public detail::BufferViewData SampleT* + constexpr auto bufferPtr(proc_ch_t channel) const noexcept -> T* { - assert(channel < this->channels()); + assert(channel < Base::channels()); assert(this->m_data != nullptr); return this->m_data[channel]; } @@ -250,7 +530,7 @@ class PlanarBufferView : public detail::BufferViewData requires (channelCount != DynamicChannelCount) - constexpr auto bufferPtr() const noexcept -> SampleT* + constexpr auto bufferPtr() const noexcept -> T* { static_assert(channel < channelCount); assert(this->m_data != nullptr); @@ -261,16 +541,25 @@ class PlanarBufferView : public detail::BufferViewData SampleT* + constexpr auto operator[](proc_ch_t channel) const noexcept -> T* { return bufferPtr(channel); } + + //! Use to distinguish between `InterleavedBufferView` and `PlanarBufferView` when using `AudioBufferView` + static constexpr bool Interleaved = false; }; // Check that the std::span-like space optimization works static_assert(sizeof(PlanarBufferView) > sizeof(PlanarBufferView)); static_assert(sizeof(PlanarBufferView) == sizeof(void**) + sizeof(f_cnt_t)); + +//! Concept for any audio buffer view, interleaved or planar +template +concept AudioBufferView = SampleType && (std::convertible_to> + || std::convertible_to>); + } // namespace lmms #endif // LMMS_AUDIO_BUFFER_VIEW_H