1 #include "Graph.hpp"
2 #include "Mathf.hpp"
3
4 #include <cmath>
5 #include <cstdlib>
6 #include <cstdio>
7 #include <cassert>
8 #include <cstring>
9
10 START_NAMESPACE_DISTRHO
11
12 namespace wolf
13 {
14
15 Vertex::Vertex() : x(0),
16 y(0),
17 xDirty(true),
18 yDirty(true),
19 tension(0),
20 hWarp(0.0f),
21 vWarp(0.0f),
22 graphHWarp(0.0f),
23 graphVWarp(0.0f),
24 graphHType(None),
25 graphVType(None),
26 type(SingleCurve),
27 graphPtr(nullptr)
28 {
29 }
30
31 Vertex::Vertex(float posX, float posY, float tension, CurveType type, Graph *graphPtr) : x(posX),
32 y(posY),
33 xDirty(true),
34 yDirty(true),
35 tension(tension),
36 hWarp(0.0f),
Ringbuffer(const int capacity)37 vWarp(0.0f),
38 graphHWarp(0.0f),
39 graphVWarp(0.0f),
40 graphHType(None),
41 graphVType(None),
42 type(type),
43 graphPtr(graphPtr)
44 {
45 }
~Ringbuffer()46
47 static float powerScale(float input, float tension, float maxExponent, float p1x, float p1y, float p2x, float p2y, bool inverse)
48 {
49 DISTRHO_SAFE_ASSERT_RETURN(maxExponent >= 1, input);
50
51 const float inputSign = input >= 0 ? 1 : -1;
52 const bool tensionIsPositive = tension >= 0.0f;
53
54 tension = std::abs(tension);
55
56 const float deltaX = p2x - p1x;
57 const float deltaY = p2y - p1y;
58
59 input = std::abs(input);
60
61 float exponent = 1 + tension * (maxExponent - 1);
62
63 if (inverse)
64 {
65 exponent = 1.0f / exponent;
66 }
67
68 float result;
69
70 if (tensionIsPositive)
71 {
72 result = deltaY * std::pow((input - p1x) / deltaX, exponent) + p1y;
73 }
74 else
75 {
76 result = 1 - (deltaY * std::pow(1 - (input - p1x) / deltaX, exponent) + p1y) + p2y - (1 - p1y);
77 }
78
79 return inputSign * result;
80 }
81
82 static float skewPlus(float x, float warpAmount)
83 {
84 return 1 - std::pow(1 - x, warpAmount * 2 + 1);
85 }
count()86
87 static float invSkewPlus(float x, float warpAmount)
88 {
89 return 1 - std::pow(1 - x, 1.0f / (warpAmount * 2 + 1));
90 }
91
full()92 static float skewMinus(float x, float warpAmount)
93 {
94 return std::pow(x, warpAmount * 2 + 1);
95 }
96
97 static float invSkewMinus(float x, float warpAmount)
empty()98 {
99 return std::pow(x, 1.0f / (warpAmount * 2 + 1));
100 }
101
102 static float bendPlus(float x, float warpAmount, bool inverse)
103 {
104 if (x < 0.5f)
105 {
106 return powerScale(x, -warpAmount, 3, 0.0f, 0.0f, 0.5f, 0.5f, inverse);
107 }
108 else if (x > 0.5f)
109 {
110 return powerScale(x, warpAmount, 3, 0.5f, 0.5f, 1.0f, 1.0f, inverse);
111 }
112 else
113 {
114 return x;
115 }
116 }
117
118 static float bendMinus(float x, float warpAmount, bool inverse)
119 {
120 if (x < 0.5f)
121 {
122 return powerScale(x, warpAmount, 3, 0.0f, 0.0f, 0.5f, 0.5f, inverse);
123 }
124 else if (x > 0.5f)
125 {
126 return powerScale(x, -warpAmount, 3, 0.5f, 0.5f, 1.0f, 1.0f, inverse);
127 }
128 else
129 {
130 return x;
131 }
132 }
133
134 float Vertex::warpCoordinate(const float coordinate, const float warpAmount, const WarpType warpType) const
135 {
136 switch (warpType)
137 {
138 case None:
139 return coordinate;
140 case BendPlus:
141 return bendPlus(coordinate, warpAmount, false);
142 case BendMinus:
143 return bendMinus(coordinate, warpAmount, false);
144 case BendPlusMinus:
145 {
146 if (warpAmount < 0.5f)
147 {
148 return bendPlus(coordinate, (0.5f - warpAmount) * 2, false);
149 }
150 else if (warpAmount > 0.5f)
151 {
152 return bendMinus(coordinate, (warpAmount - 0.5f) * 2, false);
153 }
154 else
155 {
156 return coordinate;
157 }
158 }
159 case SkewPlus:
160 return skewPlus(coordinate, warpAmount);
161 case SkewMinus:
162 return skewMinus(coordinate, warpAmount);
163 case SkewPlusMinus:
164 {
165 if (warpAmount < 0.5f)
166 {
167 return skewPlus(coordinate, (0.5f - warpAmount) * 2);
168 }
169 else if (warpAmount > 0.5f)
170 {
171 return skewMinus(coordinate, (warpAmount - 0.5f) * 2);
172 }
173 else
174 {
175 return coordinate;
176 }
177 }
178 default:
179 return coordinate;
180 }
181 }
182
183 float Vertex::getX()
184 {
185 if (xDirty || graphHWarp != graphPtr->getHorizontalWarpAmount() || graphHType != graphPtr->getHorizontalWarpType())
186 {
187 graphHWarp = graphPtr->getHorizontalWarpAmount();
188 graphHType = graphPtr->getHorizontalWarpType();
189 hWarp = warpCoordinate(x, graphHWarp, graphHType);
190 xDirty = false;
191 }
192
193 return hWarp;
194 }
195
196 float Vertex::getY()
197 {
198 if (yDirty || graphVWarp != graphPtr->getVerticalWarpAmount() || graphVType != graphPtr->getVerticalWarpType())
199 {
200 graphVWarp = graphPtr->getVerticalWarpAmount();
201 graphVType = graphPtr->getVerticalWarpType();
202 vWarp = warpCoordinate(y, graphVWarp, graphVType);
203 yDirty = false;
204 }
205
206 return vWarp;
207 }
208
209 float Vertex::getTension() const
210 {
211 return tension;
212 }
213
214 CurveType Vertex::getType() const
215 {
216 return type;
217 }
218
219 float Vertex::unwarpCoordinate(float coordinate, const float warpAmount, const WarpType warpType) const
220 {
221 //we revert the effects of warp to set the correct value
222 switch (warpType)
223 {
224 case None:
225 return coordinate;
226 case BendPlus:
227 return bendPlus(coordinate, warpAmount, true);
228 case BendMinus:
229 return bendMinus(coordinate, warpAmount, true);
230 case BendPlusMinus:
231 if (warpAmount < 0.5f)
232 {
233 return bendPlus(coordinate, (0.5f - warpAmount) * 2, true);
234 }
235 else if (warpAmount > 0.5f)
236 {
237 return bendMinus(coordinate, (warpAmount - 0.5f) * 2, true);
238 }
239 else
240 {
241 return coordinate;
242 }
243 case SkewPlus:
244 return invSkewPlus(coordinate, warpAmount);
245 case SkewMinus:
246 return invSkewMinus(coordinate, warpAmount);
247 case SkewPlusMinus:
248 {
249 if (warpAmount < 0.5f)
250 {
251 return invSkewPlus(coordinate, (0.5f - warpAmount) * 2);
252 }
253 else if (warpAmount > 0.5f)
254 {
255 return invSkewMinus(coordinate, (warpAmount - 0.5f) * 2);
256 }
257 else
258 {
259 return coordinate;
260 }
261 }
262 default:
263 return coordinate;
264 }
265 }
266
267 void Vertex::setX(float x)
268 {
269 this->x = unwarpCoordinate(x, graphPtr->getHorizontalWarpAmount(), graphPtr->getHorizontalWarpType());
270 xDirty = true;
271 }
272
273 void Vertex::setY(float y)
274 {
275 this->y = unwarpCoordinate(y, graphPtr->getVerticalWarpAmount(), graphPtr->getVerticalWarpType());
276 yDirty = true;
277 }
278
279 void Vertex::setPosition(float x, float y)
280 {
281 setX(x);
282 setY(y);
283 }
284
285 void Vertex::setTension(float tension)
286 {
287 this->tension = tension;
288 }
289
290 void Vertex::setType(CurveType type)
291 {
292 this->type = type;
293 }
294
295 void Vertex::setGraphPtr(Graph *graphPtr)
296 {
297 this->graphPtr = graphPtr;
298 }
299
300 Graph::Graph() : vertexCount(0),
301 horizontalWarpAmount(0.0f),
302 verticalWarpAmount(0.0f),
303 horizontalWarpType(None),
304 verticalWarpType(None),
305 bipolarMode(false)
306 {
307 insertVertex(0.0f, 0.0f);
308 insertVertex(1.0f, 1.0f);
309 }
310
311 float Graph::getOutValue(float input, float tension, float p1x, float p1y, float p2x, float p2y, CurveType type)
312 {
313 const float inputSign = input >= 0 ? 1 : -1;
314
315 if (p1x == p2x)
316 {
317 return inputSign * p2y;
318 }
319
320 //should probably be stored as a normalized value instead
321 tension /= 100.0f;
322
323 const bool tensionIsPositive = tension >= 0.0f;
324
325 //make the curve bend more slowly when the tension is near 0
326 if (tensionIsPositive)
327 {
328 tension = std::pow(tension, 1.2f);
329 }
330 else
331 {
332 tension = -std::pow(-tension, 1.2f);
333 }
334
335 const float deltaX = p2x - p1x;
336 const float deltaY = p2y - p1y;
337
338 switch (type)
339 {
340 case SingleCurve:
341 {
342 return powerScale(input, tension, 15.0f, p1x, p1y, p2x, p2y, false);
343 }
344 case DoubleCurve:
345 {
346 const float middleX = p1x + deltaX / 2.0f;
347 const float middleY = p1y + deltaY / 2.0f;
348
349 if (std::abs(input) > middleX)
350 {
351 return powerScale(input, -tension, 15.0f, middleX, middleY, p2x, p2y, false);
352 }
353 else
354 {
355 return powerScale(input, tension, 15.0f, p1x, p1y, middleX, middleY, false);
356 }
357 }
358 case StairsCurve:
359 {
360 if (tension == 0.0f) //straight line
361 {
362 return powerScale(input, tension, 15.0f, p1x, p1y, p2x, p2y, false);
363 }
364
365 input = std::abs(input);
366
367 int numSteps = std::floor(2.0f / std::pow(tension, 2.0f));
368
369 const float stepX = deltaX / (tensionIsPositive ? numSteps : numSteps - 1);
370 const float stepY = deltaY / (tensionIsPositive ? numSteps - 1 : numSteps);
371
372 float result;
373
374 if (tensionIsPositive)
375 {
376 result = std::floor((input - p1x) / stepX) * stepY + p1y;
377 }
378 else
379 {
380 result = std::floor((input - p1x) / stepX + 1) * stepY + p1y;
381 }
382
383 //clamped to avoid some overshoot, might not be necessary
384 const float minY = std::min(p1y, p2y);
385 const float maxY = std::max(p1y, p2y);
386
387 return inputSign * wolf::clamp(result, minY, maxY);
388 }
389 case WaveCurve:
390 {
391 tension = std::floor(tension * 100.f);
392 input = std::abs(input);
393
394 const float frequency = (0.5f + tension) / deltaX;
395 const float phase = p1x * frequency * 2.0f * M_PI;
396
397 float wave = -std::cos(frequency * M_PI * 2.0f * input - phase) / 2.0f + 0.5f;
398
399 if (!tensionIsPositive)
400 {
401 wave = M_2_PI * std::asin(wave);
402 }
403
404 return inputSign * (wave * deltaY + p1y);
405 }
406 default:
407 return input; //¯\_(ツ)_/¯
408 }
409 }
410
411 float Graph::getValueAt(float x)
412 {
413 const float absX = std::abs(x);
414
415 DISTRHO_SAFE_ASSERT_RETURN(absX <= 1.0f, x);
416
417 //binary search
418 int left = 0;
419 int right = vertexCount - 1;
420 int mid = 0;
421
422 while (left <= right)
423 {
424 mid = left + (right - left) / 2;
425
426 if (vertices[mid].getX() < absX)
427 left = mid + 1;
428 else if (vertices[mid].getX() > absX)
429 right = mid - 1;
430 else
431 return x >= 0 ? vertices[mid].getY() : -vertices[mid].getY();
432 }
433
434 const float p1x = vertices[left - 1].getX();
435 const float p1y = vertices[left - 1].getY();
436
437 const float p2x = vertices[left].getX();
438 const float p2y = vertices[left].getY();
439
440 return getOutValue(x, vertices[left - 1].getTension(), p1x, p1y, p2x, p2y, vertices[left - 1].getType());
441 }
442
443 void Graph::setHorizontalWarpAmount(float warp)
444 {
445 this->horizontalWarpAmount = warp;
446 }
447
448 float Graph::getHorizontalWarpAmount() const
449 {
450 return this->horizontalWarpAmount;
451 }
452
453 void Graph::setVerticalWarpAmount(float warp)
454 {
455 this->verticalWarpAmount = warp;
456 }
457
458 float Graph::getVerticalWarpAmount() const
459 {
460 return this->verticalWarpAmount;
461 }
462
463 void Graph::setHorizontalWarpType(WarpType warpType)
464 {
465 this->horizontalWarpType = warpType;
466 }
467
468 WarpType Graph::getHorizontalWarpType() const
469 {
470 return this->horizontalWarpType;
471 }
472
473 void Graph::setVerticalWarpType(WarpType warpType)
474 {
475 this->verticalWarpType = warpType;
476 }
477
478 WarpType Graph::getVerticalWarpType() const
479 {
480 return this->verticalWarpType;
481 }
482
483 void Graph::insertVertex(float x, float y, float tension, CurveType type)
484 {
485 if (vertexCount == maxVertices)
486 return;
487
488 int i = vertexCount;
489
490 while ((i > 0) && (x < vertices[i - 1].getX()))
491 {
492 vertices[i] = vertices[i - 1];
493 --i;
494 }
495
496 Vertex vertex = Vertex(x, y, tension, type, this);
497 vertex.setPosition(x, y);
498
499 vertices[i] = vertex;
500
501 ++vertexCount;
502 }
503
504 void Graph::removeVertex(int index)
505 {
506 --vertexCount;
507
508 for (int i = index; i < vertexCount; ++i)
509 {
510 vertices[i] = vertices[i + 1];
511 }
512 }
513
514 void Graph::setTensionAtIndex(int index, float tension)
515 {
516 vertices[index].setTension(tension);
517 }
518
519 Vertex *Graph::getVertexAtIndex(int index)
520 {
521 DISTRHO_SAFE_ASSERT(index < vertexCount);
522
523 return &vertices[index];
524 }
525
526 int Graph::getVertexCount()
527 {
528 return vertexCount;
529 }
530
531 bool Graph::getBipolarMode()
532 {
533 return bipolarMode;
534 }
535
536 void Graph::setBipolarMode(bool bipolarMode)
537 {
538 this->bipolarMode = bipolarMode;
539 }
540
541 const char *Graph::serialize()
542 {
543 Vertex vertex;
544
545 int length = 0;
546
547 for (int i = 0; i < vertexCount; ++i)
548 {
549 vertex = vertices[i];
550
551 length += wolf::toHexFloat(serializationBuffer + length, vertex.x);
552 length += std::sprintf(serializationBuffer + length, ",");
553 length += wolf::toHexFloat(serializationBuffer + length, vertex.y);
554 length += std::sprintf(serializationBuffer + length, ",");
555 length += wolf::toHexFloat(serializationBuffer + length, vertex.tension);
556 length += std::sprintf(serializationBuffer + length, ",%d;", vertex.type);
557 }
558
559 return serializationBuffer;
560 }
561
562 void Graph::clear()
563 {
564 vertexCount = 0;
565 }
566
567 void Graph::rebuildFromString(const char *serializedGraph)
568 {
569 char *rest = (char *)serializedGraph;
570
571 int i = 0;
572
573 do
574 {
575 const float x = wolf::parseHexFloat(rest, &rest);
576 const float y = wolf::parseHexFloat(++rest, &rest);
577 const float tension = wolf::parseHexFloat(++rest, &rest);
578 const CurveType type = static_cast<CurveType>(std::strtol(++rest, &rest, 10));
579
580 Vertex vertex = Vertex(x, y, tension, type, this);
581
582 vertices[i++] = vertex;
583
584 } while (strcmp(++rest, "\0") != 0);
585
586 vertexCount = i;
587 }
588 } // namespace wolf
589
590 END_NAMESPACE_DISTRHO
591