diff --git a/include/AudioBufferView.h b/include/AudioBufferView.h new file mode 100644 index 00000000000..56fff10faf8 --- /dev/null +++ b/include/AudioBufferView.h @@ -0,0 +1,276 @@ +/* + * 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 "LmmsTypes.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 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 + : m_data{data} + , m_frames{frames} + { + assert(channels == channelCount); + } + + constexpr BufferViewData(SampleT* 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 frames() const noexcept -> f_cnt_t { return m_frames; } + +protected: + SampleT* m_data = nullptr; + f_cnt_t m_frames = 0; +}; + +// For 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 + : m_data{data} + , m_channels{channels} + , m_frames{frames} + { + assert(channels != DynamicChannelCount); + } + + constexpr auto data() const noexcept -> SampleT* { 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; + proc_ch_t m_channels = 0; + f_cnt_t m_frames = 0; +}; + +} // namespace detail + + +/** + * 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; + +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 + : 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 + : 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()} + { + } + + constexpr auto empty() const noexcept -> bool + { + return !this->m_data || this->channels() == 0 || this->m_frames == 0; + } + + //! @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), this->channels()}; + } + else + { + return std::span{framePtr(index), this->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 -> SampleT* + { + assert(index < this->m_frames); + return this->m_data + index * this->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* + { + return framePtr(index); + } +}; + +// 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 `SampleT* 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; + + //! Contruct 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()} + { + } + + //! Contruct 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 || this->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 -> SampleT* + { + assert(channel < this->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 -> SampleT* + { + 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 -> SampleT* + { + return bufferPtr(channel); + } +}; + +// 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)); + +} // namespace lmms + +#endif // LMMS_AUDIO_BUFFER_VIEW_H diff --git a/include/LmmsTypes.h b/include/LmmsTypes.h index cf759aef995..c348a85bfc0 100644 --- a/include/LmmsTypes.h +++ b/include/LmmsTypes.h @@ -32,24 +32,25 @@ namespace lmms { -using bar_t = int32_t; -using tick_t = int32_t; -using volume_t = uint8_t; -using panning_t = int8_t; - -using sample_t = float; // standard sample-type -using int_sample_t = int16_t; // 16-bit-int-sample - -using sample_rate_t = uint32_t; // sample-rate -using fpp_t = size_t; // frames per period (0-16384) -using f_cnt_t = size_t; // standard frame-count -using ch_cnt_t = uint8_t; // channel-count (0-DEFAULT_CHANNELS) -using bpm_t = uint16_t; // tempo (MIN_BPM to MAX_BPM) -using bitrate_t = uint16_t; // bitrate in kbps -using mix_ch_t = uint16_t; // Mixer-channel (0 to MAX_CHANNEL) - -using jo_id_t = uint32_t; // (unique) ID of a journalling object - +using bar_t = std::int32_t; +using tick_t = std::int32_t; +using volume_t = std::uint8_t; +using panning_t = std::int8_t; + +using sample_t = float; // standard sample-type +using int_sample_t = std::int16_t; // 16-bit-int-sample + +using sample_rate_t = std::uint32_t; // sample-rate +using fpp_t = std::size_t; // frames per period (0-16384) +using f_cnt_t = std::size_t; // standard frame-count +using ch_cnt_t = std::uint8_t; // channel-count (0-DEFAULT_CHANNELS) +using bpm_t = std::uint16_t; // tempo (MIN_BPM to MAX_BPM) +using bitrate_t = std::uint16_t; // bitrate in kbps +using mix_ch_t = std::uint16_t; // Mixer-channel (0 to MAX_CHANNEL) +using track_ch_t = std::uint16_t; // track channel index/count (0-256) +using proc_ch_t = std::uint16_t; // audio processor channel index/count + +using jo_id_t = std::uint32_t; // (unique) ID of a journalling object } // namespace lmms