HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_signal.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath> // sin()
5#include <cstdint>
6#include <memory>
7#include <random>
8#include <string>
9#include <vector>
10
12#include "dsp/hart_dsp.hpp"
14#include "hart_utils.hpp" // floatsNotEqual(), roundToSizeT()
15
16/// @defgroup Signals Signals
17/// @brief Generate signals
18
19namespace hart {
20
21/// @brief Polymorphic base for all signals
22/// @warning This class exists only for type erasure and polymorphism.
23/// Do NOT inherit custom signals from this class directly.
24/// Inherit from @ref hart::Signal instead.
25/// @ingroup Signals
26template<typename SampleType>
27class SignalBase
28{
29public:
30 /// @brief Default constructor
31 SignalBase() = default;
32
33 /// @brief Copies other signal
34 SignalBase (const SignalBase& other):
35 m_numChannels(other.m_numChannels),
36 m_startTimestampSeconds (other.m_startTimestampSeconds)
37 {
38 if (other.m_dspChain.size() == 0)
39 return;
40
41 m_dspChain.reserve (other.m_dspChain.size());
42
43 for (auto& dsp : other.m_dspChain)
44 {
45 std::unique_ptr<DSPBase<SampleType>> dspCopy = dsp->copy();
46
47 // If you hit this, one of the effects in the DSP chain is either:
48 // - non-copyable by design, or
49 // - missing copy() implementation.
50 // There's a good chance it's your custom DSP wrapper, as they're non-copyable by default,
51 // while built-in HART DSP effects are properly copyable. If you really want your effect in
52 // this DSP chain, consider one of the following options:
53 // 1. Move the whole Signal instance that contains this DSP, instead of copying it.
54 // 2. Move your DSP instance into the signal chain, instead of copying it. You can do this
55 // either via an rvalue ref, or a unique_ptr.
56 // 3. Implement copy semantics (including copy() method) in your DSP subclass.
57 if (dspCopy == nullptr)
58 HART_THROW_OR_CONTINUE (hart::NullPointerError, "One of the DSP effects in the DSP chain has failed to return a copy");
59
60 m_dspChain.push_back (std::move (dspCopy));
61 }
62 }
63
64 /// @brief Moves from other signal
65 SignalBase (SignalBase&& other) noexcept:
66 m_numChannels (other.m_numChannels),
67 m_startTimestampSeconds (other.m_startTimestampSeconds),
68 m_dspChain (std::move (other.m_dspChain))
69 {
70 other.m_numChannels = 0;
71 other.m_startTimestampSeconds = 0.0;
72 }
73
74 /// @brief Destructor
75 virtual ~SignalBase() = default;
76
77 /// @brief Copies from other signal
78 SignalBase& operator= (const SignalBase& other)
79 {
80 if (this == &other)
81 return *this;
82
83 m_numChannels = other.m_numChannels;
84 m_startTimestampSeconds = other.m_startTimestampSeconds;
85 m_dspChain.clear();
86
87 if (other.m_dspChain.size() == 0)
88 return *this;
89
90 m_dspChain.reserve (other.m_dspChain.size());
91
92 for (auto& dsp : other.m_dspChain)
93 {
94 std::unique_ptr<DSPBase<SampleType>> dspCopy = dsp->copy();
95
96 // If you hit this, one of the effects in the DSP chain is either:
97 // - non-copyable by design, or
98 // - missing copy() implementation.
99 // There's a good chance it's your custom DSP wrapper, as they're non-copyable by default,
100 // while built-in HART DSP effects are properly copyable. If you really want your effect in
101 // this DSP chain, consider one of the following options:
102 // 1. Move the whole Signal instance that contains this DSP, instead of copying it.
103 // 2. Move your DSP instance into the signal chain, instead of copying it. You can do this
104 // either via an rvalue ref, or a unique_ptr.
105 // 3. Implement copy semantics (including copy() method) in your DSP subclass.
106 if (dspCopy == nullptr)
107 HART_THROW_OR_CONTINUE (hart::NullPointerError, "One of the DSP effects in the DSP chain has failed to return a copy");
108
109 m_dspChain.push_back (std::move (dspCopy));
110 }
111
112 return *this;
113 }
114
115 /// @brief Moves from other signal
116 SignalBase& operator= (SignalBase&& other) noexcept
117 {
118 if (this == &other)
119 return *this;
120
121 m_numChannels = other.m_numChannels;
122 m_dspChain = std::move (other.m_dspChain);
123 m_startTimestampSeconds = other.m_startTimestampSeconds;
124 other.m_numChannels = 0;
125 other.m_startTimestampSeconds = 0.0;
126
127 return *this;
128 }
129
130 /// @brief Tells the host whether this Signal is capable of generating audio for a certain amount of channels
131 /// @details It is guaranteed that the signal will not receive unsupported number of channels in @ref renderNextBlock().
132 /// This method is guaranteed to be called at least once before @ref prepare()
133 /// @note This method should only care about the Signal itself, and not the attached effects in DSP chain - they'll be queried separately
134 /// @param numChannels Number of output channels that will need to be filled
135 /// @return true if signal is capable of filling this many channels with audio, false otherwise
136 virtual bool supportsNumChannels (size_t /* numChannels */) const { return true; };
137
138 /// @brief Tells whether this Signal supports given sample rate
139 /// @details It is guaranteed to be called before @ref prepare()
140 /// @note This method should only care about the Signal itself, and not the attached effects in DSP chain - they'll be queried separately
141 /// @param sampleRateHz sample rate at which the audio should be generated
142 /// @return true if signal is capable of generating audio at a given sample rate, false otherwise
143 virtual bool supportsSampleRate (double /* sampleRateHz */) const { return true; }
144
145 /// @brief Prepare the signal for rendering
146 /// @details This method is guaranteed to be called after @ref supportsNumChannels() and supportsSampleRate(),
147 /// but before @ref renderNextBlock().
148 /// It is guaranteed that ```numChannels``` obeys supportsNumChannels() preferences, same with ```sampleRateHz```
149 /// and @ref supportsSampleRate(). It is guaranteed that all subsequent renderNextBlock() calls will be in line
150 /// with the arguments received in this callback.
151 /// @param sampleRateHz sample rate at which the audio should be generated
152 /// @param numOutputChannels Number of output channels to be filled
153 /// @param maxBlockSizeFrames Maximum block size in frames (samples)
154 virtual void prepare (double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames) = 0;
155
156 /// @brief Renders next block audio for the signal
157 /// @details This method is guaranteed to be called strictly after @ref prepare(), or not called at all.
158 /// Number of channels and max block size are guaranteed to be in line with the ones set by prepare() callback.
159 /// Assume sample rate to always be equal to the one received in the last @ref prepare() callback.
160 /// All audio blocks except the last one are guaranteed to be equal to ```maxBlockSizeFrames``` set in @ref prepare() callback.
161 /// @warning Remember that the very last block of audio is almost always smaller than the block size set in @ref prepare(), so be
162 /// careful with buffer bounds.
163 /// @note Note that this method does not have to be real-time safe, as all rendering always happens offline.
164 /// Also note that, unlike real-time audio applications, this method is called on the same thread as all others like @ref prepare().
165 /// @param output Output audio block
166 /// @warning Output audio buffer is not guaranteed to be pre-filled with zeros, it may contain junk data.
167 virtual void renderNextBlock (AudioBuffer<SampleType>& output) = 0;
168
169 /// @brief Resets the Signal to initial state
170 /// @details Ideally should be implemented in a way that audio produced after resetting is identical to audio produced after instantiation
171 virtual void reset() = 0;
172
173 /// @brief Returns a smart pointer with a copy of this object
174 virtual std::unique_ptr<SignalBase<SampleType>> copy() const = 0;
175
176 /// @brief Returns a smart pointer with a moved instance of this object
177 virtual std::unique_ptr<SignalBase<SampleType>> move() = 0;
178
179 /// @brief Makes a text representation of this Signal for test failure outputs.
180 /// @details It is strongly encouraged to follow python's
181 /// <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" target="_blank">repr()</a>
182 /// conventions for returned text - basically, put something like "MyClass(value1, value2)" (with no quotes)
183 /// into the stream whenever possible, or "<Readable info in angled brackets>" otherwise.
184 /// Also, use built-in stream manipulators like @ref dbPrecision wherever applicable.
185 /// Use @ref HART_DEFINE_GENERIC_REPRESENT() to get a basic implementation for this method.
186 /// @param[out] stream Output stream to write to
187 virtual void represent (std::ostream& stream) const = 0;
188
189 /// @brief Checks whether the signal itself, and all the DSP instances in its signal chain support the requested channel number
190 /// @details This is intended to be called by the Signal's hosts.
191 bool supportsNumChannelsWithDSPChain (size_t numChannels)
192 {
193 if (! supportsNumChannels (numChannels))
194 return false;
195
196 for (auto& dsp : m_dspChain)
197 if (! dsp->supportsChannelLayout (numChannels, numChannels))
198 return false;
199
200 return true;
201 }
202
203 /// @brief Checks whether the signal itself, and all the DSP instances in its signal chain support the requested sample rate
204 /// @details This is intended to be called by the Signal's hosts.
205 bool supportsSampleRateWithDSPChain (double sampleRateHz)
206 {
207 if (! supportsSampleRate (sampleRateHz))
208 return false;
209
210 for (auto& dsp : m_dspChain)
211 if (! dsp->supportsSampleRate (sampleRateHz))
212 return false;
213
214 return true;
215 }
216
217 /// @brief Prepares the signal and all attached effects in the DSP chain for rendering
218 /// @details This method is intended to be called by Signal hosts like AudioTestBuilder or Matcher.
219 /// If you're making something that owns an instance of a Signal and needs it to generate audio,
220 /// like a custom Matcher, you must call this method before calling @ref renderNextBlockWithDSPChain().
221 /// You must also call @ref supportsNumChannels() and @ref supportsSampleRate() before calling this method.
222 /// @attention If you're not making a custom host, you probably don't need to call this method.
223 void prepareWithDSPChain (double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)
224 {
225 m_maxBlockSizeFrames = maxBlockSizeFrames;
226
227 prepare (sampleRateHz, numOutputChannels, maxBlockSizeFrames);
228 const size_t numInputChannels = numOutputChannels;
229
230 for (auto& dsp : m_dspChain)
231 dsp->prepareWithEnvelopes (sampleRateHz, numInputChannels, numOutputChannels, maxBlockSizeFrames);
232
233 // Perform optional fast-forward set by DSP::skipTo()
235 performSkipTo (sampleRateHz, numOutputChannels, maxBlockSizeFrames);
236 }
237
238 /// @brief Renders next block audio for the signal and all the effects in the DSP chain
239 /// @details This method is intended to be called by Signal hosts like AudioTestBuilder or Matcher
240 /// If you're making something that owns an instance of a Signal and needs it to generate audio,
241 /// like a custom Matcher, you must call it after calling @ref prepareWithDSPChain().
242 /// @attention If you're not making a custom host, you probably don't need to call this method.
243 void renderNextBlockWithDSPChain (AudioBuffer<SampleType>& output)
244 {
245 // Make sure prepareWithDSPChain() was called by the host first!
247
248 // If you've triggerred this, you're probably trying to skip block-by block rendering,
249 // by rendering the whole piece of audio at once, or just accidentally got a block size
250 // larger than the onse set in prepareWithDSPChain().
251 // While is could be fine in certain cases, remember that there can be some DSP effects
252 // in the signal chain that rely in a block size set in the prepare() callback, so HART
253 // guarantees that the size of AudioBuffer for process() will never be larger than the
254 // block size set in prepare() callback.
255 // TLDR: Make sure you always render audio block-wise, obeying the size set in prepare().
256 hassert (output.getNumFrames() <= m_maxBlockSizeFrames);
257
258 renderNextBlock (output);
259 AudioBuffer<SampleType>& inputReplacing = output;
260
261 for (auto& dsp : m_dspChain)
262 dsp->processWithEnvelopes (inputReplacing, output);
263 }
264
265 /// @brief Resets to Signal and all the effects attached to its DSP chain to initial state
266 /// @details This method is intended to be called by hosts like AudioTestBuilder or Matcher.
267 /// If you're not making a custom host, you probably don't need this method.
268 virtual void resetWithDSPChain()
269 {
270 reset();
271
272 for (auto& dsp : m_dspChain)
273 dsp->resetWithEnvelopes();
274 }
275
276 /// @brief Makes a text representation of this signal and its entire signal chain for test failure outputs.
277 /// @details Used by "<<" operator
278 /// @private
279 /// @param[out] stream Output stream to write to
280 void representWithDSPChain (std::ostream& stream) const
281 {
282 represent (stream);
283
284 for (const auto& dsp : m_dspChain)
285 stream << " >> " << *dsp;
286 }
287
288 /// @brief Returns the size of the DSP chain attached to the Signal
289 /// @return Number of elements in the DSP chain
290 size_t getDSPChainSize() const
291 {
292 return this->m_dspChain.size();
293 }
294
295 /// @brief Access a specific element in the DSP chain
296 /// @param index Index of the element. Can be negative. For non-negative values, it's a usual 0-based index.
297 /// For negative values, it's counted from the end, e.g. "-1" is the last element, "-2" is the second to last etc.
298 /// @throws hart::IndexError if the index is out of range
299 /// @return A pointer to the DSP instance
300 DSPBase<SampleType>* getDSP (int index = -1) const
301 {
302 if (this->m_dspChain.empty())
303 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain is empty", nullptr);
304
305 const int size = static_cast<int> (this->m_dspChain.size());
306
307 if (index >= size || index < -size)
308 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain index is out of range", nullptr);
309
310 if (index < 0)
311 index += size;
312
313 hassert (index >= 0 && index < size);
314 return this->m_dspChain[index].get();
315 }
316
317 /// @brief Extract a specific element in the DSP chain by removing it
318 /// @param index Index of the element. Can be negative. For non-negative values, it's a usual 0-based index.
319 /// For negative values, it's counted from the end, e.g. "-1" is the last element, "-2" is the second to last etc.
320 /// @throws hart::IndexError if the index is out of range
321 /// @return A smart pointer with the DSP instance
322 std::unique_ptr<DSPBase<SampleType>> popDSP (int index = -1)
323 {
324 if (this->m_dspChain.empty())
325 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain is empty", nullptr);
326
327 const int size = static_cast<int> (this->m_dspChain.size());
328
329 if (index >= size || index < -size)
330 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain index is out of range", nullptr);
331
332 if (index < 0)
333 index += size;
334
335 hassert (index >= 0 && index < size);
336
337 std::unique_ptr<DSPBase<SampleType>> dsp = std::move (this->m_dspChain[index]);
338 this->m_dspChain.erase (this->m_dspChain.begin() + index);
339 return dsp;
340 }
341
342protected:
343 void setNumChannels (size_t numChannels)
344 {
345 m_numChannels = numChannels;
346 }
347
349 {
350 return m_numChannels;
351 }
352
354 size_t m_numChannels = 1;
356 std::vector<std::unique_ptr<DSPBase<SampleType>>> m_dspChain;
357
358private:
359 void performSkipTo (double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)
360 {
362
363 size_t fastForwardFramesLeft = roundToSizeT (m_startTimestampSeconds * sampleRateHz);
364
365 while (fastForwardFramesLeft != 0)
366 {
367 const size_t blockSizeFrames = fastForwardFramesLeft >= maxBlockSizeFrames ? maxBlockSizeFrames : fastForwardFramesLeft;
368 AudioBuffer<SampleType> dummyAudioBlock (numOutputChannels, blockSizeFrames, sampleRateHz);
369 renderNextBlockWithDSPChain (dummyAudioBlock);
370 fastForwardFramesLeft -= blockSizeFrames;
371 }
372
374 }
375};
376
377/// @brief Base class for signals
378/// @ingroup Signals
379/// @tparam SampleType Type of values that will be generated, typically ```float``` or ```double```
380/// @tparam DerivedSignal Subclass for CRTP
381template<typename SampleType, typename DerivedSignal>
382class Signal:
383 public SignalBase<SampleType>
384{
385public:
386 /// @brief Adds a DSP effect to the end of signal's DSP chain
387 /// @note You can only add a DSP by moving it, since some of the custom DSP wrappers can be non-copyable.
388 /// If you do want to copy a dsp to the chain, use its DSPBase::copy() method explicitly.
389 /// @param dsp A DSP effect instance
390 template<typename DerivedDSP,
391 typename = typename std::enable_if<
392 ! std::is_lvalue_reference<DerivedDSP>::value
393 && std::is_base_of<
394 DSPBase<SampleType>,
395 typename std::decay<DerivedDSP>::type
396 >::value
397 >::type>
398 DerivedSignal&
399 followedBy (DerivedDSP&& dsp) &
400 {
401 _followedBy (std::forward<DerivedDSP> (dsp));
402 return static_cast<DerivedSignal&> (*this);
403 }
404
405 /// @brief Adds a DSP effect to the end of signal's DSP chain
406 /// @note You can only add a DSP by moving it, since some of the custom DSP wrappers can be non-copyable.
407 /// If you do want to copy a dsp to the chain, use its DSPBase::copy() method explicitly.
408 /// @param dsp A DSP effect instance
409 template<typename DerivedDSP,
410 typename = typename std::enable_if<
411 ! std::is_lvalue_reference<DerivedDSP>::value
412 && std::is_base_of<
413 DSPBase<SampleType>,
414 typename std::decay<DerivedDSP>::type
415 >::value
416 >::type>
417 DerivedSignal&&
418 followedBy (DerivedDSP&& dsp) &&
419 {
420 _followedBy (std::forward<DerivedDSP> (dsp));
421 return static_cast<DerivedSignal&&> (*this);
422 }
423
424 /// @brief Adds a DSP effect to the end of signal's DSP chain
425 /// @note DSP smart pointer ownership will be transferred to the Signal instance
426 /// @param dsp Smart pointer to DSP effect instance
427 DerivedSignal& followedBy (std::unique_ptr<DSPBase<SampleType>> dsp) &
428 {
429 _followedBy (std::move (dsp));
430 return static_cast<DerivedSignal&> (*this);
431 }
432
433 /// @brief Adds a DSP effect to the end of signal's DSP chain
434 /// @note DSP smart pointer ownership will be transferred to the Signal instance
435 /// @param dsp Smart pointer to DSP effect instance
436 DerivedSignal&& followedBy (std::unique_ptr<DSPBase<SampleType>> dsp) &&
437 {
438 _followedBy (std::move (dsp));
439 return static_cast<DerivedSignal&&> (*this);
440 }
441
442 std::unique_ptr<SignalBase<SampleType>> copy() const override
443 {
444 return hart::make_unique<DerivedSignal> (static_cast<const DerivedSignal&> (*this));
445 }
446
447 std::unique_ptr<SignalBase<SampleType>> move() override
448 {
449 return hart::make_unique<DerivedSignal> (std::move (static_cast<DerivedSignal&&> (*this)));
450 }
451
452 /// @brief Skips the signal to a specific timestamp
453 /// @details Fast-forwards the signal, with all attaches DSP effects and their automation
454 /// envelopes. Calling it multiple times on one instance will stack the skip times.
455 /// @note Keep in mind that the skip is accurate within one audio frame tolerance
456 /// @param startTimestampSeconds How much time to skip from the start of the signal
457 DerivedSignal& skipTo (double startTimestampSeconds) &
458 {
459 _skipTo (startTimestampSeconds);
460 return static_cast<DerivedSignal&> (*this);
461 }
462
463 /// @brief Skips the signal to a specific timestamp
464 /// @details Fast-forwards the signal, with all attaches DSP effects and their automation
465 /// envelopes. Calling it multiple times on one instance will stack the skip times.
466 /// @note Keep in mind that the skip is accurate within one audio frame tolerance
467 /// @param startTimestampSeconds How much time to skip from the start of the signal
468 DerivedSignal&& skipTo (double startTimestampSeconds) &&
469 {
470 _skipTo (startTimestampSeconds);
471 return static_cast<DerivedSignal&&> (*this);
472 }
473
474 /// @brief Returns a copy of this signal, but with flipped phase
475 DerivedSignal operator-() const
476 {
477 auto newSignal = static_cast<const DerivedSignal&> (*this);
478 newSignal.m_dspChain.emplace_back (hart::make_unique<GainLinear<SampleType>> (SampleType (-1)));
479 return newSignal;
480 }
481
482 /// @brief Returns a copy of this signal, but with flipped phase
483 DerivedSignal operator~() const
484 {
485 return -(*this);
486 }
487
488private:
489 void _skipTo (double startTimestampSeconds)
490 {
491 if (startTimestampSeconds < 0)
492 HART_THROW_OR_RETURN (hart::ValueError, "Can't skip to a negative timestamp", static_cast<DerivedSignal&> (*this));
493
494 this->m_startTimestampSeconds += startTimestampSeconds;
495 }
496
497 template <typename DerivedDSP>
498 void _followedBy (DerivedDSP&& dsp)
499 {
500 static_assert (!std::is_lvalue_reference<DerivedDSP>::value, "DSP must be passed as an rvalue");
501 static_assert (std::is_base_of<DSPBase<SampleType>, typename std::decay<DerivedDSP>::type>::value, "Argument must be a DSP object");
502 this->m_dspChain.emplace_back (dsp.move());
503 }
504
505 void _followedBy(std::unique_ptr<DSPBase<SampleType>> dsp)
506 {
507 this->m_dspChain.emplace_back (std::move (dsp));
508 }
509};
510
511/// @brief Prints readable text representation of the Signal object into the I/O stream
512/// @relates Signal
513/// @ingroup Signals
514template<typename SampleType>
515std::ostream& operator<< (std::ostream& stream, const SignalBase<SampleType>& signal)
516{
517 signal.representWithDSPChain (stream);
518 return stream;
519}
520
521/// @brief Adds a DSP effect to the end of signal's DSP chain by transfering it
522/// @details Accepts any value that can be moved into the signal's DSP chain:
523/// - An rvalue of a class derived from `DSPBase<SampleType>`
524/// - An `std::unique_ptr<DSPBase<SampleType>>`
525/// @relates Signal
526/// @ingroup Signals
527template<typename DerivedSignal, typename DerivedDSP>
528auto operator>> (DerivedSignal& signal, DerivedDSP&& dsp) -> decltype (signal.followedBy (std::forward<DerivedDSP> (dsp)))
529{
530 return signal.followedBy (std::forward<DerivedDSP> (dsp));
531}
532
533/// @brief Adds a DSP effect to the end of signal's DSP chain by transfering it
534/// @details Accepts any value that can be moved into the signal's DSP chain:
535/// - An rvalue of a class derived from `DSPBase<SampleType>`
536/// - An `std::unique_ptr<DSPBase<SampleType>>`
537/// @relates Signal
538/// @ingroup Signals
539template<typename DerivedSignal, typename DerivedDSP>
540auto operator>> (DerivedSignal&& signal, DerivedDSP&& dsp) -> decltype (std::move (signal).followedBy (std::forward<DerivedDSP> (dsp)))
541{
542 return std::move (signal).followedBy (std::forward<DerivedDSP> (dsp));
543}
544
545} // namespace hart
546
547/// @brief Forbids @ref hart::Signal::copy() and @ref hart::Signal::move() methods
548/// @details Put this into your class body's ```public``` section if either is true:
549/// - Your class is not trivially copyable and movable
550/// - You don't want to trouble yourself with implementing move and copy semantics for your class
551///
552/// Otherwise, use @ref HART_SIGNAL_DEFINE_COPY_AND_MOVE() instead.
553/// Obviously, you won't be able to pass your class to the host
554/// by reference, copy or explicit move, but you still can pass
555/// it wrapped into a smart pointer like so:
556/// ```cpp
557/// processAudioWith (MyDSP())
558/// .withInputSignal(hart::make_unique<MyDspType>()) // As input signal
559/// .expectTrue (EqualsTo (hart::make_unique<MyDspType>())) // As reference signal
560/// .process();
561/// ```
562/// @ingroup Signals
563#define HART_SIGNAL_FORBID_COPY_AND_MOVE
564 std::unique_ptr<Signal<SampleType>> copy() const override {
565 static_assert(false, "This Signal cannot be copied");
566 return nullptr;
567 }
568 std::unique_ptr<Signal<SampleType>> move() override {
569 static_assert(false, "This Signal cannot be moved");
570 return nullptr;
571 }
572
573/// @private
574#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)
575 namespace aliases_float{
576 using ClassName = hart::ClassName<float>;
577 }
578 namespace aliases_double{
579 using ClassName = hart::ClassName<double>;
580 }
Container for audio data.
Polymorphic base for all DSP.
Definition hart_dsp.hpp:33
Thrown when a container index is out of range.
Thrown when a nullptr could be handled gracefully.
bool supportsNumChannelsWithDSPChain(size_t numChannels)
Checks whether the signal itself, and all the DSP instances in its signal chain support the requested...
virtual void reset()=0
Resets the Signal to initial state.
size_t m_maxBlockSizeFrames
virtual ~SignalBase()=default
Destructor.
virtual void represent(std::ostream &stream) const =0
Makes a text representation of this Signal for test failure outputs.
double m_startTimestampSeconds
bool supportsSampleRateWithDSPChain(double sampleRateHz)
Checks whether the signal itself, and all the DSP instances in its signal chain support the requested...
SignalBase(const SignalBase &other)
Copies other signal.
DSPBase< SampleType > * getDSP(int index=-1) const
Access a specific element in the DSP chain.
virtual std::unique_ptr< SignalBase< SampleType > > copy() const =0
Returns a smart pointer with a copy of this object.
void renderNextBlockWithDSPChain(AudioBuffer< SampleType > &output)
Renders next block audio for the signal and all the effects in the DSP chain.
std::vector< std::unique_ptr< DSPBase< SampleType > > > m_dspChain
virtual void resetWithDSPChain()
Resets to Signal and all the effects attached to its DSP chain to initial state.
SignalBase & operator=(const SignalBase &other)
Copies from other signal.
SignalBase()=default
Default constructor.
SignalBase & operator=(SignalBase &&other) noexcept
Moves from other signal.
virtual void renderNextBlock(AudioBuffer< SampleType > &output)=0
Renders next block audio for the signal.
void prepareWithDSPChain(double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)
Prepares the signal and all attached effects in the DSP chain for rendering.
void setNumChannels(size_t numChannels)
SignalBase(SignalBase &&other) noexcept
Moves from other signal.
std::unique_ptr< DSPBase< SampleType > > popDSP(int index=-1)
Extract a specific element in the DSP chain by removing it.
virtual std::unique_ptr< SignalBase< SampleType > > move()=0
Returns a smart pointer with a moved instance of this object.
virtual bool supportsNumChannels(size_t) const
Tells the host whether this Signal is capable of generating audio for a certain amount of channels.
virtual bool supportsSampleRate(double) const
Tells whether this Signal supports given sample rate.
size_t getNumChannels()
size_t getDSPChainSize() const
Returns the size of the DSP chain attached to the Signal.
virtual void prepare(double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)=0
Prepare the signal for rendering.
Base class for signals.
DerivedSignal && skipTo(double startTimestampSeconds) &&
Skips the signal to a specific timestamp.
DerivedSignal operator-() const
Returns a copy of this signal, but with flipped phase.
DerivedSignal & followedBy(std::unique_ptr< DSPBase< SampleType > > dsp) &
Adds a DSP effect to the end of signal's DSP chain.
DerivedSignal operator~() const
Returns a copy of this signal, but with flipped phase.
std::unique_ptr< SignalBase< SampleType > > move() override
Returns a smart pointer with a moved instance of this object.
DerivedSignal & skipTo(double startTimestampSeconds) &
Skips the signal to a specific timestamp.
std::unique_ptr< SignalBase< SampleType > > copy() const override
Returns a smart pointer with a copy of this object.
DerivedSignal & followedBy(DerivedDSP &&dsp) &
Adds a DSP effect to the end of signal's DSP chain.
DerivedSignal && followedBy(std::unique_ptr< DSPBase< SampleType > > dsp) &&
Adds a DSP effect to the end of signal's DSP chain.
DerivedSignal && followedBy(DerivedDSP &&dsp) &&
Adds a DSP effect to the end of signal's DSP chain.
Thrown when an inappropriate value is encountered.
#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 ...
#define HART_THROW_OR_CONTINUE(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and jumps to the next it...
auto operator>>(DerivedSignal &signal, DerivedDSP &&dsp) -> decltype(signal.followedBy(std::forward< DerivedDSP >(dsp)))
Adds a DSP effect to the end of signal's DSP chain by transfering it.
auto operator>>(DerivedSignal &&signal, DerivedDSP &&dsp) -> decltype(std::move(signal).followedBy(std::forward< DerivedDSP >(dsp)))
Adds a DSP effect to the end of signal's DSP chain by transfering it.
static size_t roundToSizeT(SampleType x)
Rounds a floating point value to a size_t value.
static SampleType floatsNotEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.