HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_spectrum.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint> // uint_fast32_t
4#include <vector>
5#include <cmath> // sin(), cos(), abs()
6#include <complex> // complex, conj(), real(), imag()
7#include <algorithm>
8#include <random>
9#include <utility> // swap(), make_pair(), pair
10
14#include "hart_slice.hpp"
15#include "signals/hart_audio_buffer_signal.hpp" // for toSignal()
16#include "hart_utils.hpp" // nan(), roundToSizeT(), nextPowerOfTwo()
17
18namespace hart
19{
20
21/// @brief Frequency-domain representation of a multi-channel audio signal
22/// @details
23/// Stores complex spectra for each channel independently.
24/// Spectrum is constructed by performing FFT on the entire signal,
25/// zero-padding to the next power-of-two size if necessary.
26///
27/// Current implementation assumptions:
28/// - Rectangular window (no windowing)
29/// - Real-valued input signal
30/// - One-sided spectrum only (`fftSize / 2 + 1` bins)
31/// - Double floating-point precision, regardless of input signal value types
32/// @ingroup DataStructures
34{
35public:
36 /// @brief Constructs spectrum from an audio buffer
37 /// @param buffer Audio buffer to perform FFT on
38 /// @throws hart::SampleRateError if the audio buffer has no sample rate metadata
39 template <typename SampleType>
40 Spectrum (const AudioBuffer<SampleType>& buffer)
41 {
42 if (! buffer.hasSampleRate())
43 HART_THROW_OR_RETURN_VOID (hart::SampleRateError, "Audio buffer has no sample rate metadata");
44
45 m_sampleRateHz = buffer.getSampleRateHz();
46 m_numChannels = buffer.getNumChannels();
47 m_data.resize (m_numChannels);
48
49 const size_t numFrames = buffer.getNumFrames();
50 m_sizeInTimeDomainFrames = numFrames;
51
52 m_fftSize = nextPowerOfTwo (std::max<size_t> (1, numFrames));
53 m_numBins = m_fftSize / 2 + 1;
54
55 for (size_t channel = 0; channel < m_numChannels; ++channel)
56 {
57 m_data[channel].resize (m_numBins);
58 std::vector<std::complex<double>> fftBuffer (m_fftSize);
59 const SampleType* samples = buffer[channel];
60
61 for (size_t frame = 0; frame < numFrames; ++frame)
62 fftBuffer[frame] = static_cast<double> (samples[frame]);
63
64 for (size_t frame = numFrames; frame < m_fftSize; ++frame)
65 fftBuffer[frame] = 0.0;
66
67 performFFT (fftBuffer, false);
68
69 for (size_t bin = 0; bin < m_numBins; ++bin)
70 m_data[channel][bin] = fftBuffer[bin];
71 }
72 }
73
75 {
76 Spectrum spectrum;
77
78 if (sampleRateHz < 0)
79 HART_THROW_OR_RETURN_VOID (hart::SampleRateError, "Sample rate should not be negative");
80
81 spectrum.m_sampleRateHz = sampleRateHz;
82
83 // We can allow zero channels or zero frames at some point, but need to add
84 // guards to all methods that might get zero division errors as a result
85
86 if (numChannels == 0)
87 HART_THROW_OR_RETURN_VOID (hart::SizeError, "Number of channels should not be zero");
88
89 if (signalDurationFrames == 0)
90 HART_THROW_OR_RETURN_VOID (hart::SizeError, "Signal duration should not be zero");
91
92 spectrum.m_fftSize = nextPowerOfTwo (signalDurationFrames);
93 spectrum.m_numBins = spectrum.m_fftSize / 2 + 1;
94 spectrum.m_sizeInTimeDomainFrames = signalDurationFrames;
95
96 spectrum.m_numChannels = numChannels;
97 spectrum.m_data.resize (
98 spectrum.m_numChannels,
99 std::vector<std::complex<double>> (spectrum.m_numBins, std::complex<double> (0.0, 0.0))
100 );
101
102 return spectrum;
103 }
104
105 /// @brief Common values for `beta` argument in the `colouredNoise()` factory
107 {
108 whiteNoise = 0, ///< `beta` value for white noise, for the `colouredNoise()` factory
109 pinkNoise = -1, ///< `beta` value for pink noise, for the `colouredNoise()` factory
110 brownNoise = -2, ///< `beta` value for brown (a.k.a. red, or Brownian) noise, for the `colouredNoise()` factory
111 blueNoise = 1, ///< `beta` value for blue noise, for the `colouredNoise()` factory
112 violetNoise = 2 ///< `beta` value for violet noise, for the `colouredNoise()` factory
113 };
114
115 /// @brief Options for `colouredNoise()` method
117 {
118 public:
119 /// @brief Creates ColouredNoise with a custom `beta` value
120 /// @details For common noise types, use provided factory methods:
121 ///
122 /// Noise type | Method | `beta`
123 /// --------------------|------------|-------
124 /// White | `white()` | 0
125 /// Pink | `pink()` | −1
126 /// Brown/Red/Brownian | `brown()` | −2
127 /// Blue | `blue()` | +1
128 /// Violet | `violet()` | +2
129 ///
130 /// @param beta Power-spectrum exponent (slope):
131 /// @return Chainable `ColouredNoise` options instance
132 explicit ColouredNoise (float beta)
133 {
134 m_beta = beta;
135 }
136
137 /// @brief Creates ColouredNoise instance with `beta` value for white noise
138 /// @return Chainable `ColouredNoise` options instance
140 {
141 return ColouredNoise (0.0);
142 }
143
144 /// @brief Creates ColouredNoise instance with `beta` value for pink noise
145 /// @return Chainable `ColouredNoise` options instance
147 {
148 return ColouredNoise (-1.0);
149 }
150
151 /// @brief Creates ColouredNoise instance with `beta` value for brown (a.k.a. red, or Brownian) noise
152 /// @return Chainable `ColouredNoise` options instance
154 {
155 return ColouredNoise (-2.0);
156 }
157
158 /// @brief Creates ColouredNoise instance with `beta` value for blue noise
159 /// @return Chainable `ColouredNoise` options instance
161 {
162 return ColouredNoise (1.0);
163 }
164
165 /// @brief Creates ColouredNoise instance with `beta` value for violet noise
166 /// @return Chainable `ColouredNoise` options instance
168 {
169 return ColouredNoise (2.0);
170 }
171
172 /// @brief Sets random seed
173 /// @param randomSeed RNG seed. While magnitudes will have ideal
174 /// values, the real and imaginary parts of bins will be randomized.
175 ///
176 /// Defaults to global default random seed.
177 /// @return Chainable `ColouredNoise` options instance
178 ColouredNoise withRandomSeed (uint_fast32_t randomSeed)
179 {
180 m_randomSeed = randomSeed;
181 return *this;
182 }
183
184 /// @brief Sets the lowest meaningful frequency in the spectrum
185 /// @param lowCutoffFrequencyHz The starting frequency of the noise.
186 /// Defaults to 20 Hz.
187 /// @note Bins below it will be zero. Must be at least 1 Hz.
188 /// @return Chainable `ColouredNoise` options instance
189 ColouredNoise withLowCutoff (double lowCutoffFrequencyHz)
190 {
191 if (lowCutoffFrequencyHz < 1.0)
192 HART_THROW_OR_RETURN (hart::ValueError, "Low cutoff frequency should be at least 1 Hz", *this);
193
194 m_lowCutoffFrequencyHz = lowCutoffFrequencyHz;
195 return *this;
196 }
197
198 /// @brief Sets number of channels
199 /// @note Each channel will contain a difference noise instance
200 /// @param numChannels Number of channels in the generated spectrum
201 ///
202 /// Defaults to global default number of input channels.
203 ColouredNoise withNumChannels (size_t numChannels)
204 {
205 m_numChannels = numChannels;
206 return *this;
207 }
208
209 /// @brief Sets duration of time-domain audio that spectrum will represent
210 /// @param signalDurationFrames Desired duration of the signal this
211 /// noise represents. Can be arbitrary. If not a power of 2, the
212 /// spectrum will be padded to the next power of 2, but if converted
213 /// to time domain via `toAudioBuffer()`, the desired duration will
214 /// be respected.
215 ///
216 /// Defaults to global default render duration.
217 /// @return Chainable `ColouredNoise` options instance
218 ColouredNoise withDuration (size_t signalDurationFrames)
219 {
220 m_signalDurationFrames = signalDurationFrames;
221 return *this;
222 }
223
224 /// @brief Sets sample rate
225 /// @param sampleRateHz Sample rate of the generated spectrum in Hertz.
226 ///
227 /// Defaults to global default sample rate.
228 /// @return Chainable `ColouredNoise` options instance
229 ColouredNoise withSampleRate (double sampleRateHz)
230 {
231 if (sampleRateHz < 0.0 || floatsEqual (sampleRateHz, 0.0) || std::isnan (sampleRateHz))
232 HART_THROW_OR_RETURN (hart::SampleRateError, "Invalid sample rate value", *this);
233
234 m_sampleRateHz = sampleRateHz;
235 return *this;
236 }
237
238 ///@private
239 double getBeta() { return m_beta; }
240
241 ///@private
242 double getLowCutoffHz() { return m_lowCutoffFrequencyHz; }
243
244 ///@private
245 double getSampleRateHz() { return m_sampleRateHz; }
246
247 ///@private
248 uint_fast32_t getRandomSeed() { return m_randomSeed; }
249
250 ///@private
251 size_t getNumChannels() { return m_numChannels; }
252
253 ///@private
254 size_t getDurationFrames() { return m_signalDurationFrames; }
255
256 private:
257 double m_beta = 0.0;
258 double m_lowCutoffFrequencyHz = 20.0;
260 uint_fast32_t m_randomSeed = CLIConfig::getInstance().getRandomSeed();
262 size_t m_signalDurationFrames = CLIConfig::getInstance().getDefaultRenderDurationFrames();
263
264 ColouredNoise() = default; // Beta must always be explicitly defined
265 };
266
267 /// @brief Creates deterministic coloured noise in the frequency domain
268 /// @details `beta` is interpreted as a power-spectrum exponent, so the
269 /// stored bin magnitudes follow:
270 /// @f[
271 /// |X(f)| \propto {f_{rel}}^ \frac{\beta}{2}
272 /// @f]
273 /// (|X(f)| ~ f_rel ** (beta / 2)),
274 ///
275 /// where f_rel = frequency / lowCutoffHz.
276 /// @param options `ColouredNoise` containing
278 {
279 Spectrum spectrum = Spectrum::zeros (options.getNumChannels(), options.getDurationFrames(), options.getSampleRateHz());
280 std::mt19937 randomNumberGenerator (options.getRandomSeed());
281 std::uniform_real_distribution<double> phaseDistribution (0.0, hart::twoPi);
282 std::uniform_int_distribution<int> signDistribution (0, 1);
283
284 const double lowCutoffHz = options.getLowCutoffHz();
285 const double beta = options.getBeta();
286 const double magnitudeExponent = beta / 2.0;
287
288 for (size_t channel = 0; channel < spectrum.m_numChannels; ++channel)
289 {
290 for (size_t bin = 0; bin < spectrum.m_numBins; ++bin)
291 {
292 const double frequencyHz = spectrum.getBinFrequencyHz (bin);
293
294 if (frequencyHz < lowCutoffHz || (frequencyHz == 0.0 && beta < 0.0))
295 {
296 spectrum.m_data[channel][bin] = std::complex<double> (0.0, 0.0);
297 continue;
298 }
299
300 const double magnitude = std::pow (frequencyHz / lowCutoffHz, magnitudeExponent);
301
302 if (bin == 0 || (spectrum.m_fftSize % 2 == 0 && bin == spectrum.m_numBins - 1))
303 {
304 const double signedMagnitude = signDistribution (randomNumberGenerator) == 0 ? -magnitude : magnitude;
305 spectrum.m_data[channel][bin] = std::complex<double> (signedMagnitude, 0.0);
306 continue;
307 }
308
309 const double phase = phaseDistribution (randomNumberGenerator);
310 spectrum.m_data[channel][bin] = std::complex<double> (magnitude * std::cos (phase), magnitude * std::sin (phase));
311 }
312 }
313
314 return spectrum;
315 }
316
317 /// @brief Returns number of channels
318 size_t getNumChannels() const
319 {
320 return m_numChannels;
321 }
322
323 /// @brief Returns number of frequency bins per channel
324 size_t getNumBins() const
325 {
326 return m_numBins;
327 }
328
329 /// @brief Returns FFT size
330 size_t getFFTSize() const
331 {
332 return m_fftSize;
333 }
334
335 /// @brief Returns original audio buffer duration in frames
337 {
338 return m_sizeInTimeDomainFrames;
339 }
340
341 /// @brief Returns sample rate in Hz
342 double getSampleRateHz() const
343 {
344 return m_sampleRateHz;
345 }
346
347 /// @brief Converts this spectrum to a time-domain audio buffer
348 /// @details
349 /// Reconstructs the omitted negative-frequency bins from Hermitian symmetry,
350 /// performs an inverse FFT, and trims the result to the original signal length.
351 /// @tparam SampleType Type of samples for the resulting buffer, typically
352 /// `float` or `double`
353 template <typename SampleType>
354 AudioBuffer<SampleType> toAudioBuffer() const
355 {
356 AudioBuffer<SampleType> buffer (m_numChannels, m_sizeInTimeDomainFrames, m_sampleRateHz);
357
358 for (size_t channel = 0; channel < m_numChannels; ++channel)
359 {
360 std::vector<std::complex<double>> fftBuffer (m_fftSize, std::complex<double> (0.0, 0.0));
361
362 for (size_t bin = 0; bin < m_numBins; ++bin)
363 fftBuffer[bin] = m_data[channel][bin];
364
365 for (size_t bin = 1; bin + 1 < m_numBins; ++bin)
366 fftBuffer[m_fftSize - bin] = std::conj (m_data[channel][bin]);
367
368 performFFT (fftBuffer, true);
369 SampleType* channelData = buffer[channel];
370
371 for (size_t frame = 0; frame < m_sizeInTimeDomainFrames; ++frame)
372 channelData[frame] = static_cast<SampleType> (fftBuffer[frame].real());
373 }
374
375 return buffer;
376 }
377
378 /// @brief Converts the audio represented by this Spectrum to a Signal source
379 /// @param loop Whether the signal should loop after the data represented by
380 /// this Spectrum's implied diration runs out
381 /// @tparam SampleType Type of sample sata generated by the signal, typically
382 /// `float` or `double`
383 /// @return `AudioBufferSignal` instance, playing the signal with the spectrum
384 /// identical to this one
385 template <typename SampleType>
386 AudioBufferSignal<SampleType> toSignal (Loop loop = Loop::no) const
387 {
388 return toAudioBuffer<SampleType>().toSignal (loop);
389 }
390
391 /// @brief Checks whether this spectrum contains approximately the same data as another spectrum
392 /// @details The comparison is relaxed - each pair of corresponding complex bins must differ by
393 /// no more than `toleranceLinear`. Both spectra must have the same dimensions, FFT size,
394 /// time-domain size, and sample rate.
395 /// @param other Spectrum to compare against
396 /// @param toleranceLinear Absolute complex-bin tolerance in linear domain,
397 /// for both real and imaginary parts.
398 /// @return `true` if the spectra are equal within the specified tolerance, `false` otherwise
399 bool equalsTo (const Spectrum& other, double toleranceLinear = 1e-6) const
400 {
401 if (m_numChannels != other.m_numChannels
402 || m_fftSize != other.m_fftSize
403 || m_numBins != other.m_numBins
404 || m_sizeInTimeDomainFrames != other.m_sizeInTimeDomainFrames)
405 return false;
406
407 if (! floatsEqual (m_sampleRateHz, other.m_sampleRateHz))
408 return false;
409
410 for (size_t channel = 0; channel < m_numChannels; ++channel)
411 {
412 for (size_t bin = 0; bin < m_numBins; ++bin)
413 {
414 const std::complex<double> thisValue = m_data[channel][bin];
415 const std::complex<double> otherValue = other.m_data[channel][bin];
416
417 if (std::abs (std::real (thisValue - otherValue)) > toleranceLinear || std::abs (std::imag (thisValue - otherValue)) > toleranceLinear)
418 return false;
419 }
420 }
421
422 return true;
423 }
424
425 /// @brief Checks whether two spectra contain approximately the same data
426 /// @details Equivalent to calling `Spectrum::equalsTo (other)` with the default tolerance
427 /// @param other Spectrum to compare against
428 /// @return `true` if the spectra are equal within the default tolerance, `false` otherwise
429 bool operator== (const Spectrum& other) const
430 {
431 return equalsTo (other);
432 }
433
434 /// @brief Checks whether two spectra differ beyond the default comparison tolerance
435 /// @details Equivalent to `! Spectrum::equalsTo (other)`
436 /// @param other Spectrum to compare against
437 /// @return `true` if the spectra are not equal within the default tolerance, `false` otherwise
438 bool operator!= (const Spectrum& other) const
439 {
440 return ! equalsTo (other);
441 }
442
443 /// @brief Creates a new spectrum as a multiplying each complex bin value by a provided gain value
444 /// @param other gainLinear Gain to multiply each complex bin by
445 /// @return A new `Spectrum` that is a result of multiplication
446 Spectrum operator* (double gainLinear) const
447 {
448 Spectrum tmp (*this);
449
450 for (size_t channel = 0; channel < tmp.getNumChannels(); ++channel)
451 {
452 std::complex<double>* channelData = tmp[channel];
453
454 for (size_t bin = 0; bin < tmp.getNumBins(); ++bin)
455 channelData[bin] *= gainLinear;
456 }
457
458 return tmp;
459 }
460
461 /// @brief Returns frequency corresponding to a bin index
462 double getBinFrequencyHz (size_t binIndex) const
463 {
464 if (binIndex >= m_numBins)
465 HART_THROW_OR_RETURN (hart::IndexError, "Bin index is out of range", hart::nan<double>());
466
467 return static_cast<double> (binIndex) * m_sampleRateHz / m_fftSize;
468 }
469
470 double getBinWidthHz() const
471 {
472 return m_sampleRateHz / m_fftSize;
473 }
474
475 /// @brief Returns complex value of a frequency bin, by bin index
476 std::complex<double> getBinValue (size_t channel, size_t binIndex) const
477 {
478 if (channel >= m_numChannels)
479 HART_THROW_OR_RETURN (hart::IndexError, "Channel index is out of range", hart::nan<double>());
480
481 if (binIndex >= m_numBins)
482 HART_THROW_OR_RETURN (hart::IndexError, "Bin index is out of range", hart::nan<double>());
483
484 return m_data[channel][binIndex];
485 }
486
487 /// @brief Returns complex value of a frequency bin, by frequency
488 std::complex<double> getBinValue (size_t channel, double frequencyHz) const
489 {
490 return getBinValue (channel, findClosestBin (frequencyHz));
491 }
492
493 /// @brief Returns magnitude of a frequency bin, by bin index
494 double getBinMagnitude (size_t channel, size_t binIndex) const
495 {
496 return std::abs (getBinValue (channel, binIndex));
497 }
498
499 /// @brief Returns magnitude of a frequency bin, by frequency
500 double getBinMagnitude (size_t channel, double frequencyHz) const
501 {
502 return getBinMagnitude (channel, findClosestBin (frequencyHz));
503 }
504
505 /// @brief Returns pointer to read-only magnitudes of a specific channel
506 const std::complex<double>* operator[] (size_t channel) const
507 {
508 if (channel >= m_numChannels)
509 HART_THROW_OR_RETURN (hart::IndexError, "Channel index is out of range", hart::nan<double>());
510
511 return m_data[channel].data();
512 }
513
514 /// @brief Returns pointer to mutable magnitudes of a specific channel
515 std::complex<double>* operator[] (size_t channel)
516 {
517 if (channel >= m_numChannels)
518 HART_THROW_OR_RETURN (hart::IndexError, "Channel index is out of range", hart::nan<double>());
519
520 return m_data[channel].data();
521 }
522
523 /// @brief Finds closest FFT bin to a given frequency
524 size_t findClosestBin (double frequencyHz) const
525 {
526 if (frequencyHz < 0.0)
527 HART_THROW_OR_RETURN (hart::ValueError, "This is one-sided spectrum, frequencyHz should be a positive value", 0);
528
529 const size_t bin = roundToSizeT (frequencyHz * m_fftSize / m_sampleRateHz);
530 return std::min (bin, m_numBins - 1);
531 }
532
533 /// @brief Returns a pair of indices representing a provided slice
534 /// @param slice A Slice instance. Valid slice types are `Slice::Type::whole`
535 /// `Slice::Type::bins` and `Slice::Type::freq`
536 /// @retval first Index representing the beginning of the range, inclusive
537 /// @retval second Index representing the end of the range, non-inclusive
538 std::pair<size_t, size_t> getBinIndices (const Slice& slice) const
539 {
540 const size_t numBins = getNumBins();
541
542 switch (slice.type)
543 {
545 {
546 return {0, numBins};
547 }
548
550 {
551 const size_t startBin = static_cast<size_t> (slice.start);
552 const size_t stopBin = static_cast<size_t> (slice.stop);
553
554 return {
555 std::min (startBin, numBins),
556 std::min (stopBin, numBins)
557 };
558 }
559
561 {
562 const size_t startBin = findClosestBin (slice.start);
563 const size_t stopBin = findClosestBin (slice.stop);
564
565 return {
566 std::min (startBin, numBins),
567 std::min (stopBin, numBins)
568 };
569 }
570
571 default:
572 {
573 HART_THROW_OR_RETURN (hart::UnitError, "Slice type cannot be interpreted as bin range", std::make_pair (0, numBins));
574 }
575 }
576 }
577
578 /// @brief Prints readable representation of the spectrum object
579 /// @param stream String stream to append the representation to
580 void represent (std::ostream& stream) const
581 {
582 stream << "<Spectrum>";
583 }
584
585 /// @brief Prints readable text representation of the spectrum object into the I/O stream
586 /// @relates Spectrum
587 friend std::ostream& operator<< (std::ostream& stream, const Spectrum& spectrum)
588 {
589 spectrum.represent (stream);
590 return stream;
591 }
592
593private:
594 Spectrum() = default;
595
596 static void performFFT (std::vector<std::complex<double>>& data, bool isInverse)
597 {
598 const size_t n = data.size();
599
600 hassert (n != 0);
601 hassert ((n & (n - 1)) == 0); // Size should be a power of 2
602
603 size_t j = 0;
604
605 for (size_t i = 1; i < n; ++i)
606 {
607 size_t bit = n >> 1;
608
609 while (j & bit)
610 {
611 j ^= bit;
612 bit >>= 1;
613 }
614
615 j ^= bit;
616
617 if (i < j)
618 std::swap (data[i], data[j]);
619 }
620
621 for (size_t len = 2; len <= n; len <<= 1)
622 {
623 const double angle = (isInverse ? hart::twoPi : -hart::twoPi) / static_cast<double> (len);
624 const std::complex<double> wlen (std::cos (angle), std::sin (angle));
625
626 for (size_t i = 0; i < n; i += len)
627 {
628 std::complex<double> w (1.0, 0.0);
629
630 for (size_t jj = 0; jj < len / 2; ++jj)
631 {
632 const std::complex<double> u = data[i + jj];
633 const std::complex<double> v = data[i + jj + len / 2] * w;
634
635 data[i + jj] = u + v;
636 data[i + jj + len / 2] = u - v;
637
638 w *= wlen;
639 }
640 }
641 }
642
643 if (isInverse)
644 {
645 const double scale = 1.0 / static_cast<double> (n);
646
647 for (size_t i = 0; i < n; ++i)
648 data[i] *= scale;
649 }
650 }
651
652 double m_sampleRateHz = 0.0;
653
654 size_t m_numChannels = 0;
655 size_t m_fftSize = 0;
656 size_t m_numBins = 0;
657 size_t m_sizeInTimeDomainFrames = 0;
658
659 std::vector<std::vector<std::complex<double>>> m_data;
660};
661
662} // namespace hart
Thrown when a container index is out of range.
Thrown when sample rate is mismatched or invalid.
Thrown when an unexpected container size is encountered.
Options for colouredNoise() method.
ColouredNoise withNumChannels(size_t numChannels)
Sets number of channels.
static ColouredNoise violet()
Creates ColouredNoise instance with beta value for violet noise.
ColouredNoise withSampleRate(double sampleRateHz)
Sets sample rate.
ColouredNoise(float beta)
Creates ColouredNoise with a custom beta value.
static ColouredNoise brown()
Creates ColouredNoise instance with beta value for brown (a.k.a. red, or Brownian) noise.
static ColouredNoise blue()
Creates ColouredNoise instance with beta value for blue noise.
static ColouredNoise white()
Creates ColouredNoise instance with beta value for white noise.
static ColouredNoise pink()
Creates ColouredNoise instance with beta value for pink noise.
ColouredNoise withRandomSeed(uint_fast32_t randomSeed)
Sets random seed.
ColouredNoise withDuration(size_t signalDurationFrames)
Sets duration of time-domain audio that spectrum will represent.
ColouredNoise withLowCutoff(double lowCutoffFrequencyHz)
Sets the lowest meaningful frequency in the spectrum.
Frequency-domain representation of a multi-channel audio signal.
const std::complex< double > * operator[](size_t channel) const
Returns pointer to read-only magnitudes of a specific channel.
Spectrum(const AudioBuffer< SampleType > &buffer)
Constructs spectrum from an audio buffer.
bool equalsTo(const Spectrum &other, double toleranceLinear=1e-6) const
Checks whether this spectrum contains approximately the same data as another spectrum.
std::complex< double > getBinValue(size_t channel, double frequencyHz) const
Returns complex value of a frequency bin, by frequency.
size_t getFFTSize() const
Returns FFT size.
double getSampleRateHz() const
Returns sample rate in Hz.
std::pair< size_t, size_t > getBinIndices(const Slice &slice) const
Returns a pair of indices representing a provided slice.
size_t getSizeInTimeDomainFrames() const
Returns original audio buffer duration in frames.
double getBinFrequencyHz(size_t binIndex) const
Returns frequency corresponding to a bin index.
bool operator!=(const Spectrum &other) const
Checks whether two spectra differ beyond the default comparison tolerance.
Spectrum operator*(double gainLinear) const
Creates a new spectrum as a multiplying each complex bin value by a provided gain value.
double getBinMagnitude(size_t channel, size_t binIndex) const
Returns magnitude of a frequency bin, by bin index.
AudioBufferSignal< SampleType > toSignal(Loop loop=Loop::no) const
Converts the audio represented by this Spectrum to a Signal source.
size_t getNumBins() const
Returns number of frequency bins per channel.
void represent(std::ostream &stream) const
Prints readable representation of the spectrum object.
static Spectrum colouredNoise(ColouredNoise options)
Creates deterministic coloured noise in the frequency domain.
std::complex< double > getBinValue(size_t channel, size_t binIndex) const
Returns complex value of a frequency bin, by bin index.
BetaFor
Common values for beta argument in the colouredNoise() factory.
@ brownNoise
beta value for brown (a.k.a. red, or Brownian) noise, for the colouredNoise() factory
@ violetNoise
beta value for violet noise, for the colouredNoise() factory
@ pinkNoise
beta value for pink noise, for the colouredNoise() factory
@ blueNoise
beta value for blue noise, for the colouredNoise() factory
@ whiteNoise
beta value for white noise, for the colouredNoise() factory
size_t getNumChannels() const
Returns number of channels.
size_t findClosestBin(double frequencyHz) const
Finds closest FFT bin to a given frequency.
std::complex< double > * operator[](size_t channel)
Returns pointer to mutable magnitudes of a specific channel.
double getBinWidthHz() const
static Spectrum zeros(size_t numChannels=CLIConfig::getInstance().getDefaultNumInputChannels(), size_t signalDurationFrames=CLIConfig::getInstance().getDefaultRenderDurationFrames(), double sampleRateHz=CLIConfig::getInstance().getDefaultSampleRateHz())
double getBinMagnitude(size_t channel, double frequencyHz) const
Returns magnitude of a frequency bin, by frequency.
bool operator==(const Spectrum &other) const
Checks whether two spectra contain approximately the same data.
AudioBuffer< SampleType > toAudioBuffer() const
Converts this spectrum to a time-domain audio buffer.
Thrown when some metric is requested to return a value in an unsupported unit.
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 ...
constexpr double twoPi
2 * pi
static size_t roundToSizeT(SampleType x)
Rounds a floating point value to a size_t value.
static size_t nextPowerOfTwo(size_t x)
Finds next power of 2 after a non-negative number x.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
Loop
Helper values for something that could loop, like a Signal.
Holds values set by the user via CLI interface.
uint_fast32_t getRandomSeed() const
Gets random seed set by a "`--seed`/`-s`" argument.
size_t getDefaultNumInputChannels() const
double getDefaultSampleRateHz() const
static CLIConfig & getInstance()
Get the singleton instance.
size_t getDefaultRenderDurationFrames() const
Represents a slice of analysis data.