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>
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
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 Depending on circumstances, this callback will either be called once to generate an entire piece of audio from
158 /// start to finish, or called repeatedly, one block at a time.
159 /// This method is guaranteed to be called strictly after @ref prepare(), or not called at all.
160 /// Number of channels and max block size are guaranteed to be in line with the ones set by prepare() callback.
161 /// Assume sample rate to always be equal to the one received in the last @ref prepare() callback.
162 /// All audio blocks except the last one are guaranteed to be equal to ```maxBlockSizeFrames``` set in @ref prepare() callback.
163 /// @warning Remember that the very last block of audio is almost always smaller than the block size set in @ref prepare(), so be
164 /// careful with buffer bounds.
165 /// @note Note that this method does not have to be real-time safe, as all rendering always happens offline.
166 /// Also note that, unlike real-time audio applications, this method is called on the same thread as all others like @ref prepare().
167 /// @param output Output audio block
168 /// @warning Output audio buffer is not guaranteed to be pre-filled with zeros, it may contain junk data.
169 virtual void renderNextBlock (AudioBuffer<SampleType>& output) = 0;
170
171 /// @brief Resets the Signal to initial state
172 /// @details Ideally should be implemented in a way that audio produced after resetting is identical to audio produced after instantiation
173 virtual void reset() = 0;
174
175 /// @brief Returns a smart pointer with a copy of this object
176 virtual std::unique_ptr<SignalBase<SampleType>> copy() const = 0;
177
178 /// @brief Returns a smart pointer with a moved instance of this object
179 virtual std::unique_ptr<SignalBase<SampleType>> move() = 0;
180
181 /// @brief Makes a text representation of this Signal for test failure outputs.
182 /// @details It is strongly encouraged to follow python's
183 /// <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" target="_blank">repr()</a>
184 /// conventions for returned text - basically, put something like "MyClass(value1, value2)" (with no quotes)
185 /// into the stream whenever possible, or "<Readable info in angled brackets>" otherwise.
186 /// Also, use built-in stream manipulators like @ref dbPrecision wherever applicable.
187 /// Use @ref HART_DEFINE_GENERIC_REPRESENT() to get a basic implementation for this method.
188 /// @param[out] stream Output stream to write to
189 virtual void represent (std::ostream& stream) const = 0;
190
191 /// @brief Prepares the signal and all attached effects in the DSP chain for rendering
192 /// @details This method is intended to be called by Signal hosts like AudioTestBuilder or Matcher.
193 /// If you're making something that owns an instance of a Signal and needs it to generate audio,
194 /// like a custom Matcher, you must call this method before calling @ref renderNextBlockWithDSPChain().
195 /// You must also call @ref supportsNumChannels() and @ref supportsSampleRate() before calling this method.
196 /// @attention If you're not making a custom host, you probably don't need to call this method.
197 void prepareWithDSPChain (double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)
198 {
199 prepare (sampleRateHz, numOutputChannels, maxBlockSizeFrames);
200 const size_t numInputChannels = numOutputChannels;
201
202 for (auto& dsp : m_dspChain)
203 {
204 if (! dsp->supportsChannelLayout (numInputChannels, numOutputChannels))
205 HART_THROW_OR_RETURN_VOID (ChannelLayoutError, "Not all DSP in the Signal's DSP chain support its channel layout");
206
207 if (! dsp->supportsSampleRate (sampleRateHz))
208 HART_THROW_OR_RETURN_VOID (hart::SampleRateError, "Not all DSP in the Signal's DSP chain support its sample rate");
209
210 dsp->prepareWithEnvelopes (sampleRateHz, numInputChannels, numOutputChannels, maxBlockSizeFrames);
211 }
212
213 // Perform optional fast-forward set by DSP::skipTo()
215 performSkipTo (sampleRateHz, numOutputChannels, maxBlockSizeFrames);
216 }
217
218 /// @brief Renders next block audio for the signal and all the effects in the DSP chain
219 /// @details This method is intended to be called by Signal hosts like AudioTestBuilder or Matcher
220 /// If you're making something that owns an instance of a Signal and needs it to generate audio,
221 /// like a custom Matcher, you must call it after calling @ref prepareWithDSPChain().
222 /// @attention If you're not making a custom host, you probably don't need to call this method.
223 void renderNextBlockWithDSPChain (AudioBuffer<SampleType>& output)
224 {
225 renderNextBlock (output);
226 AudioBuffer<SampleType>& inputReplacing = output;
227
228 for (auto& dsp : m_dspChain)
229 dsp->processWithEnvelopes (inputReplacing, output);
230 }
231
232 /// @brief Resets to Signal and all the effects attached to its DSP chain to initial state
233 /// @details This method is intended to be called by hosts like AudioTestBuilder or Matcher.
234 /// If you're not making a custom host, you probably don't need this method.
235 virtual void resetWithDSPChain()
236 {
237 reset();
238
239 for (auto& dsp : m_dspChain)
240 dsp->resetWithEnvelopes();
241 }
242
243 /// @brief Makes a text representation of this signal and its entire signal chain for test failure outputs.
244 /// @details Used by "<<" operator
245 /// @private
246 /// @param[out] stream Output stream to write to
247 void representWithDSPChain (std::ostream& stream) const
248 {
249 represent (stream);
250
251 for (const auto& dsp : m_dspChain)
252 stream << " >> " << *dsp;
253 }
254
255 /// @brief Returns the size of the DSP chain attached to the Signal
256 /// @return Number of elements in the DSP chain
257 size_t getDSPChainSize() const
258 {
259 return this->m_dspChain.size();
260 }
261
262 /// @brief Access a specific element in the DSP chain
263 /// @param index Index of the element. Can be negative. For non-negative values, it's a usual 0-based index.
264 /// For negative values, it's counted from the end, e.g. "-1" is the last element, "-2" is the second to last etc.
265 /// @throws hart::IndexError if the index is out of range
266 /// @return A pointer to the DSP instance
267 DSPBase<SampleType>* getDSP (int index = -1) const
268 {
269 if (this->m_dspChain.empty())
270 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain is empty", nullptr);
271
272 const int size = static_cast<int> (this->m_dspChain.size());
273
274 if (index >= size || index < -size)
275 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain index is out of range", nullptr);
276
277 if (index < 0)
278 index += size;
279
280 hassert (index >= 0 && index < size);
281 return this->m_dspChain[index].get();
282 }
283
284 /// @brief Extract a specific element in the DSP chain by removing it
285 /// @param index Index of the element. Can be negative. For non-negative values, it's a usual 0-based index.
286 /// For negative values, it's counted from the end, e.g. "-1" is the last element, "-2" is the second to last etc.
287 /// @throws hart::IndexError if the index is out of range
288 /// @return A smart pointer with the DSP instance
289 std::unique_ptr<DSPBase<SampleType>> popDSP (int index = -1)
290 {
291 if (this->m_dspChain.empty())
292 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain is empty", nullptr);
293
294 const int size = static_cast<int> (this->m_dspChain.size());
295
296 if (index >= size || index < -size)
297 HART_THROW_OR_RETURN (hart::IndexError, "DSP chain index is out of range", nullptr);
298
299 if (index < 0)
300 index += size;
301
302 hassert (index >= 0 && index < size);
303
304 std::unique_ptr<DSPBase<SampleType>> dsp = std::move (this->m_dspChain[index]);
305 this->m_dspChain.erase (this->m_dspChain.begin() + index);
306 return dsp;
307 }
308
309protected:
310 void setNumChannels (size_t numChannels)
311 {
312 m_numChannels = numChannels;
313 }
314
316 {
317 return m_numChannels;
318 }
319
320 size_t m_numChannels = 1;
322 std::vector<std::unique_ptr<DSPBase<SampleType>>> m_dspChain;
323
324private:
325 void performSkipTo (double sampleRateHz, size_t numOutputChannels, size_t maxBlockSizeFrames)
326 {
328
329 size_t fastForwardFramesLeft = roundToSizeT (m_startTimestampSeconds * sampleRateHz);
330
331 while (fastForwardFramesLeft != 0)
332 {
333 const size_t blockSizeFrames = fastForwardFramesLeft >= maxBlockSizeFrames ? maxBlockSizeFrames : fastForwardFramesLeft;
334 AudioBuffer<SampleType> dummyAudioBlock (numOutputChannels, blockSizeFrames, sampleRateHz);
335 renderNextBlockWithDSPChain (dummyAudioBlock);
336 fastForwardFramesLeft -= blockSizeFrames;
337 }
338
340 }
341};
342
343/// @brief Base class for signals
344/// @ingroup Signals
345/// @tparam SampleType Type of values that will be generated, typically ```float``` or ```double```
346/// @tparam DerivedSignal Subclass for CRTP
347template<typename SampleType, typename DerivedSignal>
348class Signal:
349 public SignalBase<SampleType>
350{
351public:
352 /// @brief Adds a DSP effect to the end of signal's DSP chain
353 /// @note You can only add a DSP by moving it, since some of the custom DSP wrappers can be non-copyable.
354 /// If you do want to copy a dsp to the chain, use its DSPBase::copy() method explicitly.
355 /// @param dsp A DSP effect instance
356 template<typename DerivedDSP,
357 typename = typename std::enable_if<
358 ! std::is_lvalue_reference<DerivedDSP>::value
359 && std::is_base_of<
360 DSPBase<SampleType>,
361 typename std::decay<DerivedDSP>::type
362 >::value
363 >::type>
364 DerivedSignal&
365 followedBy (DerivedDSP&& dsp) &
366 {
367 _followedBy (std::forward<DerivedDSP> (dsp));
368 return static_cast<DerivedSignal&> (*this);
369 }
370
371 /// @brief Adds a DSP effect to the end of signal's DSP chain
372 /// @note You can only add a DSP by moving it, since some of the custom DSP wrappers can be non-copyable.
373 /// If you do want to copy a dsp to the chain, use its DSPBase::copy() method explicitly.
374 /// @param dsp A DSP effect instance
375 template<typename DerivedDSP,
376 typename = typename std::enable_if<
377 ! std::is_lvalue_reference<DerivedDSP>::value
378 && std::is_base_of<
379 DSPBase<SampleType>,
380 typename std::decay<DerivedDSP>::type
381 >::value
382 >::type>
383 DerivedSignal&&
384 followedBy (DerivedDSP&& dsp) &&
385 {
386 _followedBy (std::forward<DerivedDSP> (dsp));
387 return static_cast<DerivedSignal&&> (*this);
388 }
389
390 /// @brief Adds a DSP effect to the end of signal's DSP chain
391 /// @note DSP smart pointer ownership will be transferred to the Signal instance
392 /// @param dsp Smart pointer to DSP effect instance
393 DerivedSignal& followedBy (std::unique_ptr<DSPBase<SampleType>> dsp) &
394 {
395 _followedBy (std::move (dsp));
396 return static_cast<DerivedSignal&> (*this);
397 }
398
399 /// @brief Adds a DSP effect to the end of signal's DSP chain
400 /// @note DSP smart pointer ownership will be transferred to the Signal instance
401 /// @param dsp Smart pointer to DSP effect instance
402 DerivedSignal&& followedBy (std::unique_ptr<DSPBase<SampleType>> dsp) &&
403 {
404 _followedBy (std::move (dsp));
405 return static_cast<DerivedSignal&&> (*this);
406 }
407
408 std::unique_ptr<SignalBase<SampleType>> copy() const override
409 {
410 return hart::make_unique<DerivedSignal> (static_cast<const DerivedSignal&> (*this));
411 }
412
413 std::unique_ptr<SignalBase<SampleType>> move() override
414 {
415 return hart::make_unique<DerivedSignal> (std::move (static_cast<DerivedSignal&&> (*this)));
416 }
417
418 /// @brief Skips the signal to a specific timestamp
419 /// @details Fast-forwards the signal, with all attaches DSP effects and their automation
420 /// envelopes. Calling it multiple times on one instance will stack the skip times.
421 /// @note Keep in mind that the skip is accurate within one audio frame tolerance
422 /// @param startTimestampSeconds How much time to skip from the start of the signal
423 DerivedSignal& skipTo (double startTimestampSeconds) &
424 {
425 _skipTo (startTimestampSeconds);
426 return static_cast<DerivedSignal&> (*this);
427 }
428
429 /// @brief Skips the signal to a specific timestamp
430 /// @details Fast-forwards the signal, with all attaches DSP effects and their automation
431 /// envelopes. Calling it multiple times on one instance will stack the skip times.
432 /// @note Keep in mind that the skip is accurate within one audio frame tolerance
433 /// @param startTimestampSeconds How much time to skip from the start of the signal
434 DerivedSignal&& skipTo (double startTimestampSeconds) &&
435 {
436 _skipTo (startTimestampSeconds);
437 return static_cast<DerivedSignal&&> (*this);
438 }
439
440 /// @brief Returns a copy of this signal, but with flipped phase
441 DerivedSignal operator-() const
442 {
443 auto newSignal = static_cast<const DerivedSignal&> (*this);
444 newSignal.m_dspChain.emplace_back (hart::make_unique<GainLinear<SampleType>> (SampleType (-1)));
445 return newSignal;
446 }
447
448 /// @brief Returns a copy of this signal, but with flipped phase
449 DerivedSignal operator~() const
450 {
451 return -(*this);
452 }
453
454private:
455 void _skipTo (double startTimestampSeconds)
456 {
457 if (startTimestampSeconds < 0)
458 HART_THROW_OR_RETURN (hart::ValueError, "Can't skip to a negative timestamp", static_cast<DerivedSignal&> (*this));
459
460 this->m_startTimestampSeconds += startTimestampSeconds;
461 }
462
463 template <typename DerivedDSP>
464 void _followedBy (DerivedDSP&& dsp)
465 {
466 static_assert (!std::is_lvalue_reference<DerivedDSP>::value, "DSP must be passed as an rvalue");
467 static_assert (std::is_base_of<DSPBase<SampleType>, typename std::decay<DerivedDSP>::type>::value, "Argument must be a DSP object");
468 this->m_dspChain.emplace_back (dsp.move());
469 }
470
471 void _followedBy(std::unique_ptr<DSPBase<SampleType>> dsp)
472 {
473 this->m_dspChain.emplace_back (std::move (dsp));
474 }
475};
476
477/// @brief Prints readable text representation of the Signal object into the I/O stream
478/// @relates Signal
479/// @ingroup Signals
480template<typename SampleType>
481std::ostream& operator<< (std::ostream& stream, const SignalBase<SampleType>& signal)
482{
483 signal.representWithDSPChain (stream);
484 return stream;
485}
486
487/// @brief Adds a DSP effect to the end of signal's DSP chain by transfering it
488/// @details Accepts any value that can be moved into the signal's DSP chain:
489/// - An rvalue of a class derived from `DSPBase<SampleType>`
490/// - An `std::unique_ptr<DSPBase<SampleType>>`
491/// @relates Signal
492/// @ingroup Signals
493template<typename DerivedSignal, typename DerivedDSP>
494auto operator>> (DerivedSignal& signal, DerivedDSP&& dsp) -> decltype (signal.followedBy (std::forward<DerivedDSP> (dsp)))
495{
496 return signal.followedBy (std::forward<DerivedDSP> (dsp));
497}
498
499/// @brief Adds a DSP effect to the end of signal's DSP chain by transfering it
500/// @details Accepts any value that can be moved into the signal's DSP chain:
501/// - An rvalue of a class derived from `DSPBase<SampleType>`
502/// - An `std::unique_ptr<DSPBase<SampleType>>`
503/// @relates Signal
504/// @ingroup Signals
505template<typename DerivedSignal, typename DerivedDSP>
506auto operator>> (DerivedSignal&& signal, DerivedDSP&& dsp) -> decltype (std::move (signal).followedBy (std::forward<DerivedDSP> (dsp)))
507{
508 return std::move (signal).followedBy (std::forward<DerivedDSP> (dsp));
509}
510
511} // namespace hart
512
513/// @brief Forbids @ref hart::Signal::copy() and @ref hart::Signal::move() methods
514/// @details Put this into your class body's ```public``` section if either is true:
515/// - Your class is not trivially copyable and movable
516/// - You don't want to trouble yourself with implementing move and copy semantics for your class
517///
518/// Otherwise, use @ref HART_SIGNAL_DEFINE_COPY_AND_MOVE() instead.
519/// Obviously, you won't be able to pass your class to the host
520/// by reference, copy or explicit move, but you still can pass
521/// it wrapped into a smart pointer like so:
522/// ```cpp
523/// processAudioWith (MyDSP())
524/// .withInputSignal(hart::make_unique<MyDspType>()) // As input signal
525/// .expectTrue (EqualsTo (hart::make_unique<MyDspType>())) // As reference signal
526/// .process();
527/// ```
528/// @ingroup Signals
529#define HART_SIGNAL_FORBID_COPY_AND_MOVE
530 std::unique_ptr<Signal<SampleType>> copy() const override {
531 static_assert(false, "This Signal cannot be copied");
532 return nullptr;
533 }
534 std::unique_ptr<Signal<SampleType>> move() override {
535 static_assert(false, "This Signal cannot be moved");
536 return nullptr;
537 }
538
539/// @private
540#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)
541 namespace aliases_float{
542 using ClassName = hart::ClassName<float>;
543 }
544 namespace aliases_double{
545 using ClassName = hart::ClassName<double>;
546 }
Container for audio data.
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 a nullptr could be handled gracefully.
Thrown when sample rate is mismatched.
Polymorphic base for all signals.
virtual void reset()=0
Resets the Signal to initial state.
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
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 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 ...
#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.