HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_audio_buffer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // max_element(), copy(), fill()
4#include <cmath> // isnan(), abs()
5#include <utility> // pair, make_pair()
6#include <vector>
7
9#include "hart_precision.hpp" // hzPrecision
11#include "hart_slice.hpp"
12#include "hart_utils.hpp" // nan(), floatsEqual(), roundToSizeT()
13
14/// @defgroup DataStructures Data Structures
15/// @brief Custom data structures and containers
16
17namespace hart {
18
19template <typename T> class DSPBase;
20template <typename T> class SignalBase;
21
22/// @brief Container for audio data
23/// @note This class owns a memory block with audio samples - so it's a container, not a view. Treat it like a heavyweight object.
24/// @ingroup DataStructures
25template <typename SampleType>
27{
28public:
29 /// @brief Creates an audio buffer
30 /// @param numChannels Initial number of channels
31 /// @param numFrames Numbers of frames (samples) to be allocated in each channel
32 /// @param sampleRateHz Metadata for sample rate (in Hz) in which the data whould be interpreted, whenever applicable
33 AudioBuffer (size_t numChannels = 0, size_t numFrames = 0, double sampleRateHz = nan<double>()) :
34 m_numChannels (numChannels),
35 m_numFrames (numFrames),
36 m_sampleRateHz (sampleRateHz),
37 m_frames (m_numChannels * m_numFrames),
38 m_channelPointers (m_numChannels)
39 {
40 updateChannelPointers();
41 }
42
43 /// @brief Creates an audio buffer by copying
44 AudioBuffer (const AudioBuffer& other) :
45 m_numChannels (other.m_numChannels),
46 m_numFrames (other.m_numFrames),
47 m_sampleRateHz (other.m_sampleRateHz),
48 m_frames (other.m_frames),
49 m_channelPointers (m_numChannels)
50 {
51 updateChannelPointers();
52 }
53
54 /// @brief Creates an audio buffer by moving
55 AudioBuffer (AudioBuffer&& other) noexcept :
56 m_numChannels (other.m_numChannels),
57 m_numFrames (other.m_numFrames),
58 m_sampleRateHz (other.m_sampleRateHz),
59 m_frames (std::move (other.m_frames)),
60 m_channelPointers (std::move (other.m_channelPointers))
61 {
62 other.erase();
63 }
64
65 /// @brief The destructor
66 ~AudioBuffer() = default;
67
68 /// @brief Creates an audio buffer by copy-assigning
70 {
71 if (this == &other)
72 return *this;
73
74 if (m_numChannels != other.m_numChannels)
75 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Can't copy from a buffer with different number of channels", *this);
76
77 m_numFrames = other.m_numFrames;
78 m_sampleRateHz = other.m_sampleRateHz;
79 m_frames = other.m_frames;
80 m_channelPointers.resize (m_numChannels);
81 updateChannelPointers();
82
83 return *this;
84 }
85
86 /// @brief Creates an audio buffer by move-assigning
87 AudioBuffer& operator= (AudioBuffer&& other) noexcept
88 {
89 if (this == &other)
90 return *this;
91
92 m_numChannels = other.m_numChannels;
93 m_numFrames = other.m_numFrames;
94 m_sampleRateHz = other.m_sampleRateHz;
95 m_frames = std::move (other.m_frames);
96 m_channelPointers = std::move (other.m_channelPointers);
97 other.erase();
98
99 return *this;
100 }
101
102 /// @brief Gets a raw pointer to the read-only audio data
103 /// @return A pointer to an array of per-channel read pointers.
104 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
105 const SampleType* const* getArrayOfReadPointers() const
106 {
107 return static_cast<const SampleType* const*> (m_channelPointers.data());
108 }
109
110 /// @brief Gets a raw pointer to the mutable audio data
111 /// @return A pointer to an array of per-channel write pointers.
112 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
113 SampleType* const* getArrayOfWritePointers()
114 {
115 return m_channelPointers.data();
116 }
117
118 /// @brief Creates an empty audio buffer with the same number of channels, frames and sample rate as the `other` buffer.
119 /// @note The data from the other buffer will not be copied. Newly allocated samples will be value-initialized.
120 /// @param other Reference buffer
121 static AudioBuffer emptyLike (const AudioBuffer& other)
122 {
123 return other.hasSampleRate()
124 ? AudioBuffer (other.getNumChannels(), other.getNumFrames(), other.getSampleRateHz())
125 : AudioBuffer (other.getNumChannels(), other.getNumFrames());
126 }
127
128 /// @brief Get number of channels
129 /// @return Number of allocated channels
130 size_t getNumChannels() const { return m_numChannels; }
131
132 /// @brief Get number of frames (samples)
133 /// @return Number of allocated frames (samples) in every channel
134 size_t getNumFrames() const { return m_numFrames; }
135
136 /// @brief Resizes the buffer to hold a new number of frames per channel
137 /// @details Resizing behaviour:
138 /// - If newNumFrames == current size, it won't do anything.
139 /// - If newNumFrames > current size, it will append silence (zeros) at the end.
140 /// - If newNumFrames < current size: it will just truncate, discarding samples from the end.
141 /// Will always preserve existing channels and sample-rate metadata.
142 /// @attention May cause reallocation, thus invalidating previous raw pointers!
143 /// @param newNumFrames New number of frames per channel
144 void setNumFrames (size_t newNumFrames)
145 {
146 if (newNumFrames == m_numFrames)
147 return;
148
149 const size_t oldTotalSamples = m_frames.size();
150 const size_t newTotalSamples = m_numChannels * newNumFrames;
151
152 if (newNumFrames < m_numFrames)
153 {
154 m_frames.resize (newTotalSamples);
155 m_numFrames = newNumFrames;
156 updateChannelPointers();
157 return;
158 }
159
160 if (newTotalSamples > oldTotalSamples)
161 {
162 m_frames.resize (newTotalSamples);
163 std::fill (
164 m_frames.begin() + oldTotalSamples,
165 m_frames.end(),
166 SampleType (0)
167 );
168 }
169
170 m_numFrames = newNumFrames;
171 updateChannelPointers();
172 }
173
174 /// @brief Check if a specific sample rate was assigned to the audio buffer
175 /// @note Uninitialized buffers, as well as some specific Signal types, will not have a specific sample rate.
176 /// @return `true` if there is a specific sample rate value, `false` otherwise
177 bool hasSampleRate() const
178 {
179 return ! std::isnan (m_sampleRateHz);
180 }
181
182 /// @brief Get a sample rate metadata
183 /// @note Call `hasSampleRate()` if you're not sure if the buffer has a specific sample rate
184 /// @return Buffer's sample rate in Hz
185 double getSampleRateHz() const
186 {
187 hassert (! std::isnan (m_sampleRateHz)); // Call hasSampleRate() first! This buffer doesn't have a specific sample rate.
188 return m_sampleRateHz;
189 }
190
191 /// @brief Get a duration of the audio buffer
192 /// @note Before calling it, it's a good idea to call `hasSampleRate()` to check if the buffer has a specific sample rate, otherwise the duration is unknown.
193 /// @return Duration of the audio buffer in seconds
194 double getLengthSeconds() const
195 {
196 if (m_numFrames == 0)
197 return 0.0;
198
199 hassert (! std::isnan (m_sampleRateHz)); // This buffer doesn't have a specific sample rate, and hence the length is unknown
200 hassert (! floatsEqual (m_sampleRateHz, 0.0));
201 return static_cast <double> (m_numFrames) / m_sampleRateHz;
202 }
203
204 /// @brief Get a raw pointer to a specific channel's mutable audio data
205 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
206 /// @return Pointer to the audio data
207 SampleType* operator[] (size_t channel)
208 {
209 return m_channelPointers[channel];
210 }
211
212 /// @brief Get a raw pointer to a specific channel's read-only audio data
213 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
214 /// @return Pointer to the audio data
215 const SampleType* operator[] (size_t channel) const
216 {
217 return m_channelPointers[channel];
218 }
219
220 /// @brief Checks whether this buffer contains approximately the same audio as another buffer
221 /// @details The comparison is relaxed - each pair of corresponding samples must differ by
222 /// no more than `toleranceLinear`, but not bit-exact. Both buffers must have the same number
223 /// of channels and frames. Also, if both buffers have sample-rate metadata, the sample rates
224 /// must match. If both buffers have no sample-rate metadata assigned, they will still be
225 /// considered equal as long as their dimensions match, and sample values match within tolerance.
226 /// @param other Buffer to compare against
227 /// @param toleranceLinear Absolute sample tolerance in linear domain (not decibels)
228 /// @return `true` if the buffers are equal within the specified tolerance, `false` otherwise
229 bool equalsTo (const AudioBuffer& other, SampleType toleranceLinear = (SampleType) 1e-6) const
230 {
231 if (m_numChannels != other.m_numChannels || m_numFrames != other.m_numFrames)
232 return false;
233
234 if (hasSampleRate() && other.hasSampleRate() && ! floatsEqual (m_sampleRateHz, other.m_sampleRateHz))
235 return false;
236
237 const SampleType* thisRawData = m_frames.data();
238 const SampleType* otherRawData = other.m_frames.data();
239 const size_t numFlattenedSamples = m_frames.size();
240
241 for (size_t sample = 0; sample < numFlattenedSamples; ++sample)
242 if (std::abs (thisRawData[sample] - otherRawData[sample]) > toleranceLinear)
243 return false;
244
245 return true;
246 }
247
248 /// @brief Checks whether two buffers contain approximately the same audio
249 /// @details Equivalent to calling `AudioBuffer::equalsTo (other)` with the default tolerance
250 /// @param other Buffer to compare against
251 /// @return `true` if the buffers are equal within the default tolerance, `false` otherwise
252 bool operator== (const AudioBuffer& other) const
253 {
254 return equalsTo (other);
255 }
256
257 /// @brief Checks whether two buffers differ beyond the default comparison tolerance
258 /// @details Equivalent to `! AudioBuffer::equalsTo (other)`
259 /// @param other Buffer to compare against
260 /// @return `true` if the buffers are not equal within the default tolerance, `false` otherwise
261 bool operator!= (const AudioBuffer& other) const
262 {
263 return ! equalsTo (other);
264 }
265
266 /// @brief Appends data from another buffer
267 /// @warning This operation will resize the current buffer and potentially invalidate the previous raw data pointers returned by
268 /// `getArrayOfReadPointers()`, `getArrayOfWritePointers()` and the "`[]`" operator, so make sure to keep those external pointers up to date.
269 /// @param otherBuffer A buffer to append from
270 void appendFrom (const AudioBuffer<SampleType>& otherBuffer)
271 {
272 if (otherBuffer.getNumChannels() != m_numChannels)
273 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, "Channel count mismatch");
274
275 // Not a huge deal, but appending a buffer with a different SR must be at least a little suspicious
276 hassert ((! hasSampleRate() || ! otherBuffer.hasSampleRate()) || floatsEqual (getSampleRateHz(), otherBuffer.getSampleRateHz()));
277
278 const size_t thisNumFrames = m_numFrames;
279 const size_t otherNumFrames = otherBuffer.getNumFrames();
280
281 std::vector<SampleType> combinedFrames (m_numChannels * (thisNumFrames + otherNumFrames));
282
283 for (size_t channel = 0; channel < m_numChannels; ++channel)
284 {
285 SampleType* newChannelStart = &combinedFrames[channel * (thisNumFrames + otherNumFrames)];
286 std::copy (m_channelPointers[channel], m_channelPointers[channel] + thisNumFrames, newChannelStart);
287 std::copy (otherBuffer[channel], otherBuffer[channel] + otherNumFrames, newChannelStart + thisNumFrames);
288 }
289
290 m_frames = std::move (combinedFrames);
291 m_numFrames += otherNumFrames;
292
293 updateChannelPointers();
294 }
295
296 /// @brief Clears the buffer
297 /// @details The number of frames after this operation will be zero, but channel number will persist.
298 void erase()
299 {
300 // Keeping the sample rate though, just wiping the data
301
302 m_numFrames = 0;
303 m_frames.clear();
304
305 // If m_channelPointers was std::move'd, its size will be zero
306 if (m_channelPointers.size() != m_numChannels)
307 m_channelPointers.resize (m_numChannels);
308
309 updateChannelPointers();
310 }
311
312 /// @brief Get the maximum absolute value in the buffer in a specific channel
313 /// @param channel Channel of which the magnitude should be measured
314 /// @param startFrame The beginning of the range of the frames to look for magnitude
315 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
316 /// @return The maximum value, in linear domain (i.e. not decibels)
317 SampleType getMagnitude (size_t channel, size_t startFrame, size_t numFrames) const
318 {
319 if (channel >= m_numChannels)
320 HART_THROW_OR_RETURN (hart::IndexError, "Invalid channel", (SampleType) 0);
321
322 if (startFrame + numFrames > m_numFrames || numFrames == 0)
323 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
324
325 const SampleType* start = m_channelPointers[channel] + startFrame;
326 const SampleType* peakSample = std::max_element (
327 start,
328 start + numFrames,
329 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
330 );
331
332 return std::abs (*peakSample);
333 }
334
335 /// @brief Get the maximum absolute value in the buffer across all channels
336 /// @param startFrame The beginning of the range of the frames to look for magnitude
337 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
338 /// @return The maximum value, in linear domain (i.e. not decibels)
339 SampleType getMagnitude (size_t startFrame, size_t numFrames) const
340 {
341 if (startFrame + numFrames > m_numFrames || numFrames == 0)
342 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
343
344 SampleType peakSampleAcrossAllChannels = (SampleType) 0;
345
346 for (size_t channel = 0; channel < m_numChannels; ++channel)
347 {
348 const SampleType* start = m_channelPointers[channel] + startFrame;
349 const SampleType* peakSample = std::max_element (
350 start,
351 start + numFrames,
352 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
353 );
354 peakSampleAcrossAllChannels = std::max (peakSampleAcrossAllChannels, std::abs (*peakSample));
355 }
356
357 return peakSampleAcrossAllChannels;
358 }
359
360 /// @brief Copies audio from another buffer
361 /// @param destChannel Channel within this buffer to copy the frames to
362 /// @param destStartFrame Start frame within this buffer's channel
363 /// @param source Source buffer to read from
364 /// @param sourceChannel Channel within the source buffer to read from
365 /// @param sourceStartFrame Offset within the source buffer's channel to start reading frames from
366 /// @param numFrames Number of frames to copy
367 void copyFrom (size_t destChannel, size_t destStartFrame, const AudioBuffer& source, size_t sourceChannel, size_t sourceStartFrame, size_t numFrames)
368 {
369 if (destChannel >= m_numChannels || sourceChannel >= source.m_numChannels)
370 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
371
372 if (destStartFrame + numFrames > m_numFrames || sourceStartFrame + numFrames > source.m_numFrames)
373 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
374
375 // Not a huge deal, but copying from a buffer with different SR must be at least a little suspicious
376 hassert ((! hasSampleRate() || ! source.hasSampleRate()) || floatsEqual (getSampleRateHz(), source.getSampleRateHz()));
377
378 std::copy (
379 source.m_channelPointers[sourceChannel] + sourceStartFrame,
380 source.m_channelPointers[sourceChannel] + sourceStartFrame + numFrames,
381 m_channelPointers[destChannel] + destStartFrame
382 );
383 }
384
385 /// @brief Copies audio from another generic audio buffer
386 /// @param destChannel Channel within this buffer to copy the frames to
387 /// @param destStartFrame Start frame within this buffer's channel
388 /// @param source Pointer to the source sample data, must contain at least `numFrames` samples
389 /// @param numFrames Number of frames to copy
390 void copyFrom (size_t destChannel, size_t destStartFrame, const SampleType* source, size_t numFrames)
391 {
392 if (destChannel >= m_numChannels)
393 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid destination channel");
394
395 if (destStartFrame + numFrames > m_numFrames)
396 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
397
398 std::copy (source, source + numFrames, m_channelPointers[destChannel] + destStartFrame);
399 }
400
401 /// @brief Clears the entire buffer
402 /// @details Sets all frames in all channels to zeros, keeping the sample rate value intact
403 void clear()
404 {
405 std::fill (m_frames.begin(), m_frames.end(), (SampleType) 0);
406 }
407
408 /// @brief Clears a specific section of a given channel
409 /// @details Overwrites a selected section of the channel with zeros
410 /// @param channel Channel in which to clear a frame range
411 /// @param startFrame Start of the frame range to clear (inclusive)
412 /// @param numFrames Amount of frames to clear
413 void clear (size_t channel, size_t startFrame, size_t numFrames)
414 {
415 if (channel >= m_numChannels)
416 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
417
418 if (startFrame + numFrames > m_numFrames)
419 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
420
421 std::fill (m_channelPointers[channel], m_channelPointers[channel] + numFrames, (SampleType) 0);
422 }
423
424 /// @brief Prints readable representation of the audio buffer
425 /// @param stream String stream to append the representation to
426 void represent (std::ostream& stream) const
427 {
428 stream << "AudioBuffer (" << m_numChannels << ", " << m_numFrames;
429
430 if (hasSampleRate())
431 stream << ", " << hzPrecision << m_sampleRateHz << "_Hz)";
432 else
433 stream << ", nan())";
434 }
435
436 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
437 /// @details
438 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
439 /// The Signal is not consumed and can be re-used after rendering completes.
440 ///
441 /// The buffer's sample rate and channel layout are used as the hosting configuration.
442 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
443 ///
444 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
445 /// The final block may be shorter than the requested block size.
446 /// @param signal A reusable Signal object to render from.
447 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
448 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
449 /// @return A reference to this AudioBuffer for chaining further operations.
450 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
451 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
452 /// is unsupported by the Signal
453 /// @throws hart::SizeError is this buffer has no frames allocated
454 AudioBuffer<SampleType>& fillWith (SignalBase<SampleType>& signal, size_t blockSizeFrames = 0, Preparation signalPreparation = Preparation::resetAndPrepare) &
455 {
456 _fillWith (signal, blockSizeFrames, signalPreparation);
457 return *this;
458 }
459
460 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
461 /// @details
462 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
463 ///
464 /// The buffer's sample rate and channel layout are used as the hosting configuration.
465 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
466 ///
467 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
468 /// The final block may be shorter than the requested block size.
469 /// @param signal A temporary Signal instance to render from.
470 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
471 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
472 /// @return A reference to this AudioBuffer for chaining further operations.
473 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
474 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
475 /// is unsupported by the Signal
476 /// @throws hart::SizeError is this buffer has no frames allocated
477 AudioBuffer<SampleType>& fillWith (SignalBase<SampleType>&& signal, size_t blockSizeFrames = 0, Preparation signalPreparation = Preparation::resetAndPrepare) &
478 {
479 _fillWith (signal, blockSizeFrames, signalPreparation);
480 return *this;
481 }
482
483 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
484 /// @details
485 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
486 /// The Signal is not consumed and can be re-used after rendering completes.
487 ///
488 /// The buffer's sample rate and channel layout are used as the hosting configuration.
489 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
490 ///
491 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
492 /// The final block may be shorter than the requested block size.
493 /// @param signal A reusable Signal object to render from.
494 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
495 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
496 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
497 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
498 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
499 /// is unsupported by the Signal
500 /// @throws hart::SizeError is this buffer has no frames allocated
501 AudioBuffer<SampleType>&& fillWith (SignalBase<SampleType>& signal, size_t blockSizeFrames = 0, Preparation signalPreparation = Preparation::resetAndPrepare) &&
502 {
503 _fillWith (signal, blockSizeFrames, signalPreparation);
504 return std::move (*this);
505 }
506
507 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
508 /// @details
509 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
510 ///
511 /// The buffer's sample rate and channel layout are used as the hosting configuration.
512 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
513 ///
514 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
515 /// The final block may be shorter than the requested block size.
516 /// @param signal A temporary Signal instance to render from.
517 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
518 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
519 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
520 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
521 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
522 /// is unsupported by the Signal
523 /// @throws hart::SizeError is this buffer has no frames allocated
524 AudioBuffer<SampleType>&& fillWith (SignalBase<SampleType>&& signal, size_t blockSizeFrames = 0, Preparation signalPreparation = Preparation::resetAndPrepare) &&
525 {
526 _fillWith (signal, blockSizeFrames, signalPreparation);
527 return std::move (*this);
528 }
529
530 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
531 /// @details
532 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
533 /// The DSP is not consumed and can be re-used after rendering completes.
534 ///
535 /// The buffer's sample rate and channel layout are used as the hosting configuration.
536 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
537 ///
538 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
539 /// The final block may be shorter than the requested block size.
540 /// @param signal A reusable hart::DSP object to render from
541 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go
542 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
543 /// @return A reference to this AudioBuffer for chaining further operations
544 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
545 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
546 /// is unsupported by the DSP object
547 /// @throws hart::SizeError is this buffer has no frames allocated
548 AudioBuffer<SampleType>& processWith (DSPBase<SampleType>& dsp, size_t blockSizeFrames = 0, Preparation dspPreparation = Preparation::resetAndPrepare) &
549 {
550 _processWith (dsp, blockSizeFrames, dspPreparation);
551 return *this;
552 }
553
554 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
555 /// @details
556 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples
557 ///
558 /// The buffer's sample rate and channel layout are used as the hosting configuration.
559 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
560 ///
561 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length
562 /// The final block may be shorter than the requested block size
563 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
564 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
565 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
566 /// @return A reference to this AudioBuffer for chaining further operations
567 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
568 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
569 /// is unsupported by the DSP object
570 /// @throws hart::SizeError is this buffer has no frames allocated
571 AudioBuffer<SampleType>& processWith (DSPBase<SampleType>&& dsp, size_t blockSizeFrames = 0, Preparation dspPreparation = Preparation::resetAndPrepare) &
572 {
573 _processWith (dsp, blockSizeFrames, dspPreparation);
574 return *this;
575 }
576
577 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
578 /// @details
579 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
580 /// The DSP is not consumed and can be re-used after rendering completes.
581 ///
582 /// The buffer's sample rate and channel layout are used as the hosting configuration.
583 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
584 ///
585 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
586 /// The final block may be shorter than the requested block size.
587 /// @param signal A reusable hart::DSP object to render from
588 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
589 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering.
590 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
591 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
592 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
593 /// is unsupported by the DSP object
594 /// @throws hart::SizeError is this buffer has no frames allocated
595 AudioBuffer<SampleType>&& processWith (DSPBase<SampleType>& dsp, size_t blockSizeFrames = 0, Preparation dspPreparation = Preparation::resetAndPrepare) &&
596 {
597 _processWith (dsp, blockSizeFrames, dspPreparation);
598 return std::move (*this);
599 }
600
601 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
602 /// @details
603 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
604 ///
605 /// The buffer's sample rate and channel layout are used as the hosting configuration.
606 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
607 ///
608 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
609 /// The final block may be shorter than the requested block size.
610 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
611 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
612 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
613 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
614 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
615 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
616 /// is unsupported by the DSP object
617 /// @throws hart::SizeError is this buffer has no frames allocated
618 AudioBuffer<SampleType>&& processWith (DSPBase<SampleType>&& dsp, size_t blockSizeFrames = 0, Preparation dspPreparation = Preparation::resetAndPrepare) &&
619 {
620 _processWith (dsp, blockSizeFrames, dspPreparation);
621 return std::move (*this);
622 }
623
624 /// @brief Returns a pair of indices representing a provided slice
625 /// @param slice A Slice instance. Valid slice types are `Slice::Type::whole`
626 /// `Slice::Type::frames` and `Slice::Type::time`
627 /// @retval first Index representing the beginning of the range, inclusive
628 /// @retval second Index representing the end of the range, non-inclusive
629 std::pair<size_t, size_t> getFrameIndices (const Slice& slice) const
630 {
631 const size_t numFrames = getNumFrames();
632
633 switch (slice.type)
634 {
636 {
637 return {0, numFrames};
638 }
639
641 {
642 const size_t startFrame = static_cast<size_t> (slice.start);
643 const size_t stopFrame = static_cast<size_t> (slice.stop);
644
645 return {
646 std::min (startFrame, numFrames),
647 std::min (stopFrame, numFrames)
648 };
649 }
650
652 {
653 const double sampleRateHz = getSampleRateHz();
654 const size_t startFrame = roundToSizeT (slice.start * sampleRateHz);
655 const size_t stopFrame = roundToSizeT (slice.stop * sampleRateHz);
656
657 return {
658 std::min (startFrame, numFrames),
659 std::min (stopFrame, numFrames)
660 };
661 }
662
663 default:
664 {
665 HART_THROW_OR_RETURN (hart::UnitError, "Slice type cannot be interpreted as frame range", std::make_pair (0, numFrames));
666 }
667 }
668 }
669
670private:
671 size_t m_numChannels = 0;
672 size_t m_numFrames = 0;
673 double m_sampleRateHz = nan<double>();
674 std::vector<SampleType> m_frames;
675 std::vector<SampleType*> m_channelPointers;
676
677 void updateChannelPointers()
678 {
679 for (size_t channel = 0; channel < m_numChannels; ++channel)
680 m_channelPointers[channel] = m_numFrames > 0 ? &m_frames[channel * m_numFrames] : nullptr;
681 }
682
683 /// @brief Prints readable text representation of the AudioBuffer object into the I/O stream
684 /// @relates AudioBuffer
685 friend std::ostream& operator<< (std::ostream& stream, const AudioBuffer& audioBuffer)
686 {
687 audioBuffer.represent (stream);
688 return stream;
689 }
690
691 void _processWith (DSPBase<SampleType>& dsp, size_t blockSizeFrames, Preparation dspPreparation);
692 void _fillWith (SignalBase<SampleType>& signal, size_t blockSizeFrames, Preparation signalPreparation);
693};
694
695} // namespace hart
696
Container for audio data.
void clear(size_t channel, size_t startFrame, size_t numFrames)
Clears a specific section of a given channel.
AudioBuffer< SampleType > && processWith(DSPBase< SampleType > &dsp, size_t blockSizeFrames=0, Preparation dspPreparation=Preparation::resetAndPrepare) &&
Processes this AudioBuffer by rendering its audio through a provided DSP.
AudioBuffer< SampleType > & fillWith(SignalBase< SampleType > &signal, size_t blockSizeFrames=0, Preparation signalPreparation=Preparation::resetAndPrepare) &
Fills this AudioBuffer by rendering audio from a Signal.
SampleType * operator[](size_t channel)
Get a raw pointer to a specific channel's mutable audio data.
static AudioBuffer emptyLike(const AudioBuffer &other)
Creates an empty audio buffer with the same number of channels, frames and sample rate as the other b...
size_t getNumFrames() const
Get number of frames (samples)
AudioBuffer & operator=(const AudioBuffer &other)
Creates an audio buffer by copy-assigning.
AudioBuffer(size_t numChannels=0, size_t numFrames=0, double sampleRateHz=nan< double >())
Creates an audio buffer.
void appendFrom(const AudioBuffer< SampleType > &otherBuffer)
Appends data from another buffer.
AudioBuffer(AudioBuffer &&other) noexcept
Creates an audio buffer by moving.
double getLengthSeconds() const
Get a duration of the audio buffer.
bool hasSampleRate() const
Check if a specific sample rate was assigned to the audio buffer.
void setNumFrames(size_t newNumFrames)
Resizes the buffer to hold a new number of frames per channel.
void copyFrom(size_t destChannel, size_t destStartFrame, const SampleType *source, size_t numFrames)
Copies audio from another generic audio buffer.
void copyFrom(size_t destChannel, size_t destStartFrame, const AudioBuffer &source, size_t sourceChannel, size_t sourceStartFrame, size_t numFrames)
Copies audio from another buffer.
double getSampleRateHz() const
Get a sample rate metadata.
std::pair< size_t, size_t > getFrameIndices(const Slice &slice) const
Returns a pair of indices representing a provided slice.
AudioBuffer< SampleType > & processWith(DSPBase< SampleType > &&dsp, size_t blockSizeFrames=0, Preparation dspPreparation=Preparation::resetAndPrepare) &
Processes this AudioBuffer by rendering its audio through a provided DSP.
AudioBuffer< SampleType > && processWith(DSPBase< SampleType > &&dsp, size_t blockSizeFrames=0, Preparation dspPreparation=Preparation::resetAndPrepare) &&
Processes this AudioBuffer by rendering its audio through a provided DSP.
AudioBuffer< SampleType > & fillWith(SignalBase< SampleType > &&signal, size_t blockSizeFrames=0, Preparation signalPreparation=Preparation::resetAndPrepare) &
Fills this AudioBuffer by rendering audio from a Signal.
const SampleType *const * getArrayOfReadPointers() const
Gets a raw pointer to the read-only audio data.
bool operator!=(const AudioBuffer &other) const
Checks whether two buffers differ beyond the default comparison tolerance.
SampleType getMagnitude(size_t startFrame, size_t numFrames) const
Get the maximum absolute value in the buffer across all channels.
AudioBuffer & operator=(AudioBuffer &&other) noexcept
Creates an audio buffer by move-assigning.
SampleType *const * getArrayOfWritePointers()
Gets a raw pointer to the mutable audio data.
AudioBuffer(const AudioBuffer &other)
Creates an audio buffer by copying.
~AudioBuffer()=default
The destructor.
const SampleType * operator[](size_t channel) const
Get a raw pointer to a specific channel's read-only audio data.
AudioBuffer< SampleType > && fillWith(SignalBase< SampleType > &&signal, size_t blockSizeFrames=0, Preparation signalPreparation=Preparation::resetAndPrepare) &&
Fills this AudioBuffer by rendering audio from a Signal.
AudioBuffer< SampleType > && fillWith(SignalBase< SampleType > &signal, size_t blockSizeFrames=0, Preparation signalPreparation=Preparation::resetAndPrepare) &&
Fills this AudioBuffer by rendering audio from a Signal.
AudioBuffer< SampleType > & processWith(DSPBase< SampleType > &dsp, size_t blockSizeFrames=0, Preparation dspPreparation=Preparation::resetAndPrepare) &
Processes this AudioBuffer by rendering its audio through a provided DSP.
void represent(std::ostream &stream) const
Prints readable representation of the audio buffer.
SampleType getMagnitude(size_t channel, size_t startFrame, size_t numFrames) const
Get the maximum absolute value in the buffer in a specific channel.
void clear()
Clears the entire buffer.
size_t getNumChannels() const
Get number of channels.
void erase()
Clears the buffer.
bool equalsTo(const AudioBuffer &other, SampleType toleranceLinear=(SampleType) 1e-6) const
Checks whether this buffer contains approximately the same audio as another buffer.
bool operator==(const AudioBuffer &other) const
Checks whether two buffers contain approximately the same audio.
Thrown when a numbers of channels is mismatched.
Polymorphic base for all DSP.
Definition hart_dsp.hpp:33
Thrown when a container index is out of range.
Thrown when some metric is requested to return a value in an unsupported unit.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns otherwise.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns a specified ...
std::ostream & hzPrecision(std::ostream &stream)
Sets number of decimal places for values in hertz.
FloatType nan()
Returns a quiet NaN value for the given floating-point type.
static size_t roundToSizeT(SampleType x)
Rounds a floating point value to a size_t value.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
Preparation
Describes whether to call reset() and/or prepare() before rendering through DSP or a Signal.
Represents a slice of analysis data.