Skip to content

Commit

Permalink
Plugin audio ports (#3)
Browse files Browse the repository at this point in the history
* Add PluginAudioPort class (WIP)

Refactors how the pin connector and audio buffers are incorporated into
AudioPlugin, how buffer updates occur, and how buffer customization
works.

Also uses more class type NTTP to simplify more of the template
parameters.

* Add ConfigurableAudioPort; Fix build

* Some fixes (WIP)

* Update (WIP)

* Replace AudioDataLayout with bool

* Remove SampleType

Reasons:
- It's clunky to use
- It bloats the PR
- Without a strong typedef it does nothing that can't be accomplished
with a comment

* Update tests

* Fix ZynAddSubFX when switching back to local

* Minor cleanup
  • Loading branch information
messmerd authored Jan 29, 2025
1 parent b34df28 commit eadeabf
Show file tree
Hide file tree
Showing 29 changed files with 1,122 additions and 599 deletions.
90 changes: 31 additions & 59 deletions include/AudioData.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* AudioData.h - Audio data types
*
* Copyright (c) 2024 Dalton Messmer <messmer.dalton/at/gmail.com>
* Copyright (c) 2025 Dalton Messmer <messmer.dalton/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
Expand Down Expand Up @@ -33,74 +33,42 @@
namespace lmms
{

//! Conventions for passing audio data
enum class AudioDataLayout

//! Types of audio data supported in LMMS
enum class AudioDataKind : std::uint8_t
{
/**
* Given:
* - N == Frame count
* - C == Number of channels
* - i == Sample index, where 0 <= i < N
* - `samples` has the type sample_t*
* - `samples` size == N * C
*/
SampleFrame,
F32,
// F64,
// I16,
// etc.
};

/**
* Layout where the samples for each channel are interleaved.
* i.e. "LRLRLRLR"
*
* Samples for individual channels can be accessed like this:
* - Channel #0 samples: samples[C*i]
* - Channel #1 samples: samples[C*i + 1]
* - Channel #2 samples: samples[C*i + 2]
* - Channel #3 samples: samples[C*i + 3]
* - ...
*/
Interleaved,
namespace detail {

/**
* Layout where all samples for a particular channel are grouped together.
* i.e. "LLLLRRRR"
*
* Samples for individual channels can be accessed like this:
* - Channel #0 samples: samples[i]
* - Channel #1 samples: samples[1*N + i]
* - Channel #2 samples: samples[2*N + i]
* - Channel #3 samples: samples[3*N + i]
* - ...
*/
Split
};
//! Specialize this struct to enable the use of an audio data kind
template<AudioDataKind kind> struct AudioDataType;

template<> struct AudioDataType<AudioDataKind::F32> { using type = float; };

/**
* A simple type alias for floating point audio data types which documents the data layout.
*
* For example, `const InterleavedSampleType<sample_t>*` can be used as a replacement for `const sample_t*`
* parameters in order to document that the data layout of the audio is interleaved.
*
* NOTE: Can add support for integer sample types later
*/
template<AudioDataLayout layout, typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using SampleType = T;
} // namespace detail

template<typename T>
using SplitSampleType = SampleType<AudioDataLayout::Split, T>;

template<typename T>
using InterleavedSampleType = SampleType<AudioDataLayout::Interleaved, T>;
//! Metafunction to convert `AudioDataKind` to its type
template<AudioDataKind kind>
using GetAudioDataType = typename detail::AudioDataType<kind>::type;


//! Use when the number of channels is not known at compile time
inline constexpr int DynamicChannelCount = -1;


/**
* Non-owning view for multi-channel "split" (non-interleaved) audio data
* Non-owning view for multi-channel non-interleaved audio data
*
* TODO C++23: Use std::mdspan
*/
template<typename SampleT, int channelCount = DynamicChannelCount, typename = SplitSampleType<SampleT>>
template<typename SampleT, int channelCount = DynamicChannelCount>
class SplitAudioData
{
public:
Expand All @@ -112,7 +80,7 @@ class SplitAudioData
* data[channels][frames]
* Each buffer contains `frames` frames.
*/
SplitAudioData(SplitSampleType<SampleT>* const* data, pi_ch_t channels, f_cnt_t frames)
SplitAudioData(SampleT* const* data, pi_ch_t channels, f_cnt_t frames)
: m_data{data}
, m_channels{channels}
, m_frames{frames}
Expand All @@ -133,17 +101,19 @@ class SplitAudioData
* Returns pointer to the buffer of a given channel.
* The size of the buffer is `frames()`.
*/
auto buffer(pi_ch_t channel) const -> SplitSampleType<SampleT>*
auto buffer(pi_ch_t channel) const -> SampleT*
{
assert(channel < m_channels);
assert(m_data != nullptr);
return m_data[channel];
}

template<pi_ch_t channel>
auto buffer() const -> SplitSampleType<SampleT>*
auto buffer() const -> SampleT*
{
static_assert(channel != DynamicChannelCount);
static_assert(channel < channelCount);
assert(m_data != nullptr);
return m_data[channel];
}

Expand All @@ -161,21 +131,23 @@ class SplitAudioData

auto frames() const -> f_cnt_t { return m_frames; }

auto empty() const -> bool { return !m_data || m_channels == 0 || m_frames == 0; }

/**
* WARNING: This method assumes that internally there is a single
* contiguous buffer for all channels whose size is channels() * frames().
* Whether this is true depends on the implementation of the source buffer.
*/
auto sourceBuffer() const -> SplitSampleType<SampleT>*
auto sourceBuffer() const -> Span<SampleT>
{
assert(m_data != nullptr);
return m_data[0];
return Span<SampleT>{m_data[0], channels() * frames()};
}

auto data() const -> SplitSampleType<SampleT>* const* { return m_data; }
auto data() const -> SampleT* const* { return m_data; }

private:
SplitSampleType<SampleT>* const* m_data = nullptr;
SampleT* const* m_data = nullptr;
pi_ch_t m_channels = 0;
f_cnt_t m_frames = 0;
};
Expand Down
Loading

0 comments on commit eadeabf

Please sign in to comment.