diff --git a/sfizz/AudioSpan.h b/sfizz/AudioSpan.h index b47a147e..e8290edd 100644 --- a/sfizz/AudioSpan.h +++ b/sfizz/AudioSpan.h @@ -34,6 +34,58 @@ namespace sfz { +/** + * @brief Extension of the concept of spans to multiple channels. + * + * A span (and by extension an audiospan) is at its core a structure + * containing a pointer and a size to a buffer that is owned by another + * object. A span is thus a view into a buffer that is cheap to copy and + * pass around, and safe as long as the underlying buffer is allocated. + * The goal of the class is to reduce interfaces and usage annoyance for + * codebases. Obviously, this requires that most functions use AudioSpans. + * It also protects against overreading a buffer. Users can still indicate + * that they + * @code{.cpp} + * constexpr int bufferSize { 1024 }; + * void gain(AudioSpan arrayView, float gain) + * { + * for (auto& f: arrayView) + * f *= gain; + * } + * + * int main(char argc, char** argv) + * { + * float leftChannel [bufferSize]; + * float rightChannel [bufferSize]; + * + * for (int i = 0; i < bufferSize; ++i) + * { + * leftChannel[i] = 1.0f; + * rightChannel[i] = 1.0f; + * } + * + * // Type is inferred + * AudioSpan explicitView { { leftChannel, rightChannel }, bufferSize }; + * // Size will be taken as the minimum size of all the spans given + * // for all types that can be automatically cast to absl::Span or std::span + * AudioSpan explicitView2 { { leftChannel, rightChannel } }; + * + * gain(explicitView, 0.5f); // the array elements are now equal to 0.5f + * gain(explicitView2, 0.5f); // the array elements are now equal to 0.25f + * + * // You can also build spans implicitely + * gain({ { leftChannel, rightChannel }, bufferSize }, 0.5f); // elements equal to 0.125f + * } + * @endcode + * You can build AudioSpans from AudioBuffers directly, the AudioBufferT.cpp file in the test + * folder show some example. + * As with many things templated in C++ the `Type` can be`const` or `volatile`, and `const float` + * is not the same type as `float`. You thus cannot build an `AudioSpan` from + * a `const float *` buffer for example. + * + * @tparam Type the underlying buffer type + * @tparam MaxChannels the maximum number of channels. Defaults to sfz::config::numChannels + */ template class AudioSpan { public: @@ -42,8 +94,16 @@ public: { } - AudioSpan(const std::array& spans, int numChannels, size_type offset, size_type size) - : numFrames(size) + /** + * @brief Construct a new Audio Span object + * + * @param spans an array of MaxChannels pointers to buffers. + * @param numChannels the number of spans to take in from the array + * @param offset starting offset for the AudioSpan + * @param numFrames size of the AudioSpan + */ + AudioSpan(const std::array& spans, int numChannels, size_type offset, size_type numFrames) + : numFrames(numFrames) , numChannels(numChannels) { ASSERT(static_cast(numChannels) <= MaxChannels); @@ -51,6 +111,12 @@ public: this->spans[i] = spans[i] + offset; } + /** + * @brief Construct a new Audio Span object from initializer lists + * + * @param spans the list of span + * @param numFrames the size of the audio span + */ AudioSpan(std::initializer_list spans, size_type numFrames) : numFrames(numFrames) , numChannels(spans.size()) @@ -65,6 +131,15 @@ public: } } + /** + * @brief Construct a new Audio Span object from a list of absl::Span + * + * This constructor can be implicitely called for any source that can be cast transparently to + * an absl::Span. The size of the AudioSpan is inferred from the size of the smallest + * absl::Span. + * + * @param spans a list of objects compatible with absl::Span + */ AudioSpan(std::initializer_list> spans) : numChannels(spans.size()) { @@ -78,6 +153,17 @@ public: } } + /** + * @brief Construct a new Audio Span object from an AudioBuffer with a const Type. + * + * This constructor can be implicitely called for any source that can be cast transparently to + * an AudioBuffer. + * + * @tparam U the underlying type compatible with the template Type of the AudioSpan + * @tparam N the number of channels in the AudioBuffer + * @tparam Alignment the alignment block size for the platform + * @param audioBuffer the source AudioBuffer. + */ template , typename = std::enable_if_t::value, int>> AudioSpan(AudioBuffer& audioBuffer) : numFrames(audioBuffer.getNumFrames()) @@ -87,6 +173,18 @@ public: this->spans[i] = audioBuffer.channelReader(i); } } + + /** + * @brief Construct a new Audio Span object from an AudioBuffer with a non-const Type. + * + * This constructor can be implicitely called for any source that can be cast transparently to + * an AudioBuffer. + * + * @tparam U the underlying type compatible with the template Type of the AudioSpan + * @tparam N the number of channels in the AudioBuffer + * @tparam Alignment the alignment block size for the platform + * @param audioBuffer the source AudioBuffer. + */ template > AudioSpan(AudioBuffer& audioBuffer) : numFrames(audioBuffer.getNumFrames()) @@ -96,6 +194,12 @@ public: this->spans[i] = audioBuffer.channelWriter(i); } } + + /** + * @brief AudioSpan copy constructor + * + * @param other the other AudioSpan + */ template > AudioSpan(const AudioSpan& other) : numFrames(other.getNumFrames()) @@ -106,6 +210,12 @@ public: } } + /** + * @brief Get a raw pointer to a specific channel from the AudioSpan + * + * @param channelIndex the channel + * @return Type* the raw pointer to the channel + */ Type* getChannel(int channelIndex) { ASSERT(channelIndex < numChannels); @@ -115,6 +225,12 @@ public: return {}; } + /** + * @brief Get a Span corresponding to a specific channel + * + * @param channelIndex the channel + * @return absl::Span + */ absl::Span getSpan(int channelIndex) { ASSERT(channelIndex < numChannels); @@ -124,6 +240,12 @@ public: return {}; } + /** + * @brief Get a Span corresponding to a specific channel + * + * @param channelIndex the channel + * @return absl::Span + */ absl::Span getConstSpan(int channelIndex) { ASSERT(channelIndex < numChannels); @@ -133,6 +255,11 @@ public: return {}; } + /** + * @brief Get the mean of the squared values of the AudioSpan elements on all channels. + * + * @return Type + */ Type meanSquared() noexcept { if (numChannels == 0) @@ -143,27 +270,52 @@ public: return result / numChannels; } + /** + * @brief Fills all the elements of the AudioSpan with the same value + * + * @param value the filling value + */ void fill(Type value) noexcept { + static_assert(!std::is_const_v, "Can't allow mutating operations on const AudioSpans"); for (int i = 0; i < numChannels; ++i) sfz::fill(getSpan(i), value); } + /** + * @brief Apply a gain span elementwise to all channels in the AudioSpan. + * + * @param gain the gain to apply + */ void applyGain(absl::Span gain) noexcept { + static_assert(!std::is_const_v, "Can't allow mutating operations on const AudioSpans"); for (int i = 0; i < numChannels; ++i) sfz::applyGain(gain, getSpan(i)); } + /** + * @brief Apply a gain to all channels in the AudioSpan. + * + * @param gain the gain to apply + */ void applyGain(Type gain) noexcept { + static_assert(!std::is_const_v, "Can't allow mutating operations on const AudioSpans"); for (int i = 0; i < numChannels; ++i) sfz::applyGain(gain, getSpan(i)); } + /** + * @brief Add another AudioSpan with a compatible number of channels to the current + * AudioSpan. + * + * @param other the other AudioSpan + */ template > void add(AudioSpan& other) { + static_assert(!std::is_const_v, "Can't allow mutating operations on const AudioSpans"); ASSERT(other.getNumChannels() == numChannels); if (other.getNumChannels() == numChannels) { for (int i = 0; i < numChannels; ++i) @@ -171,9 +323,16 @@ public: } } + /** + * @brief Copy the elements of another AudioSpan with a compatible number of channels + * to the current AudioSpan. + * + * @param other the other AudioSpan + */ template > void copy(AudioSpan& other) { + static_assert(!std::is_const_v, "Can't allow mutating operations on const AudioSpans"); ASSERT(other.getNumChannels() == numChannels); if (other.getNumChannels() == numChannels) { for (int i = 0; i < numChannels; ++i) @@ -181,34 +340,69 @@ public: } } + /** + * @brief Get the size of this AudioSpan. + * + * @returns size_type the number of frames in the AudioSpan + */ size_type getNumFrames() { return numFrames; } + /** + * @brief Get the number of channels of this AudioSpan. + * + * @returns size_type the number of channels in the AudioSpan + */ int getNumChannels() { return numChannels; } - + + /** + * @brief Creates a new AudioSpan but with only the `length` first elements of each channel. + * + * @param length the number of elements to take on each channel + */ AudioSpan first(size_type length) { ASSERT(length <= numFrames); return { spans, numChannels, 0, length }; } + /** + * @brief Creates a new AudioSpan but with only the `length` last elements of each channel. + * + * @param length the number of elements to take on each channel + */ AudioSpan last(size_type length) { ASSERT(length <= numFrames); return { spans, numChannels, numFrames - length, length }; } + /** + * @brief Creates a new AudioSpan starting at an offset `offset` on each channel and + * taking `length` elements. The new AudioSpan will have `length` elements. This basically + * removes the first `offset` elements and the last `numFrames - length - offset` elements + * from the AudioSpan. + * + * @param length the number of elements to take on each channel + */ AudioSpan subspan(size_type offset, size_type length) { ASSERT(length + offset <= numFrames); return { spans, numChannels, offset, length }; } + /** + * @brief Creates a new AudioSpan starting at an offset `offset` on each channel and + * taking all the remaining elements. This function basically removes the first `offset` + * elements from the AudioSpan. The new Audiospan will have size `numFrames - offset`. + * + * @param length the number of elements to take on each channel + */ AudioSpan subspan(size_type offset) { ASSERT(offset <= numFrames);