HART  0.1.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_segmentedenvelope.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <memory>
4#include <cmath> // pow(), abs()
5
6#include "envelopes/hart_envelope.hpp"
8#include "hart_utils.hpp"
9
10namespace hart
11{
12
13/// @brief A simple envelope constructed from semgents
14/// @ingroup Envelopes
16 public Envelope
17{
18public:
19 // TODO: Log curve?
20 /// @brief Determines a shape of ramp curve
21 enum class Shape
22 {
23 linear, ///< Linear curve
24 exponential, ///< Exponential curve
25 sCurve ///< S-curve
26 };
27
28 /// @brief Creates a segmented envelope instance
29 /// @param startValue Initial value of the envelope, in any unit
30 SegmentedEnvelope (double startValue):
31 m_resetValue (startValue),
32 m_beginValue (startValue),
33 m_endValue (startValue),
34 m_currentValue (startValue)
35 {
36 }
37
38 void renderNextBlock (size_t blockSize, std::vector<double>& valuesOutput) override
39 {
40 if (valuesOutput.size() != blockSize)
41 {
42 HART_WARNING ("Make sure to configure your envelope container size before processing audio");
43 valuesOutput.resize (blockSize);
44 }
45
46 for (size_t i = 0; i < blockSize; ++i)
47 {
48 advance (m_frameTimeSeconds);
49 valuesOutput[i] = m_currentValue;
50 }
51 }
52
53 void prepare (double sampleRateHz, size_t /* maxBlockSizeFrames */) override
54 {
55 if (sampleRateHz < 0 || floatsEqual (sampleRateHz, 0.0))
56 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Illegal sample rate value");
57
58 m_frameTimeSeconds = 1.0 / sampleRateHz;
59 }
60
61 void reset() override
62 {
63 m_currentTimeSeconds = 0.0;
64 m_currentSegmentIndex = 0;
65 m_currentValue = m_resetValue;
66 }
67
68 /// @brief Adds a flat horizontal section to the envelope
69 /// @param durationSeconds Duration of the new segment in seconds
70 SegmentedEnvelope& hold (double durationSeconds)
71 {
72 //m_segments.emplace_back (durationSeconds, m_endValue, Shape::linear, true);
73 m_segments.push_back (Segment { durationSeconds, m_endValue, Shape::linear, true });
74 return *this;
75 }
76
77 /// @brief Adds a transitional section to the envelope
78 /// @param targetValue Value at the end of transition, in any unit
79 /// @param durationSeconds Duration of the the transition in seconds
80 /// @param shape Shape of the transition ramp curve
81 SegmentedEnvelope& rampTo (double targetValue, double durationSeconds, Shape shape = Shape::linear)
82 {
83 m_segments.push_back (Segment { durationSeconds, targetValue, shape, false });
84 //m_segments.emplace_back (durationSeconds, targetValue, shape, false);
85 m_endValue = targetValue;
86 return *this;
87 }
88
89 /// @brief Created a copy of the envelope wrapped in a smart pointer
90 std::unique_ptr<Envelope> copy() const override
91 {
93 }
94
95private:
96 struct Segment
97 {
98 double durationSeconds;
99 double targetValue;
100 Shape shape;
101 bool isHold;
102
103 Segment (double d, double t, Shape s = Shape::linear, bool h = false):
104 durationSeconds (d), targetValue (t), shape (s), isHold (h) {}
105 };
106
107 const double m_resetValue;
108 double m_beginValue;
109 double m_endValue;
110 std::vector<Segment> m_segments;
111
112 double m_currentTimeSeconds = 0.0;
113 size_t m_currentSegmentIndex = 0;
114 double m_currentValue;
115
116 double m_frameTimeSeconds = 1.0 / 44100.0;
117
118 void advance (double timeSeconds)
119 {
120 m_currentTimeSeconds += timeSeconds;
121
122 // TODO: Less nesting here
123 while (m_currentSegmentIndex < m_segments.size())
124 {
125 const Segment& currentSegment = m_segments[m_currentSegmentIndex];
126
127 if (m_currentTimeSeconds < currentSegment.durationSeconds)
128 {
129 const double t = m_currentTimeSeconds / currentSegment.durationSeconds;
130
131 if (currentSegment.isHold)
132 {
133 m_currentValue = currentSegment.targetValue;
134 }
135 else
136 {
137 switch (currentSegment.shape)
138 {
139 case Shape::linear:
140 {
141 m_currentValue = m_beginValue + (currentSegment.targetValue - m_beginValue) * t;
142 break;
143 }
144
146 {
147 const double ratio = currentSegment.targetValue / m_beginValue;
148
149 if (std::abs (ratio - 1.0) < 1e-9)
150 {
151 m_currentValue = m_beginValue;
152 break;
153 }
154
155 const bool isFallingCurve = ratio > 1.0;
156
157 if (isFallingCurve)
158 {
159 double k = std::log (ratio) / currentSegment.durationSeconds;
160 m_currentValue = m_beginValue * std::exp (k * m_currentTimeSeconds);
161 }
162 else
163 {
164 double k = std::log (1.0 / ratio) / currentSegment.durationSeconds;
165 m_currentValue = m_beginValue * std::exp (-k * m_currentTimeSeconds);
166 }
167
168 break;
169 }
170
171 case Shape::sCurve:
172 {
173 const double smoothstep = t * t * (3.0 - 2.0 * t);
174 m_currentValue = m_beginValue + (currentSegment.targetValue - m_beginValue) * smoothstep;
175 break;
176 }
177 }
178 }
179
180 break;
181 }
182 else
183 {
184 m_currentTimeSeconds -= currentSegment.durationSeconds;
185 m_beginValue = currentSegment.targetValue;
186 ++m_currentSegmentIndex;
187 }
188 }
189
190 if (m_currentSegmentIndex >= m_segments.size())
191 m_currentValue = m_segments.back().targetValue;
192 }
193};
194
196
197} // namespace hart
Represents an Envelope curve for DSP parameters.
A simple envelope constructed from semgents.
void renderNextBlock(size_t blockSize, std::vector< double > &valuesOutput) override
Shape
Determines a shape of ramp curve.
@ exponential
Exponential curve.
std::unique_ptr< Envelope > copy() const override
Created a copy of the envelope wrapped in a smart pointer.
void prepare(double sampleRateHz, size_t) override
SegmentedEnvelope & rampTo(double targetValue, double durationSeconds, Shape shape=Shape::linear)
Adds a transitional section to the envelope.
SegmentedEnvelope(double startValue)
Creates a segmented envelope instance.
SegmentedEnvelope & hold(double durationSeconds)
Adds a flat horizontal section to the envelope.
std::unique_ptr< ObjectType > make_unique(Args &&... args)
std::make_unique() replacement for C++11
static SampleType floatsEqual(SampleType a, SampleType b, SampleType epsilon=(SampleType) 1e-8)
Compares two floating point numbers within a given tolerance.
#define HART_ENVELOPE_DECLARE_ALIASES_FOR(ClassName)
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
#define HART_WARNING(message)