HART  0.2.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 // TODO: Make this statement strict (valuesOutput.size() != blockSize) once we have a proper EnvelopeBuffers class
41 if (valuesOutput.size() < blockSize)
42 {
43 HART_WARNING ("Make sure to configure your envelope container size before processing audio");
44 valuesOutput.resize (blockSize);
45 }
46
47 for (size_t i = 0; i < blockSize; ++i)
48 {
49 advance (m_frameTimeSeconds);
50 valuesOutput[i] = m_currentValue;
51 }
52 }
53
54 void prepare (double sampleRateHz, size_t /* maxBlockSizeFrames */) override
55 {
56 if (sampleRateHz < 0 || floatsEqual (sampleRateHz, 0.0))
57 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Illegal sample rate value");
58
59 m_frameTimeSeconds = 1.0 / sampleRateHz;
60 }
61
62 void reset() override
63 {
64 m_currentTimeSeconds = 0.0;
65 m_currentSegmentIndex = 0;
66 m_currentValue = m_resetValue;
67 }
68
69 /// @brief Adds a flat horizontal section to the envelope
70 /// @param durationSeconds Duration of the new segment in seconds
71 SegmentedEnvelope& hold (double durationSeconds)
72 {
73 //m_segments.emplace_back (durationSeconds, m_endValue, Shape::linear, true);
74 m_segments.push_back (Segment { durationSeconds, m_endValue, Shape::linear, true });
75 return *this;
76 }
77
78 /// @brief Adds a transitional section to the envelope
79 /// @param targetValue Value at the end of transition, in any unit
80 /// @param durationSeconds Duration of the the transition in seconds
81 /// @param shape Shape of the transition ramp curve
82 SegmentedEnvelope& rampTo (double targetValue, double durationSeconds, Shape shape = Shape::linear)
83 {
84 m_segments.push_back (Segment { durationSeconds, targetValue, shape, false });
85 //m_segments.emplace_back (durationSeconds, targetValue, shape, false);
86 m_endValue = targetValue;
87 return *this;
88 }
89
90 /// @brief Created a copy of the envelope wrapped in a smart pointer
91 std::unique_ptr<Envelope> copy() const override
92 {
94 }
95
96private:
97 struct Segment
98 {
99 double durationSeconds;
100 double targetValue;
101 Shape shape;
102 bool isHold;
103
104 Segment (double d, double t, Shape s = Shape::linear, bool h = false):
105 durationSeconds (d), targetValue (t), shape (s), isHold (h) {}
106 };
107
108 const double m_resetValue;
109 double m_beginValue;
110 double m_endValue;
111 std::vector<Segment> m_segments;
112
113 double m_currentTimeSeconds = 0.0;
114 size_t m_currentSegmentIndex = 0;
115 double m_currentValue;
116
117 double m_frameTimeSeconds = 1.0 / 44100.0;
118
119 void advance (double timeSeconds)
120 {
121 m_currentTimeSeconds += timeSeconds;
122
123 // TODO: Less nesting here
124 while (m_currentSegmentIndex < m_segments.size())
125 {
126 const Segment& currentSegment = m_segments[m_currentSegmentIndex];
127
128 if (m_currentTimeSeconds < currentSegment.durationSeconds)
129 {
130 const double t = m_currentTimeSeconds / currentSegment.durationSeconds;
131
132 if (currentSegment.isHold)
133 {
134 m_currentValue = currentSegment.targetValue;
135 }
136 else
137 {
138 switch (currentSegment.shape)
139 {
140 case Shape::linear:
141 {
142 m_currentValue = m_beginValue + (currentSegment.targetValue - m_beginValue) * t;
143 break;
144 }
145
147 {
148 const double ratio = currentSegment.targetValue / m_beginValue;
149
150 if (std::abs (ratio - 1.0) < 1e-9)
151 {
152 m_currentValue = m_beginValue;
153 break;
154 }
155
156 const bool isFallingCurve = ratio > 1.0;
157
158 if (isFallingCurve)
159 {
160 double k = std::log (ratio) / currentSegment.durationSeconds;
161 m_currentValue = m_beginValue * std::exp (k * m_currentTimeSeconds);
162 }
163 else
164 {
165 double k = std::log (1.0 / ratio) / currentSegment.durationSeconds;
166 m_currentValue = m_beginValue * std::exp (-k * m_currentTimeSeconds);
167 }
168
169 break;
170 }
171
172 case Shape::sCurve:
173 {
174 const double smoothstep = t * t * (3.0 - 2.0 * t);
175 m_currentValue = m_beginValue + (currentSegment.targetValue - m_beginValue) * smoothstep;
176 break;
177 }
178 }
179 }
180
181 break;
182 }
183 else
184 {
185 m_currentTimeSeconds -= currentSegment.durationSeconds;
186 m_beginValue = currentSegment.targetValue;
187 ++m_currentSegmentIndex;
188 }
189 }
190
191 if (m_currentSegmentIndex >= m_segments.size())
192 m_currentValue = m_segments.back().targetValue;
193 }
194};
195
197
198} // 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.
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 HART_WARNING(message)
Prints a warning message.
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)