/* B.Shapr * Beat / envelope shaper LV2 plugin * * Copyright (C) 2019 by Sven Jähnichen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SHAPE_HPP_ #define SHAPE_HPP_ #include #include #include #include // #include #include "BUtilities/Point.hpp" #include "Node.hpp" #include "StaticArrayList.hpp" #define MAPRES 1024 template class Shape { public: Shape (); Shape (const StaticArrayList nodes, double transformFactor = 1.0, double transformOffset = 0.0); virtual ~Shape (); bool operator== (const Shape& rhs); bool operator!= (const Shape& rhs); void setTransformation (const double transformFactor, const double transformOffset); virtual void clearShape (); virtual void setDefaultShape (); bool isDefault () const; size_t size () const; Node getRawNode (const size_t nr) const; Node getNode (const size_t nr) const; size_t findRawNode (const Node& node); bool validateNode (const size_t nr); bool validateShape (); bool appendRawNode (const Node& node); bool appendNode (const Node& node); bool insertRawNode (const size_t pos, const Node& node); bool insertNode (const size_t pos, const Node& node); bool insertRawNode (const Node& node); bool insertNode (const Node& node); bool changeRawNode (const size_t pos, const Node& newnode); bool changeNode (const size_t pos, const Node& newnode); bool deleteNode (const size_t pos); double getMapRawValue (const double x) const; double getMapValue (const double x) const; double* getMap (); protected: double transform (const double value) const; double retransform (const double value) const; Node transformNode (const Node& node) const; Node retransformNode (const Node& node) const; virtual void drawLineOnMap (const BUtilities::Point p1, const BUtilities::Point p2); BUtilities::Point getPointPerc (const BUtilities::Point p1, const BUtilities::Point p2, const double perc) const; virtual void renderBezier (const Node& n1, const Node& n2); StaticArrayList nodes_; double map_[MAPRES]; double factor_; double offset_; }; template Shape::Shape () : nodes_ (), map_ {0.0}, factor_ (1.0), offset_ (0.0) {} template Shape::Shape (const StaticArrayList nodes, double transformFactor, double transformOffset) : nodes_ (nodes), map_ {0.0}, factor_ (transformFactor), offset_ (transformFactor) {} template Shape::~Shape () {} template bool Shape::operator== (const Shape& rhs) { if (size () != rhs.size ()) return false; for (unsigned int i = 0; i < size (); ++i) if (nodes_[i] != rhs.nodes_[i]) return false; return true; } template bool Shape::operator!= (const Shape& rhs) {return !(*this == rhs);} template void Shape::setTransformation (const double transformFactor, const double transformOffset) { // Prevent div by zero if (transformFactor == 0.0) return; factor_ = transformFactor; offset_ = transformOffset; } template void Shape::clearShape () { while (!nodes_.empty ()) nodes_.pop_back (); for (int i = 0; i < MAPRES; ++i) map_[i] = 0; } template void Shape::setDefaultShape () { clearShape (); nodes_.push_back ({NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}}); nodes_.push_back ({NodeType::END_NODE, {1, 0}, {0, 0}, {0, 0}}); renderBezier (nodes_[0], nodes_[1]); } templatebool Shape::isDefault () const { return ((nodes_.size == 2) && (nodes_[0] == Node {NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}})); } templatesize_t Shape::size () const {return nodes_.size;} templateNode Shape::getRawNode (const size_t nr) const {return nodes_[nr];} templateNode Shape::getNode (const size_t nr) const {return retransformNode (getRawNode (nr));} templatesize_t Shape::findRawNode (const Node& node) { for (int i = 0; i < nodes_.size; ++i) { if (nodes_[i] == node) return i; } return nodes_.size; } template bool Shape::appendRawNode (const Node& node) { if (nodes_.size < sz) { nodes_.push_back (node); return true; } return false; } template bool Shape::appendNode (const Node& node) { return appendRawNode (transformNode (node)); } template bool Shape::insertRawNode (const size_t pos, const Node& node) { // Nodes full => errorNode if (nodes_.size >= sz) return false; size_t p = pos; // Insert if (p < nodes_.size) nodes_.insert (nodes_.begin() + p, node); // Otherwise append else { p = nodes_.size; nodes_.push_back (node); } // Validate node and its neighbors if (!validateNode (p)) return false; if ((p > 0) && (!validateNode (p - 1))) return false; if ((p + 1 < nodes_.size) && (!validateNode (p + 1))) return false; // Update map for (unsigned int i = (p >= 2 ? p - 2 : 0); (i <= p + 1) && (i + 1 < nodes_.size); ++ i) renderBezier (nodes_[i], nodes_[i + 1]); return true; } template bool Shape::insertNode (const size_t pos, const Node& node) { return insertRawNode (pos, transformNode (node)); } template bool Shape::insertRawNode (const Node& node) { // Find position size_t pos = nodes_.size; for (unsigned int i = 0; i < nodes_.size; ++i) { if (node.point.x < nodes_[i].point.x) { // Add if not redundant if ((i > 0) && (node != nodes_[i - 1])) { pos = i; break; } else return false; } } // Insert return insertRawNode (pos, node); } template bool Shape::insertNode (const Node& node) { return insertRawNode (transformNode (node)); } template bool Shape::changeRawNode (const size_t pos, const Node& node) { if (pos >= nodes_.size) return false; nodes_[pos] = node; // Validate node and its neighbors if (!validateNode (pos)) return false; if ((pos > 0) && (!validateNode (pos - 1))) return false; if ((pos + 1 < nodes_.size) && (!validateNode (pos + 1))) return false; // Update map for (unsigned int i = (pos >= 2 ? pos - 2 : 0); (i <= pos + 1) && (i + 1 < nodes_.size); ++i) renderBezier (nodes_[i], nodes_[i + 1]); return true; } template bool Shape::changeNode (const size_t pos, const Node& node) { return changeRawNode (pos, transformNode (node)); } template bool Shape::deleteNode (const size_t pos) { // Only deletion of middle nodes allowed if ((pos == 0) || (pos >= nodes_.size - 1)) return false; nodes_.erase (nodes_.begin() + pos); // Validate neighbor nodes if (!validateNode (pos - 1)) return false; if (!validateNode (pos)) return false; // Update map for (unsigned int i = (pos >= 2 ? pos - 2 : 0); (i <= pos) && (i + 1 < nodes_.size); ++ i) renderBezier (nodes_[i], nodes_[i + 1]); return true; } template bool Shape::validateNode (const size_t nr) { // Exception: Invalid parameters if (nr >= nodes_.size) { fprintf (stderr, "BSchaffl.lv2: Node validation called with invalid parameters (node: %li).\n", nr); return false; } // Exception: Invalid node order if ((nodes_.size >= 3) && (nr > 1) && (nr < nodes_.size - 1) && (nodes_[nr-1].point.x > nodes_[nr+1].point.x)) { fprintf (stderr, "BSchaffl.lv2: Corrupt node data at node %li (%f, %f). Reset shape.\n", nr, nodes_[nr].point.x, nodes_[nr].point.y); setDefaultShape (); return false; } // Start node if (nr == 0) { // Check: Only end nodes on start position if (nodes_[0].nodeType != NodeType::END_NODE) nodes_[0] = {NodeType::END_NODE, {0, 0}, {0, 0}, {0, 0}}; // Check: Start position if (nodes_[0].point.x != 0) nodes_[0].point.x = 0; // Check: No handles nodes_[0].handle1 = BUtilities::Point (0, 0); nodes_[0].handle2 = BUtilities::Point (0, 0); } // End node else if (nr == nodes_.size - 1) { // Check: Only end nodes on end position if (nodes_[nr].nodeType != NodeType::END_NODE) { nodes_[nr] = {NodeType::END_NODE, {1, nodes_[0].point.y}, {0, 0}, {0, 0}}; } // Check: No handles nodes_[nr].handle1 = BUtilities::Point (0, 0); nodes_[nr].handle2 = BUtilities::Point (0, 0); } // Middle nodes else { // Check: No end nodes in the middle if (nodes_[nr].nodeType == NodeType::END_NODE) nodes_[nr].nodeType = NodeType::CORNER_NODE; // Check: Nodes point order if (nodes_[nr].point.x < nodes_[nr - 1].point.x) nodes_[nr].point.x = nodes_[nr - 1].point.x; if (nodes_[nr].point.x > nodes_[nr + 1].point.x) nodes_[nr].point.x = nodes_[nr + 1].point.x; // Check: POINT_NODE without handles if (nodes_[nr].nodeType == NodeType::POINT_NODE) { nodes_[nr].handle1 = BUtilities::Point (0, 0); nodes_[nr].handle2 = BUtilities::Point (0, 0); } // Check: Handles order if (nodes_[nr].handle1.x > 0) nodes_[nr].handle1.x = 0; if (nodes_[nr].handle2.x < 0) nodes_[nr].handle2.x = 0; // Check: AUTO_SMOOTH_NODE with symmetric handles with the half size of the distance to the closest neighbor point if (nodes_[nr].nodeType == NodeType::AUTO_SMOOTH_NODE) { double dist = (nodes_[nr].point.x - nodes_[nr-1].point.x > nodes_[nr+1].point.x - nodes_[nr].point.x ? nodes_[nr+1].point.x - nodes_[nr].point.x : nodes_[nr].point.x - nodes_[nr-1].point.x); double ydist = (nodes_[nr + 1].point.y - nodes_[nr - 1].point.y); double yamp = (fabs (nodes_[nr - 1].point.y != 0) && fabs (nodes_[nr + 1].point.y != 0) ? (fabs (nodes_[nr - 1].point.y) < fabs (nodes_[nr + 1].point.y) ? fabs (nodes_[nr - 1].point.y) / (fabs (nodes_[nr - 1].point.y) + fabs (nodes_[nr + 1].point.y)) : fabs (nodes_[nr + 1].point.y) / (fabs (nodes_[nr - 1].point.y) + fabs (nodes_[nr + 1].point.y))) : 0); nodes_[nr].handle1.x = -dist / 2; nodes_[nr].handle1.y = -ydist * yamp; nodes_[nr].handle2.x = dist / 2; nodes_[nr].handle2.y = ydist * yamp; } // Check: SYMMETRIC_SMOOTH_NODE must be symmetric else if (nodes_[nr].nodeType == NodeType::SYMMETRIC_SMOOTH_NODE) { //Check if handle1 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x) { double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x; nodes_[nr].handle1.x *= f; nodes_[nr].handle1.y *= f; } // Make handele2 symmetric to handle1 nodes_[nr].handle2 = BUtilities::Point (0, 0) - nodes_[nr].handle1; //Check if handle2 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x) { double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x; nodes_[nr].handle2.x *= f; nodes_[nr].handle2.y *= f; nodes_[nr].handle1 = BUtilities::Point (0, 0) - nodes_[nr].handle2; } } // Check: SMOOTH_NODE handles point to opposite directions else if (nodes_[nr].nodeType == NodeType::SMOOTH_NODE) { //Check if handle1 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x) { double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x; nodes_[nr].handle1.x *= f; nodes_[nr].handle1.y *= f; } // Calculate handle distances double dist1 = sqrt (nodes_[nr].handle1.x * nodes_[nr].handle1.x + nodes_[nr].handle1.y * nodes_[nr].handle1.y); double dist2 = sqrt (nodes_[nr].handle2.x * nodes_[nr].handle2.x + nodes_[nr].handle2.y * nodes_[nr].handle2.y); // Recalculate handle2 if ((dist1 != 0) && (dist2 != 0)) { nodes_[nr].handle2.x = -(nodes_[nr].handle1.x * dist2 / dist1); nodes_[nr].handle2.y = -(nodes_[nr].handle1.y * dist2 / dist1); } //Check if handle2 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x) { double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x; nodes_[nr].handle2.x *= f; nodes_[nr].handle2.y *= f; } } // Check: CORNER_NODE else if (nodes_[nr].nodeType == NodeType::CORNER_NODE) { //Check if handle1 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle1.x < nodes_[nr-1].point.x) { double f = (nodes_[nr-1].point.x - nodes_[nr].point.x) / nodes_[nr].handle1.x; nodes_[nr].handle1.x *= f; nodes_[nr].handle1.y *= f; } //Check if handle2 overlaps neighbor point if (nodes_[nr].point.x + nodes_[nr].handle2.x > nodes_[nr+1].point.x) { double f = (nodes_[nr+1].point.x - nodes_[nr].point.x) / nodes_[nr].handle2.x; nodes_[nr].handle2.x *= f; nodes_[nr].handle2.y *= f; } } } return true; } template bool Shape::validateShape () { // TODO Sort ??? // Validate nodes bool status = true; for (unsigned int i = 0; i < nodes_.size; ++i) { if (!validateNode (i)) status = false; } // Update map for (unsigned int i = 0; i + 1 < nodes_.size; ++i) renderBezier (nodes_[i], nodes_[i+1]); return status; } template double Shape::transform (const double value) const { return (value - offset_) / factor_; } template double Shape::retransform (const double value) const { return factor_ * value + offset_; } template Node Shape::transformNode (const Node& node) const { return Node ( node.nodeType, BUtilities::Point (node.point.x, transform (node.point.y)), BUtilities::Point (node.handle1.x, node.handle1.y / factor_), BUtilities::Point (node.handle2.x, node.handle2.y / factor_) ); } template Node Shape::retransformNode (const Node& node) const { return Node ( node.nodeType, BUtilities::Point (node.point.x, retransform (node.point.y)), BUtilities::Point (node.handle1.x, node.handle1.y * factor_), BUtilities::Point (node.handle2.x, node.handle2.y * factor_) ); } template void Shape::drawLineOnMap (BUtilities::Point p1, BUtilities::Point p2) { if (p1.x < p2.x) { for (double x = p1.x; (x <= p2.x) && (x <= 1.0); x += (1.0 / MAPRES)) { uint32_t i = ((uint32_t) (x * MAPRES)) % MAPRES; map_[i] = p1.y + (p2.y - p1.y) * (x - p1.x) / (p2.x - p1.x); } } else { uint32_t i = ((uint32_t) (p2.x * MAPRES)) % MAPRES; map_ [i] = p2.y; } } template BUtilities::Point Shape::getPointPerc (const BUtilities::Point p1, const BUtilities::Point p2 , const double perc) const { BUtilities::Point p; p.x = p1.x + (p2.x - p1.x) * perc; p.y = p1.y + (p2.y - p1.y) * perc; return p; } template void Shape::renderBezier (const Node& n1, const Node& n2) { // Interpolate Bezier curve BUtilities::Point p1 = n1.point; BUtilities::Point p2 = n1.point + n1.handle2; BUtilities::Point p4 = n2.point; BUtilities::Point p3 = n2.point + n2.handle1; BUtilities::Point py = p1; double step = 1 / (fabs (n2.point.x - n1.point.x) * MAPRES + 1); for (double t = 0; t < 1; t += step) { BUtilities::Point pa = getPointPerc (p1, p2, t); BUtilities::Point pb = getPointPerc (p2, p3, t); BUtilities::Point pc = getPointPerc (p3, p4, t); BUtilities::Point pm = getPointPerc (pa, pb, t); BUtilities::Point pn = getPointPerc (pb, pc, t); BUtilities::Point pz = getPointPerc (pm, pn, t); drawLineOnMap (py, pz); py = pz; } drawLineOnMap (py,p4); } template double Shape::getMapRawValue (const double x) const { double mapx = fmod (x * MAPRES, MAPRES); double xmod = mapx - int (mapx); return (1 - xmod) * map_[int (mapx)] + xmod * map_[int (mapx + 1) % MAPRES]; } template double Shape::getMapValue (const double x) const { return retransform (getMapRawValue (x)); } template double* Shape::getMap () {return &map_[0];} /* template std::ostream &operator<<(std::ostream &output, Shape& shape) { output << shape.nodes; return output; } */ #endif /* SHAPE_HPP_ */