HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_sawtooth.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
13/// @brief Produces a bandlimited sawtooth wave at fixed frequency
14/// @details Outputs a signal peaking at unity gain (-1.0..+1.0). Rising edge, phase = 0 corresponds to value = -1.0.
15/// Uses quadratic PolyBLEP for anti aliasing.
16/// @ingroup Signals
17template<typename SampleType>
19 public Signal<SampleType, Sawtooth<SampleType>>
20{
21public:
22 /// @brief Creates a sawtooth signal instance
23 /// @param frequencyHz Fixed frequency in Hz
24 /// @param phaseRadians Initial phase in radians
25 Sawtooth (double frequencyHz = 1000.0, double phaseRadians = 0.0):
26 m_frequencyHz (frequencyHz),
27 m_initialPhaseRadians (wrapPhase (phaseRadians)),
28 m_phaseRadians (m_initialPhaseRadians)
29 {
30 if (frequencyHz <= 0.0)
31 HART_THROW (hart::ValueError, "Invalid frequency value");
32 }
33
34 void prepare (double sampleRateHz, size_t /* numOutputChannels */, size_t /*maxBlockSizeFrames*/) override
35 {
36 m_sampleRateHz = sampleRateHz;
37 m_phaseIncrementRadians = hart::twoPi * m_frequencyHz / m_sampleRateHz;
38 m_ratio = m_frequencyHz / m_sampleRateHz;
39 updateMakeupGain();
40 }
41
42 void renderNextBlock (AudioBuffer<SampleType>& output) override
43 {
44 for (size_t frame = 0; frame < output.getNumFrames(); ++frame)
45 {
46 const double portion = m_phaseRadians / hart::twoPi;
47 SampleType value = SampleType (2.0 * portion - 1.0);
48
49 if (portion < m_ratio)
50 {
51 const double t = portion / m_ratio;
52 value += SampleType ((t - 1.0) * (t - 1.0));
53 }
54 else if (portion > 1.0 - m_ratio)
55 {
56 const double t = (portion - 1.0) / m_ratio;
57 value -= SampleType ((t + 1.0) * (t + 1.0));
58 }
59
60 value *= m_makeupGainLinear;
61
62 for (size_t channel = 0; channel < output.getNumChannels(); ++channel)
63 output[channel][frame] = value;
64
65 m_phaseRadians = wrapPhase (m_phaseRadians + m_phaseIncrementRadians);
66 }
67 }
68
69 void reset() override
70 {
71 m_phaseRadians = m_initialPhaseRadians;
72 }
73
74 void represent (std::ostream& stream) const override
75 {
76 stream << "Sawtooth ("
77 << hzPrecision << m_frequencyHz << "_Hz, "
78 << radPrecision << m_initialPhaseRadians << "_rad)";
79 }
80
81private:
82 const double m_frequencyHz;
83 const double m_initialPhaseRadians;
84 double m_phaseRadians = 0.0;
85 double m_sampleRateHz = 44100.0;
86 double m_phaseIncrementRadians = hart::twoPi * m_frequencyHz / m_sampleRateHz;
87 double m_ratio = m_frequencyHz / m_sampleRateHz;
88 SampleType m_makeupGainLinear = (SampleType) 1;
89
90 inline void updateMakeupGain()
91 {
92 // Pre-calculated poly fits for common sample rates.
93 // Generated a few cycles at a given sample rate at various sawtooth frequencies,
94 // measured sample peak at each one, calculated a quadratic fit. It's almost linear,
95 // but slightly curved, so the quadratic fit is bang on.
96
97 if (floatsEqual (m_sampleRateHz, 44100.0))
98 {
99 m_makeupGainLinear = approximateMakeupGain (0.9999792899251587, -4.535565865100019e-05, 5.14460147253294e-10);
100 return;
101 }
102
103 if (floatsEqual (m_sampleRateHz, 48000.0))
104 {
105 m_makeupGainLinear = approximateMakeupGain (0.9999806254677883, -4.16723249855034e-05, 4.342374921390162e-10);
106 return;
107 }
108
109 if (floatsEqual (m_sampleRateHz, 88200.0))
110 {
111 m_makeupGainLinear = approximateMakeupGain (0.9999858295396981, -2.267921714003928e-05, 1.287471954911674e-10);
112 return;
113 }
114
115 if (floatsEqual (m_sampleRateHz, 96000.0))
116 {
117 m_makeupGainLinear = approximateMakeupGain (0.9999827886316706, -2.089171398739896e-05, 1.118510164833643e-10);
118 return;
119 }
120
121 if (floatsEqual (m_sampleRateHz, 192000.0))
122 {
123 m_makeupGainLinear = approximateMakeupGain (1.000109987458273, -1.047641814174239e-05, 2.964600179948776e-11);
124 return;
125 }
126
127 // Uncommon sampling rate - generate a few cycles and observe sample peak value.
128 m_makeupGainLinear = measureMakeupGain();
129 }
130
131 inline SampleType approximateMakeupGain (double a0, double a1, double a2)
132 {
133 return static_cast<SampleType> (1.0 / (m_frequencyHz * (a2 * m_frequencyHz + a1) + a0));
134 }
135
136 SampleType measureMakeupGain()
137 {
138 const double cyclesToMeasure = 32.0;
139 const size_t framesToMeasure = static_cast<size_t> (cyclesToMeasure * m_sampleRateHz / m_frequencyHz);
140 const double originalPhaseRadians = m_phaseRadians;
141
142 AudioBuffer<SampleType> buffer (1, framesToMeasure);
143 renderNextBlock (buffer);
144
145 SampleType peakLinear = (SampleType) 0;
146
147 for (size_t i = 0; i < framesToMeasure; ++i)
148 peakLinear = std::max (peakLinear, std::abs (buffer[0][i]));
149
150 m_phaseRadians = originalPhaseRadians;
151 return static_cast<SampleType> ((peakLinear > (SampleType) 0) ? SampleType (1) / peakLinear : (SampleType) 1);
152 }
153};
154
156
157} // namespace hart
Produces a bandlimited sawtooth wave at fixed frequency.
void renderNextBlock(AudioBuffer< SampleType > &output) override
Renders next block audio for the signal.
Sawtooth(double frequencyHz=1000.0, double phaseRadians=0.0)
Creates a sawtooth signal instance.
void represent(std::ostream &stream) const override
Makes a text representation of this Signal for test failure outputs.
void prepare(double sampleRateHz, size_t, size_t) override
Prepare the signal for rendering.
void reset() override
Resets the Signal to initial state.
Base class for signals.
std::ostream & radPrecision(std::ostream &stream)
Sets number of decimal places for values in radians.
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 SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
#define HART_THROW(ExceptionType, message)
#define HART_SIGNAL_DECLARE_ALIASES_FOR(ClassName)