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
12#include "hart_slice.hpp"
13#include "hart_utils.hpp" // nan(), floatsEqual(), roundToSizeT()
14
15/// @defgroup DataStructures Data Structures
16/// @brief Custom data structures and containers
17
18namespace hart {
19
20template <typename T> class DSPBase;
21template <typename T> class SignalBase;
22
23/// @brief Container for audio data
24/// @note This class owns a memory block with audio samples - so it's a container, not a view. Treat it like a heavyweight object.
25/// @ingroup DataStructures
26template <typename SampleType>
28{
29public:
30 /// @brief Creates an audio buffer
31 /// @param numChannels Initial number of channels
32 /// @param numFrames Numbers of frames (samples) to be allocated in each channel
33 /// @param sampleRateHz Metadata for sample rate (in Hz) in which the data whould be interpreted, whenever applicable
40 {
42 }
43
44 /// @brief Creates an audio buffer by copying
51 {
53 }
54
55 /// @brief Creates an audio buffer by moving
62 {
63 other.erase();
64 }
65
66 /// @brief The destructor
67 ~AudioBuffer() = default;
68
69 /// @brief Creates an audio buffer by copy-assigning
71 {
72 if (this == &other)
73 return *this;
74
76 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Can't copy from a buffer with different number of channels", *this);
77
83
84 return *this;
85 }
86
87 /// @brief Creates an audio buffer by move-assigning
89 {
90 if (this == &other)
91 return *this;
92
98 other.erase();
99
100 return *this;
101 }
102
103 /// @brief Gets a raw pointer to the read-only audio data
104 /// @return A pointer to an array of per-channel read pointers.
105 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
106 const SampleType* const* getArrayOfReadPointers() const
107 {
108 return static_cast<const SampleType* const*> (m_channelPointers.data());
109 }
110
111 /// @brief Gets a raw pointer to the mutable audio data
112 /// @return A pointer to an array of per-channel write pointers.
113 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
115 {
116 return m_channelPointers.data();
117 }
118
119 /// @brief Creates an empty audio buffer with the same number of channels, frames and sample rate as the `other` buffer.
120 /// @note The data from the other buffer will not be copied. Newly allocated samples will be value-initialized.
121 /// @param other Reference buffer
123 {
124 return other.hasSampleRate()
127 }
128
129 /// @brief Get number of channels
130 /// @return Number of allocated channels
132
133 /// @brief Get number of frames (samples)
134 /// @return Number of allocated frames (samples) in every channel
135 size_t getNumFrames() const { return m_numFrames; }
136
137 /// @brief Resizes the buffer to hold a new number of frames per channel
138 /// @details Resizing behaviour:
139 /// - If newNumFrames == current size, it won't do anything.
140 /// - If newNumFrames > current size, it will append silence (zeros) at the end.
141 /// - If newNumFrames < current size: it will just truncate, discarding samples from the end.
142 /// Will always preserve existing channels and sample-rate metadata.
143 /// @attention May cause reallocation, thus invalidating previous raw pointers!
144 /// @param newNumFrames New number of frames per channel
146 {
148 return;
149
152
154 {
158 return;
159 }
160
162 {
164 std::fill (
166 m_frames.end(),
167 SampleType (0)
168 );
169 }
170
173 }
174
175 /// @brief Check if a specific sample rate was assigned to the audio buffer
176 /// @note Uninitialized buffers, as well as some specific Signal types, will not have a specific sample rate.
177 /// @return `true` if there is a specific sample rate value, `false` otherwise
178 bool hasSampleRate() const
179 {
180 return ! std::isnan (m_sampleRateHz);
181 }
182
183 /// @brief Get a sample rate metadata
184 /// @note Call `hasSampleRate()` if you're not sure if the buffer has a specific sample rate
185 /// @return Buffer's sample rate in Hz
186 double getSampleRateHz() const
187 {
188 hassert (! std::isnan (m_sampleRateHz)); // Call hasSampleRate() first! This buffer doesn't have a specific sample rate.
189 return m_sampleRateHz;
190 }
191
192 /// @brief Get a duration of the audio buffer
193 /// @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.
194 /// @return Duration of the audio buffer in seconds
195 double getLengthSeconds() const
196 {
197 if (m_numFrames == 0)
198 return 0.0;
199
200 hassert (! std::isnan (m_sampleRateHz)); // This buffer doesn't have a specific sample rate, and hence the length is unknown
202 return static_cast <double> (m_numFrames) / m_sampleRateHz;
203 }
204
205 /// @brief Get a raw pointer to a specific channel's mutable audio data
206 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
207 /// @return Pointer to the audio data
209 {
211 }
212
213 /// @brief Get a raw pointer to a specific channel's read-only audio data
214 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
215 /// @return Pointer to the audio data
217 {
219 }
220
221 /// @brief Checks whether this buffer contains approximately the same audio as another buffer
222 /// @details The comparison is relaxed - each pair of corresponding samples must differ by
223 /// no more than `toleranceLinear`, but not bit-exact. Both buffers must have the same number
224 /// of channels and frames. Also, if both buffers have sample-rate metadata, the sample rates
225 /// must match. If both buffers have no sample-rate metadata assigned, they will still be
226 /// considered equal as long as their dimensions match, and sample values match within tolerance.
227 /// @param other Buffer to compare against
228 /// @param toleranceLinear Absolute sample tolerance in linear domain (not decibels)
229 /// @return `true` if the buffers are equal within the specified tolerance, `false` otherwise
231 {
233 return false;
234
236 return false;
237
241
244 return false;
245
246 return true;
247 }
248
249 /// @brief Checks whether two buffers contain approximately the same audio
250 /// @details Equivalent to calling `AudioBuffer::equalsTo (other)` with the default tolerance
251 /// @param other Buffer to compare against
252 /// @return `true` if the buffers are equal within the default tolerance, `false` otherwise
253 bool operator== (const AudioBuffer& other) const
254 {
255 return equalsTo (other);
256 }
257
258 /// @brief Checks whether two buffers differ beyond the default comparison tolerance
259 /// @details Equivalent to `! AudioBuffer::equalsTo (other)`
260 /// @param other Buffer to compare against
261 /// @return `true` if the buffers are not equal within the default tolerance, `false` otherwise
262 bool operator!= (const AudioBuffer& other) const
263 {
264 return ! equalsTo (other);
265 }
266
267 /// @brief Creates a new buffer as a sample-wise sum of two existing ones
268 /// @param other Buffer to sum with
269 /// @return A new buffer that contains a sample-wise sum of two buffers
270 /// @throws hart::SizeError is buffer lengths (in frames) are mismatched
271 /// @throws hart::ChannelLayoutError is buffer cumbers of channels are mismatched
272 /// @throws hart::SampleRateError is buffer sample rates are mismatched.
273 /// However, if neither of buffers has a sample rate assigned, this operation is allowed.
275 {
277 HART_THROW_OR_RETURN (hart::SizeError, "Cannot add two buffers of mismatched lengths", {});
278
280 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Cannot add two buffers of mismatched numbers of channels", {});
281
284
285 // This could be a legit use case, but still not very clean, so let's not allow it until we have a good reason to let it happen
287 HART_THROW_OR_RETURN (hart::SampleRateError, "Cannot add two buffers of mismatched sample rates", {});
288
289 AudioBuffer<SampleType> tmp (*this);
291
294
295 for (size_t sample = 0; sample < tmp.m_frames.size(); ++sample)
297
298 return tmp;
299 }
300
301 /// @brief Creates a new buffer as a multiplying each sample of existing number by a provided gain value
302 /// @param other gainLinear Gain to multiply each sample by
303 /// @return A new buffer that contains a samples of existing buffer, multiplied by the provided gain
305 {
306 AudioBuffer<SampleType> tmp (*this);
308
309 for (size_t sample = 0; sample < tmp.m_frames.size(); ++sample)
311
312 return tmp;
313 }
314
315 /// @brief Creates a new buffer as a sample-wise difference of two existing ones
316 /// @param other Buffer to subtract from with
317 /// @return A new buffer that contains a sample-wise difference of two buffers
318 /// @throws hart::SizeError is buffer lengths (in frames) are mismatched
319 /// @throws hart::ChannelLayoutError is buffer cumbers of channels are mismatched
320 /// @throws hart::SampleRateError is buffer sample rates are mismatched.
321 /// However, if neither of buffers has a sample rate assigned, this operation is allowed.
323 {
324 return *this + (other * SampleType (-1));
325 }
326
327 /// @brief Appends data from another buffer
328 /// @warning This operation will resize the current buffer and potentially invalidate the previous raw data pointers returned by
329 /// `getArrayOfReadPointers()`, `getArrayOfWritePointers()` and the "`[]`" operator, so make sure to keep those external pointers up to date.
330 /// @param otherBuffer A buffer to append from
332 {
334 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, "Channel count mismatch");
335
336 // Not a huge deal, but appending a buffer with a different SR must be at least a little suspicious
338
341
343
345 {
349 }
350
353
355 }
356
357 /// @brief Clears the buffer
358 /// @details The number of frames after this operation will be zero, but channel number will persist.
359 void erase()
360 {
361 // Keeping the sample rate though, just wiping the data
362
363 m_numFrames = 0;
364 m_frames.clear();
365
366 // If m_channelPointers was std::move'd, its size will be zero
369
371 }
372
373 /// @brief Get the maximum absolute value in the buffer in a specific channel
374 /// @param channel Channel of which the magnitude should be measured
375 /// @param startFrame The beginning of the range of the frames to look for magnitude
376 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
377 /// @return The maximum value, in linear domain (i.e. not decibels)
379 {
380 if (channel >= m_numChannels)
381 HART_THROW_OR_RETURN (hart::IndexError, "Invalid channel", (SampleType) 0);
382
384 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
385
388 start,
390 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
391 );
392
393 return std::abs (*peakSample);
394 }
395
396 /// @brief Get the maximum absolute value in the buffer across all channels
397 /// @param startFrame The beginning of the range of the frames to look for magnitude
398 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
399 /// @return The maximum value, in linear domain (i.e. not decibels)
401 {
403 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
404
406
408 {
411 start,
413 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
414 );
416 }
417
419 }
420
421 /// @brief Copies audio from another buffer
422 /// @param destChannel Channel within this buffer to copy the frames to
423 /// @param destStartFrame Start frame within this buffer's channel
424 /// @param source Source buffer to read from
425 /// @param sourceChannel Channel within the source buffer to read from
426 /// @param sourceStartFrame Offset within the source buffer's channel to start reading frames from
427 /// @param numFrames Number of frames to copy
429 {
431 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
432
434 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
435
436 // Not a huge deal, but copying from a buffer with different SR must be at least a little suspicious
438
439 std::copy (
443 );
444 }
445
446 /// @brief Copies audio from another generic audio buffer
447 /// @param destChannel Channel within this buffer to copy the frames to
448 /// @param destStartFrame Start frame within this buffer's channel
449 /// @param source Pointer to the source sample data, must contain at least `numFrames` samples
450 /// @param numFrames Number of frames to copy
452 {
454 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid destination channel");
455
457 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
458
460 }
461
462 /// @brief Clears the entire buffer
463 /// @details Sets all frames in all channels to zeros, keeping the sample rate value intact
464 void clear()
465 {
467 }
468
469 /// @brief Clears a specific section of a given channel
470 /// @details Overwrites a selected section of the channel with zeros
471 /// @param channel Channel in which to clear a frame range
472 /// @param startFrame Start of the frame range to clear (inclusive)
473 /// @param numFrames Amount of frames to clear
475 {
476 if (channel >= m_numChannels)
477 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
478
480 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
481
483 }
484
485 /// @brief Prints readable representation of the audio buffer
486 /// @param stream String stream to append the representation to
488 {
489 stream << "AudioBuffer (" << m_numChannels << ", " << m_numFrames;
490
491 if (hasSampleRate())
492 stream << ", " << hzPrecision << m_sampleRateHz << "_Hz)";
493 else
494 stream << ", nan())";
495 }
496
497 /// @brief Fills this entire AudioBuffer with a specific single value
498 /// @param value Value to assign every frame in the buffer to
499 /// @return A reference to this AudioBuffer for chaining further operations.
501 {
503 return *this;
504 }
505
506 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
507 /// @details
508 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
509 /// The Signal is not consumed and can be re-used after rendering completes.
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 reusable Signal object 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 A reference to this AudioBuffer for chaining further operations.
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
525 {
527 return *this;
528 }
529
530 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
531 /// @details
532 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
533 ///
534 /// The buffer's sample rate and channel layout are used as the hosting configuration.
535 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
536 ///
537 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
538 /// The final block may be shorter than the requested block size.
539 /// @param signal A temporary Signal instance to render from.
540 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
541 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
542 /// @return A reference to this AudioBuffer for chaining further operations.
543 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
544 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
545 /// is unsupported by the Signal
546 /// @throws hart::SizeError is this buffer has no frames allocated
548 {
550 return *this;
551 }
552
553 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
554 /// @details
555 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
556 /// The Signal is not consumed and can be re-used after rendering completes.
557 ///
558 /// The buffer's sample rate and channel layout are used as the hosting configuration.
559 /// If the Signal 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 reusable Signal object to render from.
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 Signal should be reset and/or prepared before rendering.
566 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
567 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
568 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
569 /// is unsupported by the Signal
570 /// @throws hart::SizeError is this buffer has no frames allocated
572 {
574 return std::move (*this);
575 }
576
577 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
578 /// @details
579 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
580 ///
581 /// The buffer's sample rate and channel layout are used as the hosting configuration.
582 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
583 ///
584 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
585 /// The final block may be shorter than the requested block size.
586 /// @param signal A temporary Signal instance to render from.
587 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
588 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
589 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
590 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
591 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
592 /// is unsupported by the Signal
593 /// @throws hart::SizeError is this buffer has no frames allocated
595 {
597 return std::move (*this);
598 }
599
600 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
601 /// @details
602 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
603 /// The DSP is not consumed and can be re-used after rendering completes.
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 reusable hart::DSP object to render from
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 A reference to this AudioBuffer for chaining further operations
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
619 {
621 return *this;
622 }
623
624 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
625 /// @details
626 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples
627 ///
628 /// The buffer's sample rate and channel layout are used as the hosting configuration.
629 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
630 ///
631 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length
632 /// The final block may be shorter than the requested block size
633 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
634 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
635 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
636 /// @return A reference to this AudioBuffer for chaining further operations
637 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
638 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
639 /// is unsupported by the DSP object
640 /// @throws hart::SizeError is this buffer has no frames allocated
642 {
644 return *this;
645 }
646
647 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
648 /// @details
649 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
650 /// The DSP is not consumed and can be re-used after rendering completes.
651 ///
652 /// The buffer's sample rate and channel layout are used as the hosting configuration.
653 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
654 ///
655 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
656 /// The final block may be shorter than the requested block size.
657 /// @param signal A reusable hart::DSP object to render from
658 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
659 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering.
660 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
661 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
662 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
663 /// is unsupported by the DSP object
664 /// @throws hart::SizeError is this buffer has no frames allocated
666 {
668 return std::move (*this);
669 }
670
671 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
672 /// @details
673 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
674 ///
675 /// The buffer's sample rate and channel layout are used as the hosting configuration.
676 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
677 ///
678 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
679 /// The final block may be shorter than the requested block size.
680 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
681 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
682 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
683 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
684 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
685 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
686 /// is unsupported by the DSP object
687 /// @throws hart::SizeError is this buffer has no frames allocated
689 {
691 return std::move (*this);
692 }
693
694 /// @brief Returns a pair of indices representing a provided slice
695 /// @param slice A Slice instance. Valid slice types are `Slice::Type::whole`
696 /// `Slice::Type::frames` and `Slice::Type::time`
697 /// @retval first Index representing the beginning of the range, inclusive
698 /// @retval second Index representing the end of the range, non-inclusive
700 {
701 const size_t numFrames = getNumFrames();
702
703 switch (slice.type)
704 {
705 case Slice::Type::whole:
706 {
707 return {0, numFrames};
708 }
709
710 case Slice::Type::frames:
711 {
712 const size_t startFrame = static_cast<size_t> (slice.start);
713 const size_t stopFrame = static_cast<size_t> (slice.stop);
714
715 return {
718 };
719 }
720
721 case Slice::Type::time:
722 {
723 const double sampleRateHz = getSampleRateHz();
726
727 return {
730 };
731 }
732
733 default:
734 {
735 HART_THROW_OR_RETURN (hart::UnitError, "Slice type cannot be interpreted as frame range", std::make_pair (0, numFrames));
736 }
737 }
738 }
739
740 /// @brief Resamples this buffer to an arbitrary sample rate
741 /// @details Under the hood, it uses
742 /// [r8brain](https://github.com/avaneev/r8brain-free-src), which is said
743 /// to be one of the most high-quality SRC libraries out there.
744 /// It is guaranteed that there's no latency introduced to the resampled signal.
745 /// However, the exact waveform at boundary transients, i. e. at the very
746 /// beginning and at the very end of the buffer, is not guaranteed to be preserved,
747 /// due to SRC filtering.
748 /// @param targetSampleRateHz Sample rate to resample to. Can be higher, lower,
749 /// and can be any valid SR value, not just power-of-two ratios.
750 /// @return A new buffer containing resampled audio
752 {
754 HART_THROW_OR_RETURN (hart::SampleRateError, "Invalid target sample rate", AudioBuffer<SampleType> (m_numChannels, 0));
755
756 if (! hasSampleRate())
757 HART_THROW_OR_RETURN (hart::SampleRateError, "Can't resample, since this AudioBuffer doesn't have a sample rate value assigned to it", AudioBuffer<SampleType> (m_numChannels, 0, targetSampleRateHz));
758
760 return AudioBuffer<SampleType> (*this); // Same sample rate - can get away with just a copy
761
762 const double sourceLengthSeconds = getLengthSeconds();
765 hassert (floatsEqual (resampledBuffer.getLengthSeconds(), sourceLengthSeconds, 100e-6)); // It's okay to loosen this tolerance within reason, if it fails at some point
766
769
770 return resampledBuffer;
771 }
772
773private:
776 double m_sampleRateHz = nan<double>();
779
781 {
784 }
785
786 /// @brief Prints readable text representation of the AudioBuffer object into the I/O stream
787 /// @relates AudioBuffer
789 {
791 return stream;
792 }
793
796};
797
798} // namespace hart
799
Polymorphic base for all DSP.
Definition hart_dsp.hpp:33
#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 ...