/* * AudioBufferView.h - Non-owning views for interleaved and * non-interleaved (planar) buffers * * Copyright (c) 2025 Dalton Messmer * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #ifndef LMMS_AUDIO_BUFFER_VIEW_H #define LMMS_AUDIO_BUFFER_VIEW_H #include #include #include #include #include #include "LmmsTypes.h" #include "SampleFrame.h" namespace lmms { //! Use when the number of channels is not known at compile time inline constexpr auto DynamicChannelCount = static_cast(-1); namespace detail { // For buffer views with static channel count template class BufferViewData { public: constexpr BufferViewData() = default; constexpr BufferViewData(const BufferViewData&) = default; 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(T* data, f_cnt_t frames) noexcept : m_data{data} , m_frames{frames} { } 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: T* m_data = nullptr; f_cnt_t m_frames = 0; }; // For buffer views with dynamic channel count template class BufferViewData { public: constexpr BufferViewData() = default; constexpr BufferViewData(const BufferViewData&) = default; constexpr BufferViewData(T* data, proc_ch_t channels, f_cnt_t frames) noexcept : m_data{data} , m_channels{channels} , m_frames{frames} { assert(channels != DynamicChannelCount); } 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: 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 { using Base = detail::BufferViewData; using FrameIter = detail::InterleavedFrameIterator; using ConstFrameIter = detail::InterleavedFrameIterator; public: using Base::Base; //! Construct const from mutable (static channel count) template requires (std::is_const_v && channelCount != DynamicChannelCount) constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept : Base{other.data(), other.frames()} { } //! Construct const from mutable (dynamic channel count) template requires (std::is_const_v && channelCount == DynamicChannelCount) constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept : Base{other.data(), other.channels(), other.frames()} { } //! Construct dynamic channel count from static template requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) 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 || 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 constexpr auto frame(f_cnt_t index) const noexcept { if constexpr (channelCount == DynamicChannelCount) { return std::span{framePtr(index), Base::channels()}; } else { return std::span{framePtr(index), Base::channels()}; } } /** * @return pointer to the frame at the given index. * The size of the frame is `channels()`. */ constexpr auto framePtr(f_cnt_t index) const noexcept -> T* { assert(index < this->m_frames); 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 -> 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 static_assert(sizeof(InterleavedBufferView) > sizeof(InterleavedBufferView)); static_assert(sizeof(InterleavedBufferView) == sizeof(void*) + sizeof(f_cnt_t)); /** * Non-owning view for multi-channel non-interleaved audio data * * 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 { using Base = detail::BufferViewData; public: using Base::Base; //! Construct const from mutable (static channel count) template requires (std::is_const_v && channelCount != DynamicChannelCount) constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept : Base{other.data(), other.frames()} { } //! Construct const from mutable (dynamic channel count) template requires (std::is_const_v && channelCount == DynamicChannelCount) constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept : Base{other.data(), other.channels(), other.frames()} { } //! Construct dynamic channel count from static template requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) constexpr PlanarBufferView(PlanarBufferView other) noexcept : Base{other.data(), otherChannels, other.frames()} { } constexpr auto empty() const noexcept -> bool { 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 { return {bufferPtr(channel), this->m_frames}; } //! @return the buffer of the given channel template requires (channelCount != DynamicChannelCount) constexpr auto buffer() const noexcept -> std::span { return {bufferPtr(), this->m_frames}; } /** * @return pointer to the buffer of the given channel. * The size of the buffer is `frames()`. */ constexpr auto bufferPtr(proc_ch_t channel) const noexcept -> T* { assert(channel < Base::channels()); assert(this->m_data != nullptr); return this->m_data[channel]; } /** * @return pointer to the buffer of the given channel. * The size of the buffer is `frames()`. */ template requires (channelCount != DynamicChannelCount) constexpr auto bufferPtr() const noexcept -> T* { static_assert(channel < channelCount); assert(this->m_data != nullptr); return this->m_data[channel]; } /** * @return pointer to the buffer of a given channel. * The size of the buffer is `frames()`. */ 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