mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-25 09:24:00 -04:00
This reworks the auto-quit feature by introducing a new AudioBuffer class which keeps track of which channels are currently silent as audio flows through the effects chain. When track channels going into an effect's input are not marked as quiet, it is assumed a signal is present and the plugin needs to wake up if it is asleep due to auto-quit. After a plugin processes a buffer, the silence status is updated. When the auto-quit setting is disabled (that is, when effects are always kept running), effects are always assumed to have input noise (a non-quiet signal present at the plugin inputs), which should result in the same behavior as before. Benefits: - The auto-quit system now closely follows how it is supposed to function by only waking plugins which have non-zero input rather than waking all plugins at once whenever an instrument plays a note or a sample track plays. This granularity better fits multi-channel plugins and pin connector routing where not all plugin inputs are connected to the same track channels. This means a sleeping plugin whose inputs are connected to channels 3/4 would not need to wake up if a signal is only present on channels 1/2. - Silencing channels that are already known to be silent is a no-op - Calculating the absolute peak sample value for a channel already known to be silent is a no-op - The silence flags also could be useful for other purposes, such as adding visual indicators to represent how audio signals flow in and out of each plugin - With a little more work, auto-quit could be enabled/disabled for plugins on an individual basis - With a little more work, auto-quit could be implemented for instrument plugins - AudioBuffer can be used with SharedMemory - AudioBuffer could be used in plugins for their buffers This new system works so long as the silence flags for each channel remain valid at each point along the effect chain. Modifying the buffers without an accompanying update of the silence flags could violate assumptions. Through unit tests, the correct functioning of AudioBuffer itself can be validated, but its usage in AudioBusHandle, Mixer, and a few other places where track channels are handled will need to be done with care. --------- Co-authored-by: Sotonye Atemie <sakertooth@gmail.com>
615 lines
18 KiB
C++
615 lines
18 KiB
C++
/*
|
|
* AudioBufferView.h - Non-owning views for interleaved and
|
|
* non-interleaved (planar) buffers
|
|
*
|
|
* Copyright (c) 2025 Dalton Messmer <messmer.dalton/at/gmail.com>
|
|
*
|
|
* 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 <cassert>
|
|
#include <concepts>
|
|
#include <ranges>
|
|
#include <span>
|
|
#include <type_traits>
|
|
|
|
#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<ch_cnt_t>(-1);
|
|
|
|
|
|
namespace detail {
|
|
|
|
// For buffer views with static channel count
|
|
template<typename T, ch_cnt_t channelCount>
|
|
class BufferViewData
|
|
{
|
|
public:
|
|
constexpr BufferViewData() = default;
|
|
constexpr BufferViewData(const BufferViewData&) = default;
|
|
|
|
constexpr BufferViewData(T* data, [[maybe_unused]] ch_cnt_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 -> ch_cnt_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<typename T>
|
|
class BufferViewData<T, DynamicChannelCount>
|
|
{
|
|
public:
|
|
constexpr BufferViewData() = default;
|
|
constexpr BufferViewData(const BufferViewData&) = default;
|
|
|
|
constexpr BufferViewData(T* data, ch_cnt_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 -> ch_cnt_t { return m_channels; }
|
|
constexpr auto frames() const noexcept -> f_cnt_t { return m_frames; }
|
|
|
|
protected:
|
|
T* m_data = nullptr;
|
|
ch_cnt_t m_channels = 0;
|
|
f_cnt_t m_frames = 0;
|
|
};
|
|
|
|
// For interleaved frame iterators with static channel count
|
|
template<typename T, ch_cnt_t channelCount>
|
|
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 -> ch_cnt_t { return channelCount; }
|
|
|
|
protected:
|
|
T* m_data = nullptr;
|
|
};
|
|
|
|
// For interleaved frame iterators with dynamic channel count
|
|
template<typename T>
|
|
class InterleavedFrameIteratorData<T, DynamicChannelCount>
|
|
{
|
|
public:
|
|
constexpr InterleavedFrameIteratorData() = default;
|
|
constexpr InterleavedFrameIteratorData(const InterleavedFrameIteratorData&) = default;
|
|
|
|
constexpr InterleavedFrameIteratorData(T* data, ch_cnt_t channels) noexcept
|
|
: m_data{data}
|
|
, m_channels{channels}
|
|
{
|
|
}
|
|
|
|
constexpr auto channels() const noexcept -> ch_cnt_t { return m_channels; }
|
|
|
|
protected:
|
|
T* m_data = nullptr;
|
|
ch_cnt_t m_channels = 0;
|
|
};
|
|
|
|
// Allows for iterating over the frames of `InterleavedBufferView`
|
|
template<typename T, ch_cnt_t channelCount = DynamicChannelCount>
|
|
class InterleavedFrameIterator : public InterleavedFrameIteratorData<T, channelCount>
|
|
{
|
|
using Base = InterleavedFrameIteratorData<T, channelCount>;
|
|
|
|
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<InterleavedFrameIterator<float, 2>>);
|
|
|
|
template<typename T, typename... AllowedTs>
|
|
inline constexpr bool OneOf = (std::is_same_v<T, AllowedTs> || ...);
|
|
|
|
} // namespace detail
|
|
|
|
|
|
//! Recognized sample types, either const or non-const
|
|
template<typename T>
|
|
concept SampleType = detail::OneOf<std::remove_const_t<T>,
|
|
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<SampleType T, ch_cnt_t channelCount = DynamicChannelCount>
|
|
class InterleavedBufferView : public detail::BufferViewData<T, channelCount>
|
|
{
|
|
using Base = detail::BufferViewData<T, channelCount>;
|
|
|
|
using FrameIter = detail::InterleavedFrameIterator<T, channelCount>;
|
|
using ConstFrameIter = detail::InterleavedFrameIterator<const T, channelCount>;
|
|
|
|
public:
|
|
using Base::Base;
|
|
|
|
//! Construct const from mutable (static channel count)
|
|
template<typename U = T> requires (std::is_const_v<U> && channelCount != DynamicChannelCount)
|
|
constexpr InterleavedBufferView(InterleavedBufferView<std::remove_const_t<U>, channelCount> other) noexcept
|
|
: Base{other.data(), other.frames()}
|
|
{
|
|
}
|
|
|
|
//! Construct const from mutable (dynamic channel count)
|
|
template<typename U = T> requires (std::is_const_v<U> && channelCount == DynamicChannelCount)
|
|
constexpr InterleavedBufferView(InterleavedBufferView<std::remove_const_t<U>, channelCount> other) noexcept
|
|
: Base{other.data(), other.channels(), other.frames()}
|
|
{
|
|
}
|
|
|
|
//! Construct dynamic channel count from static
|
|
template<ch_cnt_t otherChannels>
|
|
requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount)
|
|
constexpr InterleavedBufferView(InterleavedBufferView<T, otherChannels> other) noexcept
|
|
: Base{other.data(), otherChannels, other.frames()}
|
|
{
|
|
}
|
|
|
|
//! Construct from SampleFrame*
|
|
InterleavedBufferView(SampleFrame* data, f_cnt_t frames) noexcept
|
|
requires (std::is_same_v<std::remove_const_t<T>, float> && channelCount == 2)
|
|
: Base{reinterpret_cast<float*>(data), frames}
|
|
{
|
|
}
|
|
|
|
//! Construct from const SampleFrame*
|
|
InterleavedBufferView(const SampleFrame* data, f_cnt_t frames) noexcept
|
|
requires (std::is_same_v<T, const float> && channelCount == 2)
|
|
: Base{reinterpret_cast<const float*>(data), frames}
|
|
{
|
|
}
|
|
|
|
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<T>
|
|
{
|
|
return std::span<T>{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<T>{framePtr(index), Base::channels()};
|
|
}
|
|
else
|
|
{
|
|
return std::span<T, channelCount>{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<T, channelCount>
|
|
{
|
|
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<ConstFrameIter, const T*>
|
|
{
|
|
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<FrameIter, T*>
|
|
{
|
|
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<T, float> && channelCount == 2)
|
|
{
|
|
assert(index < this->m_frames);
|
|
return reinterpret_cast<SampleFrame*>(this->m_data)[index];
|
|
}
|
|
|
|
auto sampleFrameAt(f_cnt_t index) const noexcept -> const SampleFrame&
|
|
requires (std::is_same_v<T, const float> && channelCount == 2)
|
|
{
|
|
assert(index < this->m_frames);
|
|
return reinterpret_cast<const SampleFrame*>(this->m_data)[index];
|
|
}
|
|
|
|
auto asSampleFrames() noexcept -> std::span<SampleFrame>
|
|
requires (std::is_same_v<T, float> && channelCount == 2)
|
|
{
|
|
return {reinterpret_cast<SampleFrame*>(this->m_data), this->m_frames};
|
|
}
|
|
|
|
auto asSampleFrames() const noexcept -> std::span<const SampleFrame>
|
|
requires (std::is_same_v<T, const float> && channelCount == 2)
|
|
{
|
|
return {reinterpret_cast<const SampleFrame*>(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<float>) > sizeof(InterleavedBufferView<float, 2>));
|
|
static_assert(sizeof(InterleavedBufferView<float, 2>) == sizeof(void*) + sizeof(f_cnt_t));
|
|
|
|
// Deduction guides
|
|
InterleavedBufferView(const SampleFrame*, f_cnt_t) -> InterleavedBufferView<const float, 2>;
|
|
InterleavedBufferView(SampleFrame*, f_cnt_t) -> InterleavedBufferView<float, 2>;
|
|
|
|
|
|
/**
|
|
* 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<SampleType T, ch_cnt_t channelCount = DynamicChannelCount>
|
|
class PlanarBufferView : public detail::BufferViewData<T* const, channelCount>
|
|
{
|
|
using Base = detail::BufferViewData<T* const, channelCount>;
|
|
|
|
public:
|
|
using Base::Base;
|
|
|
|
//! Construct const from mutable (static channel count)
|
|
template<typename U = T> requires (std::is_const_v<U> && channelCount != DynamicChannelCount)
|
|
constexpr PlanarBufferView(PlanarBufferView<std::remove_const_t<U>, channelCount> other) noexcept
|
|
: Base{other.data(), other.frames()}
|
|
{
|
|
}
|
|
|
|
//! Construct const from mutable (dynamic channel count)
|
|
template<typename U = T> requires (std::is_const_v<U> && channelCount == DynamicChannelCount)
|
|
constexpr PlanarBufferView(PlanarBufferView<std::remove_const_t<U>, channelCount> other) noexcept
|
|
: Base{other.data(), other.channels(), other.frames()}
|
|
{
|
|
}
|
|
|
|
//! Construct dynamic channel count from static
|
|
template<ch_cnt_t otherChannels>
|
|
requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount)
|
|
constexpr PlanarBufferView(PlanarBufferView<T, otherChannels> 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(ch_cnt_t channel) const noexcept -> std::span<T>
|
|
{
|
|
return {bufferPtr(channel), this->m_frames};
|
|
}
|
|
|
|
//! @return the buffer of the given channel
|
|
template<ch_cnt_t channel> requires (channelCount != DynamicChannelCount)
|
|
constexpr auto buffer() const noexcept -> std::span<T>
|
|
{
|
|
return {bufferPtr<channel>(), this->m_frames};
|
|
}
|
|
|
|
/**
|
|
* @return pointer to the buffer of the given channel.
|
|
* The size of the buffer is `frames()`.
|
|
*/
|
|
constexpr auto bufferPtr(ch_cnt_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<ch_cnt_t channel> 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[](ch_cnt_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<float>) > sizeof(PlanarBufferView<float, 2>));
|
|
static_assert(sizeof(PlanarBufferView<float, 2>) == sizeof(void**) + sizeof(f_cnt_t));
|
|
|
|
|
|
//! Concept for any audio buffer view, interleaved or planar
|
|
template<class T, typename U, ch_cnt_t channels = DynamicChannelCount>
|
|
concept AudioBufferView = SampleType<U> && (std::convertible_to<T, InterleavedBufferView<U, channels>>
|
|
|| std::convertible_to<T, PlanarBufferView<U, channels>>);
|
|
|
|
|
|
//! Converts planar buffers to interleaved buffers
|
|
template<class T, ch_cnt_t inputs, ch_cnt_t outputs>
|
|
constexpr void toInterleaved(PlanarBufferView<T, inputs> src,
|
|
InterleavedBufferView<std::remove_const_t<T>, outputs> dst)
|
|
{
|
|
assert(src.frames() == dst.frames());
|
|
if constexpr (inputs == DynamicChannelCount || outputs == DynamicChannelCount)
|
|
{
|
|
assert(src.channels() == dst.channels());
|
|
}
|
|
else { static_assert(inputs == outputs); }
|
|
|
|
for (f_cnt_t frame = 0; frame < dst.frames(); ++frame)
|
|
{
|
|
auto* framePtr = dst.framePtr(frame);
|
|
for (ch_cnt_t channel = 0; channel < dst.channels(); ++channel)
|
|
{
|
|
framePtr[channel] = src.bufferPtr(channel)[frame];
|
|
}
|
|
}
|
|
}
|
|
|
|
//! Converts interleaved buffers to planar buffers
|
|
template<class T, ch_cnt_t inputs, ch_cnt_t outputs>
|
|
constexpr void toPlanar(InterleavedBufferView<T, inputs> src,
|
|
PlanarBufferView<std::remove_const_t<T>, outputs> dst)
|
|
{
|
|
assert(src.frames() == dst.frames());
|
|
if constexpr (inputs == DynamicChannelCount || outputs == DynamicChannelCount)
|
|
{
|
|
assert(src.channels() == dst.channels());
|
|
}
|
|
else { static_assert(inputs == outputs); }
|
|
|
|
for (ch_cnt_t channel = 0; channel < dst.channels(); ++channel)
|
|
{
|
|
auto* channelPtr = dst.bufferPtr(channel);
|
|
for (f_cnt_t frame = 0; frame < dst.frames(); ++frame)
|
|
{
|
|
channelPtr[frame] = src.framePtr(frame)[channel];
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace lmms
|
|
|
|
#endif // LMMS_AUDIO_BUFFER_VIEW_H
|