Skip to content

Commit

Permalink
Various changes
Browse files Browse the repository at this point in the history
- Make new sample types strong typedefs
- Rename some types in AudioData.h
- Provide `audio_cast` methods for converting between raw audio data
pointers and pointers to the new sample types
- Append "Ref" to names of `SampleFrame` methods that return a
reference, and stop using `const float&` instead of `float`
- Improve `lmms::Span`
  • Loading branch information
messmerd committed Sep 23, 2024
1 parent 089ffb4 commit 62efd0e
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 78 deletions.
129 changes: 113 additions & 16 deletions include/AudioData.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,38 +72,135 @@ enum class AudioDataLayout
};


namespace detail {

//! Based on BOOST_STRONG_TYPEDEF
template<auto tag, typename T>
struct alignas(T) StrongTypedef
{
public:
using TagType = decltype(tag);
using Type = T;

static_assert(std::is_enum_v<TagType> || std::is_integral_v<TagType>);
static_assert(!std::is_const_v<Type>, "Use `const StrongTypedef<T>` instead of `StrongTypedef<const T>`");
static_assert(std::is_trivially_copyable_v<Type>);

static constexpr TagType Tag = tag;

constexpr StrongTypedef() = default;
constexpr /* implicit */ StrongTypedef(Type value) : m_t{value} {};
constexpr StrongTypedef(const StrongTypedef& value) = default;

constexpr operator const Type&() const { return m_t; }
constexpr operator Type&() { return m_t; }

constexpr auto operator==(StrongTypedef rhs) const noexcept -> bool { return m_t == rhs.m_t; }
constexpr auto operator<(StrongTypedef rhs) const noexcept -> bool { return m_t < rhs.m_t; }

private:
Type m_t;
};

static_assert(std::is_trivially_copyable_v<StrongTypedef<123, float>>,
"Trivially copyable for no performance penalty");
static_assert(sizeof(StrongTypedef<123, float>) == sizeof(float),
"Strong typedef must be same size as the type it wraps");
static_assert(alignof(StrongTypedef<123, float>) == alignof(float),
"Strong typedef must have same alignment as the type it wraps");

//! If `T` is const, returns `const U`, else returns `U`
template<typename T, typename U>
using copy_const_t = std::conditional_t<std::is_const_v<T>, std::add_const_t<U>, U>;

} // namespace detail


/**
* An alias for an unbounded floating point array.
* A strongly typed fundamental data type for audio that ensures
* the audio data layout is respected.
*
* Can be used as a replacement for `float*` parameters when you want to document the
* data layout of the audio data.
* Use `const SampleType<T>` for immutable data rather than `SampleType<const T>`.
* `const SampleType<T>*` and `SampleType<T>*` are intended as substitutes for `const T*` and `T*` respectively.
*/
template<AudioDataLayout layout, typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using AudioDataPtr = T*;
template<AudioDataLayout layout, typename T, std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
using SampleType = detail::StrongTypedef<layout, T>;

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

template<typename T>
using InterleavedSampleType = SampleType<AudioDataLayout::Interleaved, T>;


namespace detail {

//! Metafunction that converts a raw audio data type to a `SampleType` equivalent
template<AudioDataLayout layout, typename T, std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
using wrap_sample_t = detail::copy_const_t<T, SampleType<layout, std::remove_const_t<T>>>;

template<class T>
struct unwrap_sample;

template<AudioDataLayout layout, class U>
struct unwrap_sample<SampleType<layout, U>>
{
using type = U;
};

template<AudioDataLayout layout, class U>
struct unwrap_sample<const SampleType<layout, U>>
{
using type = const U;
};

//! Metafunction that converts `SampleType` to the corresponding raw audio data type
template<typename T>
using InterleavedAudioDataPtr = AudioDataPtr<AudioDataLayout::Interleaved, T>;
using unwrap_sample_t = typename unwrap_sample<T>::type;

} // namespace detail


