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