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