/**
* A simple (samples, size) pair for storing audio data of a particular layout.
* All data is contiguous in memory.
* A span for storing audio data of a particular layout.
*
* NOTE: More information is still needed to correctly interpret this audio data:
* - For Split layout, the frame count is needed
* - For Interleaved layout, the channel count is needed
* All data is contiguous in memory.
* The size should be equal to the frame count * the channel count.
*/
template<AudioDataLayout layout, typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using AudioData = Span<T>;
template<AudioDataLayout layout, typename T>
using AudioBuffer = Span<detail::wrap_sample_t<layout, T>>;

template<typename T>
using SplitAudioData = AudioData<AudioDataLayout::Split, T>;
using SplitAudioBuffer = AudioBuffer<AudioDataLayout::Split, T>;

template<typename T>
using InterleavedAudioData = AudioData<AudioDataLayout::Interleaved, T>;
using InterleavedAudioBuffer = AudioBuffer<AudioDataLayout::Interleaved, T>;


//! Cast a raw audio samples pointer (i.e. `float*`) to `SampleType*`
template<typename T, typename S = detail::unwrap_sample_t<std::remove_pointer_t<T>>>
inline auto audio_cast(S* samples1) -> T
{
static_assert(std::is_pointer_v<T>, "T must be a pointer");
return reinterpret_cast<T>(samples1);
}

//! Cast `SampleType*` to a raw audio samples pointer (i.e. `float*`)
template<typename T, AudioDataLayout layout>
inline auto audio_cast(SampleType<layout, std::remove_pointer_t<T>>* samples) -> T
{
static_assert(std::is_pointer_v<T>, "T must be a pointer");
return reinterpret_cast<T>(samples);
}

//! Cast `const SampleType*` to a raw audio samples pointer (i.e. `const float*`)
template<typename T, AudioDataLayout layout>
inline auto audio_cast(const SampleType<layout, std::remove_const_t<std::remove_pointer_t<T>>>* samples) -> T
{
static_assert(std::is_pointer_v<T>, "T must be a pointer");
static_assert(std::is_const_v<std::remove_pointer_t<T>>, "Cannot remove const");
return reinterpret_cast<T>(samples);
}


} // namespace lmms
Expand Down
4 changes: 2 additions & 2 deletions include/DspEffectLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ namespace lmms::DspEffectLibrary
rightFX().setGain(gain);
}

void nextSample(SampleFrame & in)
void nextSample(SampleFrame& in)
{
nextSample(in.left(), in.right());
nextSample(in.leftRef(), in.rightRef());
}

void nextSample( sample_t& inLeft, sample_t& inRight )
Expand Down
4 changes: 2 additions & 2 deletions include/PluginPinConnector.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class LMMS_EXPORT PluginPinConnector
* `in` : track channels from LMMS core (currently just the main track channel pair)
* `out` : plugin input channels in Split form
*/
void routeToPlugin(f_cnt_t frames, CoreAudioData in, SplitAudioData<sample_t> out) const;
void routeToPlugin(f_cnt_t frames, CoreAudioBuffer in, SplitAudioBuffer<sample_t> out) const;

/*
* Routes audio from plugin outputs to LMMS track channels according to the plugin pin connector configuration.
Expand All @@ -128,7 +128,7 @@ class LMMS_EXPORT PluginPinConnector
* `in` : plugin output channels in Split form
* `inOut` : track channels from/to LMMS core (inplace processing)
*/
void routeFromPlugin(f_cnt_t frames, SplitAudioData<const sample_t> in, CoreAudioDataMut inOut) const;
void routeFromPlugin(f_cnt_t frames, SplitAudioBuffer<const sample_t> in, CoreAudioBufferMut inOut) const;

