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 SampleType> class AudioBufferSignal;
21
22template <typename T> class DSPBase;
23template <typename T> class SignalBase;
24
25/// @brief Container for audio data
26/// @note This class owns a memory block with audio samples - so it's a container, not a view. Treat it like a heavyweight object.
27/// @ingroup DataStructures
28template <typename SampleType>
30{
31public:
32 /// @brief Creates an audio buffer
33 /// @param numChannels Initial number of channels
34 /// @param numFrames Numbers of frames (samples) to be allocated in each channel
35 /// @param sampleRateHz Metadata for sample rate (in Hz) in which the data whould be interpreted, whenever applicable
42 {
44 }
45
46 /// @brief Creates an audio buffer by copying
53 {
55 }
56
57 /// @brief Creates an audio buffer by moving
64 {
65 other.erase();
66 }
67
68 /// @brief The destructor
69 ~AudioBuffer() = default;
70
71 /// @brief Creates an audio buffer by copy-assigning
73 {
74 if (this == &other)
75 return *this;
76
78 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Can't copy from a buffer with different number of channels", *this);
79
85
86 return *this;
87 }
88
89 /// @brief Creates an audio buffer by move-assigning
91 {
92 if (this == &other)
93 return *this;
94
100 other.erase();
101
102 return *this;
103 }
104
105 /// @brief Gets a raw pointer to the read-only audio data
106 /// @return A pointer to an array of per-channel read pointers.
107 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
108 const SampleType* const* getArrayOfReadPointers() const
109 {
110 return static_cast<const SampleType* const*> (m_channelPointers.data());
111 }
112
113 /// @brief Gets a raw pointer to the mutable audio data
114 /// @return A pointer to an array of per-channel write pointers.
115 /// It is guaranteed that each channel points to a contiguous non-interleaved block of memory.
117 {
118 return m_channelPointers.data();
119 }
120
121 /// @brief Creates an empty audio buffer with the same number of channels, frames and sample rate as the `other` buffer.
122 /// @note The data from the other buffer will not be copied. Newly allocated samples will be value-initialized.
123 /// @param other Reference buffer
125 {
126 return other.hasSampleRate()
129 }
130
131 /// @brief Get number of channels
132 /// @return Number of allocated channels
134
135 /// @brief Get number of frames (samples)
136 /// @return Number of allocated frames (samples) in every channel
137 size_t getNumFrames() const { return m_numFrames; }
138
139 /// @brief Resizes the buffer to hold a new number of frames per channel
140 /// @details Resizing behaviour:
141 /// - If newNumFrames == current size, it won't do anything.
142 /// - If newNumFrames > current size, it will append silence (zeros) at the end.
143 /// - If newNumFrames < current size: it will just truncate, discarding samples from the end.
144 /// Will always preserve existing channels and sample-rate metadata.
145 /// @attention May cause reallocation, thus invalidating previous raw pointers!
146 /// @param newNumFrames New number of frames per channel
148 {
150 return;
151
154
156 {
160 return;
161 }
162
164 {
166 std::fill (
168 m_frames.end(),
169 SampleType (0)
170 );
171 }
172
175 }
176
177 /// @brief Check if a specific sample rate was assigned to the audio buffer
178 /// @note Uninitialized buffers, as well as some specific Signal types, will not have a specific sample rate.
179 /// @return `true` if there is a specific sample rate value, `false` otherwise
180 bool hasSampleRate() const
181 {
182 return ! std::isnan (m_sampleRateHz);
183 }
184
185 /// @brief Get a sample rate metadata
186 /// @note Call `hasSampleRate()` if you're not sure if the buffer has a specific sample rate
187 /// @return Buffer's sample rate in Hz
188 double getSampleRateHz() const
189 {
190 hassert (! std::isnan (m_sampleRateHz)); // Call hasSampleRate() first! This buffer doesn't have a specific sample rate.
191 return m_sampleRateHz;
192 }
193
194 /// @brief Get a duration of the audio buffer
195 /// @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.
196 /// @return Duration of the audio buffer in seconds
197 double getLengthSeconds() const
198 {
199 if (m_numFrames == 0)
200 return 0.0;
201
202 hassert (! std::isnan (m_sampleRateHz)); // This buffer doesn't have a specific sample rate, and hence the length is unknown
204 return static_cast <double> (m_numFrames) / m_sampleRateHz;
205 }
206
207 /// @brief Get a raw pointer to a specific channel's mutable audio data
208 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
209 /// @return Pointer to the audio data
211 {
213 }
214
215 /// @brief Get a raw pointer to a specific channel's read-only audio data
216 /// @note The data is guaranteed to have at least `getNumFrames()` items and to be a contiguous non-interleaved block of memory.
217 /// @return Pointer to the audio data
219 {
221 }
222
223 /// @brief Checks whether this buffer contains approximately the same audio as another buffer
224 /// @details The comparison is relaxed - each pair of corresponding samples must differ by
225 /// no more than `toleranceLinear`, but not bit-exact. Both buffers must have the same number
226 /// of channels and frames. Also, if both buffers have sample-rate metadata, the sample rates
227 /// must match. If both buffers have no sample-rate metadata assigned, they will still be
228 /// considered equal as long as their dimensions match, and sample values match within tolerance.
229 /// @param other Buffer to compare against
230 /// @param toleranceLinear Absolute sample tolerance in linear domain (not decibels)
231 /// @return `true` if the buffers are equal within the specified tolerance, `false` otherwise
233 {
235 return false;
236
238 return false;
239
243
246 return false;
247
248 return true;
249 }
250
251 /// @brief Checks whether two buffers contain approximately the same audio
252 /// @details Equivalent to calling `AudioBuffer::equalsTo (other)` with the default tolerance
253 /// @param other Buffer to compare against
254 /// @return `true` if the buffers are equal within the default tolerance, `false` otherwise
255 bool operator== (const AudioBuffer& other) const
256 {
257 return equalsTo (other);
258 }
259
260 /// @brief Checks whether two buffers differ beyond the default comparison tolerance
261 /// @details Equivalent to `! AudioBuffer::equalsTo (other)`
262 /// @param other Buffer to compare against
263 /// @return `true` if the buffers are not equal within the default tolerance, `false` otherwise
264 bool operator!= (const AudioBuffer& other) const
265 {
266 return ! equalsTo (other);
267 }
268
269 /// @brief Creates a new buffer as a sample-wise sum of two existing ones
270 /// @param other Buffer to sum with
271 /// @return A new buffer that contains a sample-wise sum of two buffers
272 /// @throws hart::SizeError is buffer lengths (in frames) are mismatched
273 /// @throws hart::ChannelLayoutError is buffer cumbers of channels are mismatched
274 /// @throws hart::SampleRateError is buffer sample rates are mismatched.
275 /// However, if neither of buffers has a sample rate assigned, this operation is allowed.
277 {
279 HART_THROW_OR_RETURN (hart::SizeError, "Cannot add two buffers of mismatched lengths", {});
280
282 HART_THROW_OR_RETURN (hart::ChannelLayoutError, "Cannot add two buffers of mismatched numbers of channels", {});
283
286
287 // 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
289 HART_THROW_OR_RETURN (hart::SampleRateError, "Cannot add two buffers of mismatched sample rates", {});
290
291 AudioBuffer<SampleType> tmp (*this);
293
296
297 for (size_t sample = 0; sample < tmp.m_frames.size(); ++sample)
299
300 return tmp;
301 }
302
303 /// @brief Creates a new buffer as a multiplying each sample of existing number by a provided gain value
304 /// @param other gainLinear Gain to multiply each sample by
305 /// @return A new buffer that contains a samples of existing buffer, multiplied by the provided gain
307 {
308 AudioBuffer<SampleType> tmp (*this);
310
311 for (size_t sample = 0; sample < tmp.m_frames.size(); ++sample)
313
314 return tmp;
315 }
316
317 /// @brief Creates a new buffer as a sample-wise difference of two existing ones
318 /// @param other Buffer to subtract from with
319 /// @return A new buffer that contains a sample-wise difference of two buffers
320 /// @throws hart::SizeError is buffer lengths (in frames) are mismatched
321 /// @throws hart::ChannelLayoutError is buffer cumbers of channels are mismatched
322 /// @throws hart::SampleRateError is buffer sample rates are mismatched.
323 /// However, if neither of buffers has a sample rate assigned, this operation is allowed.
325 {
326 return *this + (other * SampleType (-1));
327 }
328
329 /// @brief Appends data from another buffer
330 /// @warning This operation will resize the current buffer and potentially invalidate the previous raw data pointers returned by
331 /// `getArrayOfReadPointers()`, `getArrayOfWritePointers()` and the "`[]`" operator, so make sure to keep those external pointers up to date.
332 /// @param otherBuffer A buffer to append from
334 {
336 HART_THROW_OR_RETURN_VOID (hart::ChannelLayoutError, "Channel count mismatch");
337
338 // Not a huge deal, but appending a buffer with a different SR must be at least a little suspicious
340
343
345
347 {
351 }
352
355
357 }
358
359 /// @brief Clears the buffer
360 /// @details The number of frames after this operation will be zero, but channel number will persist.
361 void erase()
362 {
363 // Keeping the sample rate though, just wiping the data
364
365 m_numFrames = 0;
366 m_frames.clear();
367
368 // If m_channelPointers was std::move'd, its size will be zero
371
373 }
374
375 /// @brief Get the maximum absolute value in the buffer in a specific channel
376 /// @param channel Channel of which the magnitude should be measured
377 /// @param startFrame The beginning of the range of the frames to look for magnitude
378 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
379 /// @return The maximum value, in linear domain (i.e. not decibels)
381 {
382 if (channel >= m_numChannels)
383 HART_THROW_OR_RETURN (hart::IndexError, "Invalid channel", (SampleType) 0);
384
386 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
387
390 start,
392 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
393 );
394
395 return std::abs (*peakSample);
396 }
397
398 /// @brief Get the maximum absolute value in the buffer across all channels
399 /// @param startFrame The beginning of the range of the frames to look for magnitude
400 /// @param numFrames Number of frames, beginning with startFrame, to look for the magnitude
401 /// @return The maximum value, in linear domain (i.e. not decibels)
403 {
405 HART_THROW_OR_RETURN (hart::IndexError, "Invalid frame range", (SampleType) 0);
406
408
410 {
413 start,
415 [] (SampleType a, SampleType b) { return std::abs (a) < std::abs (b); }
416 );
418 }
419
421 }
422
423 /// @brief Copies audio from another buffer
424 /// @param destChannel Channel within this buffer to copy the frames to
425 /// @param destStartFrame Start frame within this buffer's channel
426 /// @param source Source buffer to read from
427 /// @param sourceChannel Channel within the source buffer to read from
428 /// @param sourceStartFrame Offset within the source buffer's channel to start reading frames from
429 /// @param numFrames Number of frames to copy
431 {
433 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
434
436 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
437
438 // Not a huge deal, but copying from a buffer with different SR must be at least a little suspicious
440
441 std::copy (
445 );
446 }
447
448 /// @brief Copies audio from another generic audio buffer
449 /// @param destChannel Channel within this buffer to copy the frames to
450 /// @param destStartFrame Start frame within this buffer's channel
451 /// @param source Pointer to the source sample data, must contain at least `numFrames` samples
452 /// @param numFrames Number of frames to copy
454 {
456 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid destination channel");
457
459 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
460
462 }
463
464 /// @brief Clears the entire buffer
465 /// @details Sets all frames in all channels to zeros, keeping the sample rate value intact
466 void clear()
467 {
469 }
470
471 /// @brief Clears a specific section of a given channel
472 /// @details Overwrites a selected section of the channel with zeros
473 /// @param channel Channel in which to clear a frame range
474 /// @param startFrame Start of the frame range to clear (inclusive)
475 /// @param numFrames Amount of frames to clear
477 {
478 if (channel >= m_numChannels)
479 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid channel");
480
482 HART_THROW_OR_RETURN_VOID (hart::IndexError, "Invalid frame range");
483
485 }
486
487 /// @brief Prints readable representation of the audio buffer
488 /// @param stream String stream to append the representation to
490 {
491 stream << "AudioBuffer (" << m_numChannels << ", " << m_numFrames;
492
493 if (hasSampleRate())
494 stream << ", " << hzPrecision << m_sampleRateHz << "_Hz)";
495 else
496 stream << ", nan())";
497 }
498
499 /// @brief Fills this entire AudioBuffer with a specific single value
500 /// @param value Value to assign every frame in the buffer to
501 /// @return A reference to this AudioBuffer for chaining further operations.
503 {
505 return *this;
506 }
507
508 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
509 /// @details
510 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
511 /// The Signal is not consumed and can be re-used after rendering completes.
512 ///
513 /// The buffer's sample rate and channel layout are used as the hosting configuration.
514 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
515 ///
516 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
517 /// The final block may be shorter than the requested block size.
518 /// @param signal A reusable Signal object to render from.
519 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
520 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
521 /// @return A reference to this AudioBuffer for chaining further operations.
522 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
523 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
524 /// is unsupported by the Signal
525 /// @throws hart::SizeError is this buffer has no frames allocated
527 {
529 return *this;
530 }
531
532 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
533 /// @details
534 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
535 ///
536 /// The buffer's sample rate and channel layout are used as the hosting configuration.
537 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
538 ///
539 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
540 /// The final block may be shorter than the requested block size.
541 /// @param signal A temporary Signal instance to render from.
542 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
543 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
544 /// @return A reference to this AudioBuffer for chaining further operations.
545 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
546 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
547 /// is unsupported by the Signal
548 /// @throws hart::SizeError is this buffer has no frames allocated
550 {
552 return *this;
553 }
554
555 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
556 /// @details
557 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
558 /// The Signal is not consumed and can be re-used after rendering completes.
559 ///
560 /// The buffer's sample rate and channel layout are used as the hosting configuration.
561 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
562 ///
563 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
564 /// The final block may be shorter than the requested block size.
565 /// @param signal A reusable Signal object to render from.
566 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
567 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
568 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
569 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
570 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
571 /// is unsupported by the Signal
572 /// @throws hart::SizeError is this buffer has no frames allocated
574 {
576 return std::move (*this);
577 }
578
579 /// @brief Fills this AudioBuffer by rendering audio from a Signal.
580 /// @details
581 /// This method uses the supplied Signal and renders audio into this buffer, replacing all existing samples.
582 ///
583 /// The buffer's sample rate and channel layout are used as the hosting configuration.
584 /// If the Signal does not support the current channel count or sample rate, an exception is raised.
585 ///
586 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
587 /// The final block may be shorter than the requested block size.
588 /// @param signal A temporary Signal instance to render from.
589 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
590 /// @param signalPreparation Controls whether the Signal should be reset and/or prepared before rendering.
591 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers.
592 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the Signal.
593 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
594 /// is unsupported by the Signal
595 /// @throws hart::SizeError is this buffer has no frames allocated
597 {
599 return std::move (*this);
600 }
601
602 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
603 /// @details
604 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
605 /// The DSP is not consumed and can be re-used after rendering completes.
606 ///
607 /// The buffer's sample rate and channel layout are used as the hosting configuration.
608 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
609 ///
610 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
611 /// The final block may be shorter than the requested block size.
612 /// @param signal A reusable hart::DSP object to render from
613 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go
614 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
615 /// @return A reference to this AudioBuffer for chaining further operations
616 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
617 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
618 /// is unsupported by the DSP object
619 /// @throws hart::SizeError is this buffer has no frames allocated
621 {
623 return *this;
624 }
625
626 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
627 /// @details
628 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples
629 ///
630 /// The buffer's sample rate and channel layout are used as the hosting configuration.
631 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
632 ///
633 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length
634 /// The final block may be shorter than the requested block size
635 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
636 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
637 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
638 /// @return A reference to this AudioBuffer for chaining further operations
639 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
640 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
641 /// is unsupported by the DSP object
642 /// @throws hart::SizeError is this buffer has no frames allocated
644 {
646 return *this;
647 }
648
649 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
650 /// @details
651 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
652 /// The DSP is not consumed and can be re-used after rendering completes.
653 ///
654 /// The buffer's sample rate and channel layout are used as the hosting configuration.
655 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
656 ///
657 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
658 /// The final block may be shorter than the requested block size.
659 /// @param signal A reusable hart::DSP object to render from
660 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
661 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering.
662 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
663 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
664 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
665 /// is unsupported by the DSP object
666 /// @throws hart::SizeError is this buffer has no frames allocated
668 {
670 return std::move (*this);
671 }
672
673 /// @brief Processes this AudioBuffer by rendering its audio through a provided DSP
674 /// @details
675 /// This method uses the supplied DSP and renders audio from this buffer, replacing all existing samples.
676 ///
677 /// The buffer's sample rate and channel layout are used as the hosting configuration.
678 /// If the DSP does not support the current channel count or sample rate, an exception is raised.
679 ///
680 /// Rendering is performed block-by-block when @p blockSizeFrames is smaller than the buffer length.
681 /// The final block may be shorter than the requested block size.
682 /// @param signal A temporary hart::DSP object to process AudioBuffer's contents with
683 /// @param blockSizeFrames Number of frames to render per block. Pass 0 to render the whole buffer in one go.
684 /// @param signalPreparation Controls whether the DSP object should be reset and/or prepared before rendering
685 /// @return An rvalue reference to this AudioBuffer, allowing fluent chaining with temporary buffers
686 /// @throws hart::SampleRateError if Sample rate of this buffer is undefined, or unsupported by the DSP object
687 /// @throws hart::ChannelLayoutError if this buffer has no channels allocated, or number of channels
688 /// is unsupported by the DSP object
689 /// @throws hart::SizeError is this buffer has no frames allocated
691 {
693 return std::move (*this);
694 }
695
696 /// @brief Returns a pair of indices representing a provided slice
697 /// @param slice A Slice instance. Valid slice types are `Slice::Type::whole`
698 /// `Slice::Type::frames` and `Slice::Type::time`
699 /// @retval first Index representing the beginning of the range, inclusive
700 /// @retval second Index representing the end of the range, non-inclusive
702 {
703 const size_t numFrames = getNumFrames();
704
705 switch (slice.type)
706 {
707 case Slice::Type::whole:
708 {
709 return {0, numFrames};
710 }
711
712 case Slice::Type::frames:
713 {
714 const size_t startFrame = static_cast<size_t> (slice.start);
715 const size_t stopFrame = static_cast<size_t> (slice.stop);
716
717 return {
720 };
721 }
722
723 case Slice::Type::time:
724 {
725 const double sampleRateHz = getSampleRateHz();
728
729 return {
732 };
733 }
734
735 default:
736 {
737 HART_THROW_OR_RETURN (hart::UnitError, "Slice type cannot be interpreted as frame range", std::make_pair (0, numFrames));
738 }
739 }
740 }
741
742 /// @brief Resamples this buffer to an arbitrary sample rate
743 /// @details Under the hood, it uses
744 /// [r8brain](https://github.com/avaneev/r8brain-free-src), which is said
745 /// to be one of the most high-quality SRC libraries out there.
746 /// It is guaranteed that there's no latency introduced to the resampled signal.
747 /// However, the exact waveform at boundary transients, i. e. at the very
748 /// beginning and at the very end of the buffer, is not guaranteed to be preserved,
749 /// due to SRC filtering.
750 /// @param targetSampleRateHz Sample rate to resample to. Can be higher, lower,
751 /// and can be any valid SR value, not just power-of-two ratios.
752 /// @return A new buffer containing resampled audio
754 {
756 HART_THROW_OR_RETURN (hart::SampleRateError, "Invalid target sample rate", AudioBuffer<SampleType> (m_numChannels, 0));
757
758 if (! hasSampleRate())
759 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));
760
762 return AudioBuffer<SampleType> (*this); // Same sample rate - can get away with just a copy
763
764 const double sourceLengthSeconds = getLengthSeconds();
767 hassert (floatsEqual (resampledBuffer.getLengthSeconds(), sourceLengthSeconds, 100e-6)); // It's okay to loosen this tolerance within reason, if it fails at some point
768
771
772 return resampledBuffer;
773 }
774
777
778private:
781 double m_sampleRateHz = nan<double>();
784
786 {
789 }
790
791 /// @brief Prints readable text representation of the AudioBuffer object into the I/O stream
792 /// @relates AudioBuffer
794 {
796 return stream;
797 }
798
801};
802
803} // namespace hart
804
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 ...