HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_sine_sweep.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4
7#include "signals/hart_signal.hpp"
8#include "hart_utils.hpp"
9
10namespace hart {
11
12/// @brief Produces a sine sweep
13/// @details Outputs a signal at unity gain (-1.0..+1.0), linear or log sweep, up or down.
14/// Tip: If you want to get an low-high-low or a high-low-high sweep, set `loop` to Loop::yes,
15/// and duration of host's signal (see @ref AudioTestBuilder::withDuration())
16/// to 2x ```durationSeconds``` of this signal.
17/// @ingroup Signals
18template<typename SampleType>
20 public Signal<SampleType, SineSweep<SampleType>>
21{
22public:
23 /// @brief Determines what to do after frequency sweep is done
24 enum class Loop
25 {
26 no, ///< Stop after finishing one sweep
27 yes ///< Keep on sweeping back and forth
28 };
29
30 /// @brief Determines how to change the frequency
31 enum class SweepType
32 {
33 linear, ///< Linear sweep, for a white noise-like spectrum
34 log ///< Logarithmic sweep, for a pink noise-like spectrum
35 };
36
37 /// @brief Creates a sine sweep signal
38 /// @param durationSeconds Duration of sine sweep
39 /// @param startFrequencyHz Start frequency of the sine sweep
40 /// @param endFrequencyHz End frequency of the sine sweep
41 /// @param type Linear or Log frequency sweep, see @ref SweepType
42 /// @param loop If Loop::no is selected, the Signal will produce silence after duration;
43 /// if Loop::yes is selected, the signal will keep on going back and forth between
44 /// start and end frequencies (up and down) indefinitely.
45 /// @param initialPhaseRadians Initial phase of the signal
47 double durationSeconds = 1.0,
48 double startFrequencyHz = 20.0,
49 double endFrequencyHz = 20.0e3,
50 SweepType type = SweepType::log,
51 Loop loop = Loop::no,
52 double initialPhaseRadians = 0.0
53 ):
54 m_durationSeconds (durationSeconds),
55 m_startFrequencyHz (startFrequencyHz),
56 m_endFrequencyHz (endFrequencyHz),
57 m_type (type),
58 m_loop (loop),
59 m_initialPhaseRadians (wrapPhase (initialPhaseRadians)),
60 m_currentPhaseRadians (m_initialPhaseRadians),
61 m_generateSilence (floatsEqual (durationSeconds, 0.0)),
62 m_isFixedFrequency (floatsEqual (m_startFrequencyHz, m_endFrequencyHz)),
63 m_frequencyRatio (m_endFrequencyHz / m_startFrequencyHz)
64 {
65 if (durationSeconds < 0)
66 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Duration cannot be negative");
67
68 if (startFrequencyHz <= 0 || endFrequencyHz <= 0)
69 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Frequencies must be positive");
70 }
71
72 /// @brief Returns a new SineSweep instance with specified duration
73 /// @details Handy if you want to skip specifying some arguments in the constructor
74 /// @param durationSeconds Duration of sine sweep
75 /// @return A new SineSweep instance with a specified parameter
76 SineSweep withDuration (double durationSeconds)
77 {
78 return SineSweep (durationSeconds, m_startFrequencyHz, m_endFrequencyHz, m_type, m_loop, m_initialPhaseRadians);
79 }
80
81 /// @brief Returns a new SineSweep instance with specified start frequency
82 /// @details Handy if you want to skip specifying some arguments in the constructor
83 /// @param startFrequencyHz Start frequency of the sine sweep
84 /// @return A new SineSweep instance with a specified parameter
85 SineSweep withStartFrequency (double startFrequencyHz)
86 {
87 return SineSweep (m_durationSeconds, startFrequencyHz, m_endFrequencyHz, m_type, m_loop, m_initialPhaseRadians);
88 }
89
90 /// @brief Returns a new SineSweep instance with specified end frequency
91 /// @details Handy if you want to skip specifying some arguments in the constructor
92 /// @param endFrequencyHz End frequency of the sine sweep
93 /// @return A new SineSweep instance with a specified parameter
94 SineSweep withEndFrequency (double endFrequencyHz)
95 {
96 return SineSweep (m_durationSeconds, m_startFrequencyHz, endFrequencyHz, m_type, m_loop, m_initialPhaseRadians);
97 }
98
99 /// @brief Returns a new SineSweep instance with specified sweep type
100 /// @details Handy if you want to skip specifying some arguments in the constructor
101 /// @param type Linear or Log frequency sweep, see @ref SweepType
102 /// @return A new SineSweep instance with a specified parameter
104 {
105 return SineSweep (m_durationSeconds, m_startFrequencyHz, m_endFrequencyHz, type, m_loop, m_initialPhaseRadians);
106 }
107
108 /// @brief Returns a new SineSweep instance with specified loop preference
109 /// @details Handy if you want to skip specifying some arguments in the constructor
110 /// @param loop If Loop::no is selected, the Signal will produce silence after duration;
111 /// if Loop::yes is selected, the signal will keep on going back and forth between
112 /// start and end frequencies (up and down) indefinitely.
113 /// @return A new SineSweep instance with a specified parameter
115 {
116 return SineSweep (m_durationSeconds, m_startFrequencyHz, m_endFrequencyHz, m_type, loop, m_initialPhaseRadians);
117 }
118
119 /// @brief Returns a new SineSweep instance with specified initial phase
120 /// @details Handy if you want to skip specifying some arguments in the constructor
121 /// @param initialPhaseRadians Initial phase of the signal
122 /// @return A new SineSweep instance with a specified parameter
123 SineSweep withPhase (double initialPhaseRadians)
124 {
125 return SineSweep (m_durationSeconds, m_startFrequencyHz, m_endFrequencyHz, m_type, m_loop, initialPhaseRadians);
126 }
127
128 bool supportsNumChannels (size_t /*numChannels*/) const override { return true; }
129
130 void prepare (double sampleRateHz, size_t /*numOutputChannels*/, size_t /*maxBlockSizeFrames*/) override
131 {
132 m_sampleRateHz = sampleRateHz;
133 m_durationFrames = roundToSizeT (m_durationSeconds * m_sampleRateHz);
134 }
135
136 void renderNextBlock (AudioBuffer<SampleType>& output) override
137 {
138 if (m_generateSilence)
139 {
140 fillWithSilence (output);
141 return;
142 }
143
144 for (size_t frame = 0; frame < output.getNumFrames(); ++frame)
145 {
146 const SampleType value = (SampleType) std::sin (m_currentPhaseRadians);
147
148 for (size_t channel = 0; channel < output.getNumChannels(); ++channel)
149 output[channel][frame] = value;
150
151 ++m_posFrames;
152
153 if (m_posFrames == m_durationFrames)
154 {
155 if (m_loop == Loop::yes)
156 {
157 // Reverse frequency direction
158 m_reverseFrequencyDirection = ! m_reverseFrequencyDirection;
159 m_posFrames = 0;
160 }
161 else
162 {
163 // Stop generating the sine
164 fillWithSilence (output, frame + 1);
165 m_posFrames = 0;
166 m_generateSilence = true;
167 break;
168 }
169 }
170
171 const double currentFrequencyHz = frequencyAtFrame (m_posFrames, m_reverseFrequencyDirection);
172 m_currentPhaseRadians += hart::twoPi * currentFrequencyHz / m_sampleRateHz;
173 m_currentPhaseRadians = wrapPhase (m_currentPhaseRadians);
174 }
175 }
176
177 void reset() override
178 {
179 m_posFrames = 0;
180 m_currentPhaseRadians = m_initialPhaseRadians;
181 m_generateSilence = floatsEqual (m_durationSeconds, 0.0);
182 m_reverseFrequencyDirection = false;
183 }
184
185 void represent (std::ostream& stream) const override
186 {
187 stream << "SineSweep ("
188 << secPrecision
189 << m_durationSeconds << "_s, "
190 << hzPrecision
191 << m_startFrequencyHz << "_Hz, "
192 << m_endFrequencyHz << "_Hz, "
193 << (m_type == SweepType::linear ? ", SweepType::linear" : "SweepType::log")
194 << (m_loop == Loop::yes ? ", Loop::yes)" : ", Loop::no)");
195 }
196
197private:
198 const double m_durationSeconds;
199 const double m_startFrequencyHz;
200 const double m_endFrequencyHz;
201 const SweepType m_type;
202 const Loop m_loop;
203
204 double m_sampleRateHz = 0.0;
205 size_t m_durationFrames = 0;
206 size_t m_posFrames = 0;
207 const double m_initialPhaseRadians;
208 double m_currentPhaseRadians;
209 bool m_generateSilence;
210 const bool m_isFixedFrequency;
211 const double m_frequencyRatio;
212 bool m_reverseFrequencyDirection = false;
213
214 void fillWithSilence (AudioBuffer<SampleType>& output, size_t startingFrame = 0)
215 {
216 if (startingFrame >= output.getNumFrames())
217 return;
218
219 for (size_t channel = 0; channel < output.getNumChannels(); ++channel)
220 std::fill (output[channel] + startingFrame, output[channel] + output.getNumFrames(), (SampleType) 0);
221 }
222
223 double frequencyAtFrame (size_t offsetFrames, bool reverseFrequencyDirection) const
224 {
225 if (m_isFixedFrequency)
226 return m_startFrequencyHz;
227
228 hassert (offsetFrames < m_durationFrames);
229 const double offsetSeconds = static_cast<double> (offsetFrames) / m_sampleRateHz;
230
231 double portion = offsetSeconds / m_durationSeconds;
232 portion = reverseFrequencyDirection ? 1.0 - portion : portion;
233
234 if (m_type == SweepType::linear)
235 return m_startFrequencyHz + (m_endFrequencyHz - m_startFrequencyHz) * portion;
236
237 // SweepType::logaritmic
238 return m_startFrequencyHz * std::pow (m_frequencyRatio, portion);
239 }
240};
241
243
244} // namespace hart
Base class for signals.
Produces a sine sweep.
void renderNextBlock(AudioBuffer< SampleType > &output) override
Renders next block audio for the signal.
SineSweep withDuration(double durationSeconds)
Returns a new SineSweep instance with specified duration.
SineSweep withEndFrequency(double endFrequencyHz)
Returns a new SineSweep instance with specified end frequency.
SweepType
Determines how to change the frequency.
@ linear
Linear sweep, for a white noise-like spectrum.
@ log
Logarithmic sweep, for a pink noise-like spectrum.
SineSweep(double durationSeconds=1.0, double startFrequencyHz=20.0, double endFrequencyHz=20.0e3, SweepType type=SweepType::log, Loop loop=Loop::no, double initialPhaseRadians=0.0)
Creates a sine sweep signal.
void represent(std::ostream &stream) const override
Makes a text representation of this Signal for test failure outputs.
bool supportsNumChannels(size_t) const override
Tells the host whether this Signal is capable of generating audio for a certain amount of channels.
SineSweep withType(SweepType type)
Returns a new SineSweep instance with specified sweep type.
void prepare(double sampleRateHz, size_t, size_t) override
Prepare the signal for rendering.
SineSweep withLoop(Loop loop)
Returns a new SineSweep instance with specified loop preference.
void reset() override
Resets the Signal to initial state.
SineSweep withPhase(double initialPhaseRadians)
Returns a new SineSweep instance with specified initial phase.
SineSweep withStartFrequency(double startFrequencyHz)
Returns a new SineSweep instance with specified start frequency.
Loop
Determines what to do after frequency sweep is done.
@ no
Stop after finishing one sweep.
@ yes
Keep on sweeping back and forth.
std::ostream & secPrecision(std::ostream &stream)
Sets number of decimal places for values in seconds.
std::ostream & hzPrecision(std::ostream &stream)
Sets number of decimal places for values in hertz.
constexpr double twoPi
2 * pi
SampleType wrapPhase(const SampleType phaseRadians)
Keeps phase in 0..twoPi range.
static size_t roundToSizeT(SampleType x)
Rounds a floating point value to a size_t value.
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
#define hassert(condition)
#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)