/**
* SerializingObject implementation
Expand Down
68 changes: 48 additions & 20 deletions include/SampleFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,42 +53,42 @@ class SampleFrame
{
}

InterleavedAudioDataPtr<sample_t> data()
InterleavedSampleType<sample_t>* data()
{
return m_samples.data();
}

InterleavedAudioDataPtr<const sample_t> data() const
const InterleavedSampleType<sample_t>* data() const
{
return m_samples.data();
}

sample_t& left()
sample_t& leftRef()
{
return m_samples[0];
}

const sample_t& left() const
sample_t left() const
{
return m_samples[0];
}

void setLeft(const sample_t& value)
void setLeft(sample_t value)
{
m_samples[0] = value;
}

sample_t& right()
sample_t& rightRef()
{
return m_samples[1];
}

const sample_t& right() const
sample_t right() const
{
return m_samples[1];
}

void setRight(const sample_t& value)
void setRight(sample_t value)
{
m_samples[1] = value;
}
Expand All @@ -98,7 +98,7 @@ class SampleFrame
return m_samples[index];
}

const sample_t& operator[](size_t index) const
sample_t operator[](size_t index) const
{
return m_samples[index];
}
Expand All @@ -110,8 +110,8 @@ class SampleFrame

SampleFrame& operator+=(const SampleFrame& other)
{
auto & l = left();
auto & r = right();
auto& l = leftRef();
auto& r = rightRef();

l += other.left();
r += other.right();
Expand Down Expand Up @@ -139,8 +139,8 @@ class SampleFrame

void operator*=(const SampleFrame& other)
{
left() *= other.left();
right() *= other.right();
leftRef() *= other.left();
rightRef() *= other.right();
}

sample_t sumOfSquaredAmplitudes() const
Expand Down Expand Up @@ -168,10 +168,10 @@ class SampleFrame

void clamp(sample_t low, sample_t high)
{
auto & l = left();
auto& l = leftRef();
l = std::clamp(l, low, high);

auto & r = right();
auto& r = rightRef();
r = std::clamp(r, low, high);
}

Expand All @@ -186,7 +186,7 @@ class SampleFrame
}

private:
std::array<sample_t, DEFAULT_CHANNELS> m_samples;
std::array<InterleavedSampleType<sample_t>, DEFAULT_CHANNELS> m_samples;
};

inline void zeroSampleFrames(SampleFrame* buffer, size_t frames)
Expand All @@ -209,7 +209,7 @@ inline SampleFrame getAbsPeakValues(SampleFrame* buffer, size_t frames)
return peaks;
}

inline void copyToSampleFrames(SampleFrame* target, InterleavedAudioDataPtr<const float> source, size_t frames)
inline void copyToSampleFrames(SampleFrame* target, const InterleavedSampleType<float>* source, size_t frames)
{
for (size_t i = 0; i < frames; ++i)
{
Expand All @@ -218,7 +218,7 @@ inline void copyToSampleFrames(SampleFrame* target, InterleavedAudioDataPtr<cons
}
}

inline void copyFromSampleFrames(InterleavedAudioDataPtr<float> target, const SampleFrame* source, size_t frames)
inline void copyFromSampleFrames(InterleavedSampleType<float>* target, const SampleFrame* source, size_t frames)
{
for (size_t i = 0; i < frames; ++i)
{
Expand All @@ -227,8 +227,36 @@ inline void copyFromSampleFrames(InterleavedAudioDataPtr<float> target, const Sa
}
}

using CoreAudioData = Span<const SampleFrame* const>;
using CoreAudioDataMut = Span<SampleFrame* const>;
using CoreAudioBuffer = Span<const SampleFrame* const>;
using CoreAudioBufferMut = Span<SampleFrame* const>;

//! Cast a raw audio samples pointer `sample_t*` to `SampleFrame*`
template<>
inline auto audio_cast(sample_t* samples) -> SampleFrame*
{
return reinterpret_cast<SampleFrame*>(samples);
}

//! Cast a raw audio samples pointer `const float*` to `const SampleFrame*`
template<>
inline auto audio_cast(const sample_t* samples) -> const SampleFrame*
{
return reinterpret_cast<const SampleFrame*>(samples);
}

//! Cast a raw audio samples pointer `InterleavedSampleType*` to `SampleFrame*`
template<>
inline auto audio_cast(InterleavedSampleType<sample_t>* samples) -> SampleFrame*
{
return reinterpret_cast<SampleFrame*>(samples);
}

//! Cast a raw audio samples pointer `const InterleavedSampleType*` to `const SampleFrame*`
template<>
inline auto audio_cast(const InterleavedSampleType<sample_t>* samples) -> const SampleFrame*
{
return reinterpret_cast<const SampleFrame*>(samples);
}

} // namespace lmms

Expand Down
Loading

0 comments on commit 62efd0e

Please sign in to comment.