1 /* B.Shapr
2 * Beat / envelope shaper LV2 plugin
3 *
4 * Copyright (C) 2019 by Sven Jähnichen
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #ifndef SHAPE_HPP_
22 #define SHAPE_HPP_
23
24 #include <cstddef>
25 #include <cstdio>
26 #include <cstdint>
27 #include <cmath>
28 // #include <iostream>
29 #include "BUtilities/Point.hpp"
30 #include "Node.hpp"
31 #include "StaticArrayList.hpp"
32
33 #define MAPRES 1024
34
35 template<size_t sz>
36 class Shape
37 {
38 public:
39 Shape ();
40 Shape (const StaticArrayList<Node, sz> nodes, double transformFactor = 1.0, double transformOffset = 0.0);
41 virtual ~Shape ();
42
43 bool operator== (const Shape<sz>& rhs);
44 bool operator!= (const Shape<sz>& rhs);
45
46 void setTransformation (const double transformFactor, const double transformOffset);
47 virtual void clearShape ();
48 virtual void setDefaultShape ();
49 bool isDefault () const;
50 size_t size () const;
51 Node getRawNode (const size_t nr) const;
52 Node getNode (const size_t nr) const;
53 size_t findRawNode (const Node& node);
54
55 bool validateNode (const size_t nr);
56 bool validateShape ();
57
58 bool appendRawNode (const Node& node);
59 bool appendNode (const Node& node);
60 bool insertRawNode (const size_t pos, const Node& node);
61 bool insertNode (const size_t pos, const Node& node);
62 bool insertRawNode (const Node& node);
63 bool insertNode (const Node& node);
64 bool changeRawNode (const size_t pos, const Node& newnode);
65 bool changeNode (const size_t pos, const Node& newnode);
66 bool deleteNode (const size_t pos);
67
68 double getMapRawValue (const double x) const;
69 double getMapValue (const double x) const;
70 double* getMap ();
71
72 protected:
73 double transform (const double value) const;
74 double retransform (const double value) const;
75 Node transformNode (const Node& node) const;
76 Node retransformNode (const Node& node) const;
77 virtual void drawLineOnMap (const BUtilities::Point p1, const BUtilities::Point p2);
78 BUtilities::Point getPointPerc (const BUtilities::Point p1, const BUtilities::Point p2, const double perc) const;
79 virtual void renderBezier (const Node& n1, const Node& n2);
80
81 StaticArrayList<Node, sz> nodes_;
82 double map_[MAPRES];
83 double factor_;
84 double offset_;
85
86 };
87
Shape()88 template<size_t sz> Shape<sz>::Shape () : nodes_ (), map_ {0.0}, factor_ (1.0), offset_ (0.0) {}
89
Shape(const StaticArrayList<Node,sz> nodes,double transformFactor,double transformOffset)90 template<size_t sz> Shape<sz>::Shape (const StaticArrayList<Node, sz> nodes, double transformFactor, double transformOffset) :
91 nodes_ (nodes), map_ {0.0}, factor_ (transformFactor), offset_ (transformFactor) {}
92
~Shape()93 template<size_t sz> Shape<sz>::~Shape () {}
94
operator ==(const Shape<sz> & rhs)95 template<size_t sz> bool Shape<sz>::operator== (const Shape<sz>& rhs)
96 {
97 if (size () != rhs.size ()) return false;
98 for (unsigned int i = 0; i < size (); ++i) if (nodes_[i] != rhs.nodes_[i]) return false;
99 return true;
100 }
101
operator !=(const Shape<sz> & rhs)102 template<size_t sz> bool Shape<sz>::operator!= (const Shape<sz>& rhs) {return !(*this == rhs);}
103
setTransformation(const double transformFactor,const double transformOffset)104 template<size_t sz> void Shape<sz>::setTransformation (const double transformFactor, const double transformOffset)
105 {
106 // Prevent div by zero
107 if (transformFactor == 0.0) return;
108
109 factor_ = transformFactor;
110 offset_ = transformOffset;
111 }
112
clearShape()113 template<size_t sz> void Shape<sz>::clearShape ()
114 {
115 while (!nodes_.empty ()) nodes_.pop_back ();
116 for (int i = 0; i < MAPRES; ++i) map_[i] = 0;
117 }
118
setDefaultShape()119 template<size_t sz> void Shape<sz>::setDefaultShape ()
120 {
121 clearShape ();
122 nodes_.push_back ({NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}});
123 nodes_.push_back ({NodeType::END_NODE, {1, 0}, {0, 0}, {0, 0}});
124 renderBezier (nodes_[0], nodes_[1]);
125 }
126
isDefault() const127 template<size_t sz>bool Shape<sz>::isDefault () const
128 {
129 return ((nodes_.size == 2) && (nodes_[0] == Node {NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}}));
130 }
131
size() const132 template<size_t sz>size_t Shape<sz>::size () const {return nodes_.size;}
133
getRawNode(const size_t nr) const134 template<size_t sz>Node Shape<sz>::getRawNode (const size_t nr) const {return nodes_[nr];}
135
getNode(const size_t nr) const136 template<size_t sz>Node Shape<sz>::getNode (const size_t nr) const {return retransformNode (getRawNode (nr));}
137
findRawNode(const Node & node)138 template<size_t sz>size_t Shape<sz>::findRawNode (const Node& node)
139 {
140 for (int i = 0; i < nodes_.size; ++i)
141 {
142 if (nodes_[i] == node) return i;
143 }
144 return nodes_.size;
145 }
146
appendRawNode(const Node & node)147 template<size_t sz> bool Shape<sz>::appendRawNode (const Node& node)
148 {
149 if (nodes_.size < sz)
150 {
151 nodes_.push_back (node);
152 return true;
153 }
154
155 return false;
156 }
157
appendNode(const Node & node)158 template<size_t sz> bool Shape<sz>::appendNode (const Node& node)
159 {
160 return appendRawNode (transformNode (node));
161 }
162
insertRawNode(const size_t pos,const Node & node)163 template<size_t sz> bool Shape<sz>::insertRawNode (const size_t pos, const Node& node)
164 {
165 // Nodes full => errorNode
166 if (nodes_.size >= sz) return false;
167
168 size_t p = pos;
169
170 // Insert
171 if (p < nodes_.size) nodes_.insert (nodes_.begin() + p, node);
172
173 // Otherwise append
174 else
175 {
176 p = nodes_.size;
177 nodes_.push_back (node);
178 }
179
180 // Validate node and its neighbors
181 if (!validateNode (p)) return false;
182 if ((p > 0) && (!validateNode (p - 1))) return false;
183 if ((p + 1 < nodes_.size) && (!validateNode (p + 1))) return false;
184
185 // Update map
186 for (unsigned int i = (p >= 2 ? p - 2 : 0); (i <= p + 1) && (i + 1 < nodes_.size); ++ i) renderBezier (nodes_[i], nodes_[i + 1]);
187 return true;
188 }
189
insertNode(const size_t pos,const Node & node)190 template<size_t sz> bool Shape<sz>::insertNode (const size_t pos, const Node& node)
191 {
192 return insertRawNode (pos, transformNode (node));
193 }
194
insertRawNode(const Node & node)195 template<size_t sz> bool Shape<sz>::insertRawNode (const Node& node)
196 {
197 // Find position
198 size_t pos = nodes_.size;
199 for (unsigned int i = 0; i < nodes_.size; ++i)
200 {
201 if (node.point.x < nodes_[i].point.x)
202 {
203 // Add if not redundant
204 if ((i > 0) && (node != nodes_[i - 1]))
205 {
206 pos = i;
207 break;
208 }
209
210 else return false;
211 }
212 }
213
214 // Insert
215 return insertRawNode (pos, node);
216 }
217
insertNode(const Node & node)218 template<size_t sz> bool Shape<sz>::insertNode (const Node& node)
219 {
220 return insertRawNode (transformNode (node));
221 }
222
changeRawNode(const size_t pos,const Node & node)223 template<size_t sz> bool Shape<sz>::changeRawNode (const size_t pos, const Node& node)
224 {
225 if (pos >= nodes_.size) return false;
226 nodes_[pos] = node;
227
228 // Validate node and its neighbors
229 if (!validateNode (pos)) return false;
230 if ((pos > 0) && (!validateNode (pos - 1))) return false;
231 if ((pos + 1 < nodes_.size) && (!validateNode (pos + 1))) return false;
232
233 // Update map
234 for (unsigned int i = (pos >= 2 ? pos - 2 : 0); (i <= pos + 1) && (i + 1 < nodes_.size); ++i) renderBezier (nodes_[i], nodes_[i + 1]);
235
236 return true;
237 }
238
changeNode(const size_t pos,const Node & node)239 template<size_t sz> bool Shape<sz>::changeNode (const size_t pos, const Node& node)
240 {
241 return changeRawNode (pos, transformNode (node));
242 }
243
deleteNode(const size_t pos)244 template<size_t sz> bool Shape<sz>::deleteNode (const size_t pos)
245 {
246 // Only deletion of middle nodes allowed
247 if ((pos == 0) || (pos >= nodes_.size - 1)) return false;
248
249 nodes_.erase (nodes_.begin() + pos);
250
251 // Validate neighbor nodes
252 if (!validateNode (pos - 1)) return false;
253 if (!validateNode (pos)) return false;
254
255 // Update map
256 for (unsigned int i = (pos >= 2 ? pos - 2 : 0); (i <= pos) && (i + 1 < nodes_.size); ++ i) renderBezier (nodes_[i], nodes_[i + 1]);
257 return true;
258 }
259
validateNode(const size_t nr)260 template<size_t sz> bool Shape<sz>::validateNode (const size_t nr)
261 {
262 // Exception: Invalid parameters
263 if (nr >= nodes_.size)
264 {
265 fprintf (stderr, "BSchaffl.lv2: Node validation called with invalid parameters (node: %li).\n", nr);
266 return false;
267 }
268
269 // Exception: Invalid node order
270 if ((nodes_.size >= 3) && (nr > 1) && (nr < nodes_.size - 1) && (nodes_[nr-1].point.x > nodes_[nr+1].point.x))
271 {
272 fprintf (stderr, "BSchaffl.lv2: Corrupt node data at node %li (%f, %f). Reset shape.\n", nr, nodes_[nr].point.x, nodes_[nr].point.y);
273 setDefaultShape ();
274 return false;
275 }
276
277 // Start node
278 if (nr == 0)
279 {
280 // Check: Only end nodes on start position
281 if (nodes_[0].nodeType != NodeType::END_NODE) nodes_[0] = {NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}};
282
283 // Check: Start position
284 if (nodes_[0].point.x != 0) nodes_[0].point.x = 0;
285
286 // Check: No handles
287 nodes_[0].handle1 = BUtilities::Point (0, 0);
288 nodes_[0].handle2 = BUtilities::Point (0, 0);
289 }
290
291 // End node
292 else if (nr == nodes_.size - 1)
293 {
294 // Check: Only end nodes on end position
295 if (nodes_[nr].nodeType != NodeType::END_NODE)
296 {
297 nodes_[nr] = {NodeType::END_NODE, {1, nodes_[0].point.y}, {0, 0}, {0, 0}};
298 }
299
300 // Check: No handles
301 nodes_[nr].handle1 = BUtilities::Point (0, 0);
302 nodes_[nr].handle2 = BUtilities::Point (0, 0);
303 }
304
305 // Middle nodes
306 else
307 {
308 // Check: No end nodes in the middle
309 if (nodes_[nr].nodeType == NodeType::END_NODE) nodes_[nr].nodeType = NodeType::CORNER_NODE;
310
311 // Check: Nodes point order
312 if (nodes_[nr].point.x < nodes_[nr - 1].point.x) nodes_[nr].point.x = nodes_[nr - 1].point.x;
313 if (nodes_[nr].point.x > nodes_[nr + 1].point.x) nodes_[nr].point.x = nodes_[nr + 1].point.x;
314
315 // Check: POINT_NODE without handles
316 if (nodes_[nr].nodeType == NodeType::POINT_NODE)
317 {
318 nodes_[nr].handle1 = BUtilities::Point (0, 0);
319 nodes_[nr].handle2 = BUtilities::Point (0, 0);
320 }
321
322 // Check: Handles order
323 if (nodes_[nr].handle1.x > 0) nodes_[nr].handle1.x = 0;
324 if (nodes_[nr].handle2.x < 0) nodes_[nr].handle2.x = 0;
325
326 // Check: AUTO_SMOOTH_NODE with symmetric handles with the half size of the distance to the closest neighbor point
327 if (nodes_[nr].nodeType == NodeType::AUTO_SMOOTH_NODE)
328 {
329 double dist = (nodes_[nr].point.x - nodes_[nr-1].point.x > nodes_[nr+1].point.x - nodes_[nr].point.x ?
330 nodes_[nr+1].point.x - nodes_[nr].point.x :
331 nodes_[nr].point.x - nodes_[nr-1].point.x);
332 double ydist = (nodes_[nr + 1].point.y - nodes_[nr - 1].point.y);
333 double yamp = (fabs (nodes_[nr - 1].point.y != 0) && fabs (nodes_[nr + 1].point.y != 0) ?
334 (fabs (nodes_[nr - 1].point.y) < fabs (nodes_[nr + 1].point.y) ?
335 fabs (nodes_[nr - 1].point.y) / (fabs (nodes_[nr - 1].point.y) + fabs (nodes_[nr + 1].point.y)) :
336 fabs (nodes_[nr + 1].point.y) / (fabs (nodes_[nr - 1].point.y) + fabs (nodes_[nr + 1].point.y))) :
337 0);
338 nodes_[nr].handle1.x = -dist / 2;
339 nodes_[nr].handle1.y = -ydist * yamp;
340 nodes_[nr].handle2.x = dist / 2;
341 nodes_[nr].handle2.y = ydist * yamp;
342 }
343
344 // Check: SYMMETRIC_SMOOTH_NODE must be symmetric
345 else if (nodes_[nr].nodeType == NodeType::SYMMETRIC_SMOOTH_NODE)
346 {
347 //Check if handle1 overlaps neighbor point
348 if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x)
349 {
350 double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x;
351 nodes_[nr].handle1.x *= f;
352 nodes_[nr].handle1.y *= f;
353 }
354
355 // Make handele2 symmetric to handle1
356 nodes_[nr].handle2 = BUtilities::Point (0, 0) - nodes_[nr].handle1;
357
358 //Check if handle2 overlaps neighbor point
359 if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x)
360 {
361 double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x;
362 nodes_[nr].handle2.x *= f;
363 nodes_[nr].handle2.y *= f;
364 nodes_[nr].handle1 = BUtilities::Point (0, 0) - nodes_[nr].handle2;
365 }
366 }
367
368 // Check: SMOOTH_NODE handles point to opposite directions
369 else if (nodes_[nr].nodeType == NodeType::SMOOTH_NODE)
370 {
371 //Check if handle1 overlaps neighbor point
372 if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x)
373 {
374 double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x;
375 nodes_[nr].handle1.x *= f;
376 nodes_[nr].handle1.y *= f;
377 }
378
379 // Calculate handle distances
380 double dist1 = sqrt (nodes_[nr].handle1.x * nodes_[nr].handle1.x + nodes_[nr].handle1.y * nodes_[nr].handle1.y);
381 double dist2 = sqrt (nodes_[nr].handle2.x * nodes_[nr].handle2.x + nodes_[nr].handle2.y * nodes_[nr].handle2.y);
382
383 // Recalculate handle2
384 if ((dist1 != 0) && (dist2 != 0))
385 {
386 nodes_[nr].handle2.x = -(nodes_[nr].handle1.x * dist2 / dist1);
387 nodes_[nr].handle2.y = -(nodes_[nr].handle1.y * dist2 / dist1);
388 }
389
390 //Check if handle2 overlaps neighbor point
391 if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x)
392 {
393 double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x;
394 nodes_[nr].handle2.x *= f;
395 nodes_[nr].handle2.y *= f;
396 }
397 }
398
399 // Check: CORNER_NODE
400 else if (nodes_[nr].nodeType == NodeType::CORNER_NODE)
401 {
402 //Check if handle1 overlaps neighbor point
403 if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x)
404 {
405 double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x;
406 nodes_[nr].handle1.x *= f;
407 nodes_[nr].handle1.y *= f;
408 }
409
410 //Check if handle2 overlaps neighbor point
411 if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x)
412 {
413 double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x;
414 nodes_[nr].handle2.x *= f;
415 nodes_[nr].handle2.y *= f;
416 }
417 }
418 }
419
420 return true;
421 }
422
validateShape()423 template<size_t sz> bool Shape<sz>::validateShape ()
424 {
425 // TODO Sort ???
426
427 // Validate nodes
428 bool status = true;
429 for (unsigned int i = 0; i < nodes_.size; ++i)
430 {
431 if (!validateNode (i)) status = false;
432 }
433
434 // Update map
435 for (unsigned int i = 0; i + 1 < nodes_.size; ++i) renderBezier (nodes_[i], nodes_[i+1]);
436
437 return status;
438 }
439
transform(const double value) const440 template<size_t sz> double Shape<sz>::transform (const double value) const
441 {
442 return (value - offset_) / factor_;
443 }
444
retransform(const double value) const445 template<size_t sz> double Shape<sz>::retransform (const double value) const
446 {
447 return factor_ * value + offset_;
448 }
449
transformNode(const Node & node) const450 template<size_t sz> Node Shape<sz>::transformNode (const Node& node) const
451 {
452 return Node
453 (
454 node.nodeType,
455 BUtilities::Point (node.point.x, transform (node.point.y)),
456 BUtilities::Point (node.handle1.x, node.handle1.y / factor_),
457 BUtilities::Point (node.handle2.x, node.handle2.y / factor_)
458 );
459 }
460
retransformNode(const Node & node) const461 template<size_t sz> Node Shape<sz>::retransformNode (const Node& node) const
462 {
463 return Node
464 (
465 node.nodeType,
466 BUtilities::Point (node.point.x, retransform (node.point.y)),
467 BUtilities::Point (node.handle1.x, node.handle1.y * factor_),
468 BUtilities::Point (node.handle2.x, node.handle2.y * factor_)
469 );
470 }
471
drawLineOnMap(BUtilities::Point p1,BUtilities::Point p2)472 template<size_t sz> void Shape<sz>::drawLineOnMap (BUtilities::Point p1, BUtilities::Point p2)
473 {
474 if (p1.x < p2.x)
475 {
476 for (double x = p1.x; (x <= p2.x) && (x <= 1.0); x += (1.0 / MAPRES))
477 {
478 uint32_t i = ((uint32_t) (x * MAPRES)) % MAPRES;
479 map_[i] = p1.y + (p2.y - p1.y) * (x - p1.x) / (p2.x - p1.x);
480 }
481 }
482
483 else
484 {
485 uint32_t i = ((uint32_t) (p2.x * MAPRES)) % MAPRES;
486 map_ [i] = p2.y;
487 }
488 }
489
getPointPerc(const BUtilities::Point p1,const BUtilities::Point p2,const double perc) const490 template<size_t sz> BUtilities::Point Shape<sz>::getPointPerc (const BUtilities::Point p1, const BUtilities::Point p2 , const double perc) const
491 {
492 BUtilities::Point p;
493 p.x = p1.x + (p2.x - p1.x) * perc;
494 p.y = p1.y + (p2.y - p1.y) * perc;
495 return p;
496 }
497
renderBezier(const Node & n1,const Node & n2)498 template<size_t sz> void Shape<sz>::renderBezier (const Node& n1, const Node& n2)
499 {
500 // Interpolate Bezier curve
501 BUtilities::Point p1 = n1.point;
502 BUtilities::Point p2 = n1.point + n1.handle2;
503 BUtilities::Point p4 = n2.point;
504 BUtilities::Point p3 = n2.point + n2.handle1;
505 BUtilities::Point py = p1;
506 double step = 1 / (fabs (n2.point.x - n1.point.x) * MAPRES + 1);
507
508 for (double t = 0; t < 1; t += step)
509 {
510 BUtilities::Point pa = getPointPerc (p1, p2, t);
511 BUtilities::Point pb = getPointPerc (p2, p3, t);
512 BUtilities::Point pc = getPointPerc (p3, p4, t);
513 BUtilities::Point pm = getPointPerc (pa, pb, t);
514 BUtilities::Point pn = getPointPerc (pb, pc, t);
515 BUtilities::Point pz = getPointPerc (pm, pn, t);
516
517 drawLineOnMap (py, pz);
518 py = pz;
519 }
520 drawLineOnMap (py,p4);
521 }
522
getMapRawValue(const double x) const523 template<size_t shapesize> double Shape<shapesize>::getMapRawValue (const double x) const
524 {
525 double mapx = fmod (x * MAPRES, MAPRES);
526 double xmod = mapx - int (mapx);
527
528 return (1 - xmod) * map_[int (mapx)] + xmod * map_[int (mapx + 1) % MAPRES];
529 }
530
getMapValue(const double x) const531 template<size_t shapesize> double Shape<shapesize>::getMapValue (const double x) const
532 {
533 return retransform (getMapRawValue (x));
534 }
535
getMap()536 template<size_t sz> double* Shape<sz>::getMap () {return &map_[0];}
537
538 /*
539 template<size_t sz> std::ostream &operator<<(std::ostream &output, Shape<sz>& shape)
540 {
541 output << shape.nodes;
542 return output;
543 }
544 */
545 #endif /* SHAPE_HPP_ */
546