1 /**
2  * File name: envelope.h
3  * Project: Geonkick (A kick synthesizer)
4  *
5  * Copyright (C) 2017 Iurie Nistor <http://iuriepage.wordpress.com>
6  *
7  * This file is part of Geonkick.
8  *
9  * GeonKick is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  */
23 
24 #include "envelope.h"
25 #include "globals.h"
26 
27 #include <iomanip>
28 #include <math.h>
29 #include <cmath>	// for std::llround()
30 
Envelope(const RkRect & area)31 Envelope::Envelope(const RkRect &area)
32         : drawingArea{area}
33         , pointRadius{10}
34         , dotRadius{3}
35         , selectedPointIndex{0}
36         , supportedTypes({Type::Amplitude,
37 			 Type::Frequency,
38 			 Type::FilterCutOff,
39                          Type::DistortionDrive,
40                          Type::DistortionVolume,
41                          Type::PitchShift})
42         , overPointIndex{0}
43         , isOverPoint{false}
44         , pointSelected{false}
45         , envelopeCategory{Category::Oscillator1}
46         , envelopeType{Type::Amplitude}
47 {
48 }
49 
W(void) const50 int Envelope::W(void) const
51 {
52         return drawingArea.width();
53 }
54 
H(void) const55 int Envelope::H(void) const
56 {
57         return drawingArea.height();
58 }
59 
getOrigin(void) const60 RkPoint Envelope::getOrigin(void) const
61 {
62         return drawingArea.bottomLeft();
63 }
64 
draw(RkPainter & painter,DrawLayer layer)65 void Envelope::draw(RkPainter &painter, DrawLayer layer)
66 {
67         if (layer == DrawLayer::Axies) {
68                 drawAxies(painter);
69                 drawScale(painter);
70         } else if (layer == DrawLayer::Envelope) {
71                 drawPoints(painter);
72                 drawLines(painter);
73         }
74 }
75 
drawAxies(RkPainter & painter)76 void Envelope::drawAxies(RkPainter & painter)
77 {
78         auto pen = painter.pen();
79         pen.setColor(RkColor(125, 125, 125));
80         pen.setWidth(1);
81         painter.setPen(pen);
82         RkPoint point = getOrigin();
83         painter.drawLine(point.x(), point.y(), point.x() + W() + 10, point.y());
84         painter.drawLine(point.x(), point.y(), point.x(), point.y() - H() - 10);
85 }
86 
drawScale(RkPainter & painter)87 void Envelope::drawScale(RkPainter &painter)
88 {
89         drawTimeScale(painter);
90         drawValueScale(painter);
91 }
92 
drawTimeScale(RkPainter & painter)93 void Envelope::drawTimeScale(RkPainter &painter)
94 {
95         RkFont font = painter.font();
96         font.setSize(10);
97         painter.setFont(font);
98 
99         auto val = envelopeLengh() / 10;
100         int dx = W() / 10;
101         RkPoint point = getOrigin();
102         int x  = point.x() + dx;
103         for (auto i = 1; i <= 10; i++) {
104                 RkPen pen(RkColor(80, 80, 80));
105                 pen.setStyle(RkPen::PenStyle::DotLine);
106                 painter.setPen(pen);
107                 painter.drawLine(x, point.y() - font.size() - 4, x, point.y() - H());
108 
109                 RkRect rect(x - 12, point.y() - 12, 25, font.size());
110                 painter.setPen(RkColor(110, 110, 110));
111                 painter.drawText(rect, std::to_string(std::llround(i * val)));
112                 x += dx;
113         }
114 
115         font.setSize(12);
116         painter.setFont(font);
117         painter.setPen(RkPen(RkColor(180, 180, 180, 200)));
118         painter.drawText(point.x() + W() / 2 - 35, point.y() +  font.size() + 10,
119                          "Length, " + std::to_string(std::llround(envelopeLengh())) + " ms");
120 }
121 
drawValueScale(RkPainter & painter)122 void Envelope::drawValueScale(RkPainter &painter)
123 {
124         std::string text;
125         switch (type()) {
126         case Type::Amplitude:
127                 text = "Amplitude";
128                 break;
129         case Type::DistortionDrive:
130 		text = "Distortion Drive";
131                 break;
132         case Type::DistortionVolume:
133                 text = "Distortion Volume";
134                 break;
135         case Type::Frequency:
136         case Type::FilterCutOff:
137                 text = "Frequency, Hz";
138                 break;
139         case Type::PitchShift:
140                 text = "Semitones";
141                 break;
142         default:
143                 text = "Unknown";
144         }
145 
146         painter.translate(RkPoint(getOrigin().x() - 30, getOrigin().y() - H() / 2 + 35));
147         painter.rotate(-M_PI / 2);
148 
149         painter.drawText(-5, -5, text);
150         painter.rotate(M_PI / 2);
151         painter.translate(RkPoint(-(getOrigin().x() - 30), -(getOrigin().y() - H() / 2 + 35)));
152 
153         RkFont font = painter.font();
154         font.setSize(10);
155         painter.setFont(font);
156         int rectH = font.size() + 2;
157         painter.setPen(RkPen(RkColor(110, 110, 110)));
158 
159         if (type() == Type::Amplitude
160             || type() == Type::DistortionDrive
161             || type() == Type::DistortionVolume) {
162                 double step = envelopeAmplitude() / 10;
163                 double amplitude = envelopeAmplitude();
164                 for (int i = 1; i <= 10; i++) {
165                         int x = getOrigin().x();
166                         int y = 0;
167                         if (amplitude > std::numeric_limits<double>::min())
168                                 y = getOrigin().y() - H() * (i * step / amplitude);
169                         RkPen pen(RkColor(80, 80, 80));
170                         pen.setStyle(RkPen::PenStyle::DotLine);
171                         painter.setPen(pen);
172                         painter.drawLine(x + 1, y, x + W(), y);
173                         RkRect rect(x - 28,  y -  rectH / 2, 22, rectH);
174                         painter.setPen(RkPen(RkColor(110, 110, 110)));
175                         std::ostringstream ss;
176 			if ( type() == Type::DistortionDrive || type() == Type::DistortionVolume)
177 				ss << std::fixed << std::setprecision(2) << i * step * pow(10, 36.0/20);
178 			else
179 				ss << std::fixed << std::setprecision(2) << i * step;
180                         painter.drawText(rect, ss.str(), Rk::Alignment::AlignRight);
181                 }
182         } else if (type() == Type::PitchShift && envelopeAmplitude() > std::numeric_limits<double>::min()) {
183                 double step = 2 * std::abs(envelopeAmplitude() / 10);
184                 double amplitude = 2 * std::abs(envelopeAmplitude());
185                 for (int i = 1; i <= 10; i++) {
186                         int x = getOrigin().x();
187                         int y = 0;
188                         y = getOrigin().y() - H() * (i * step / amplitude);
189                         RkPen pen(RkColor(80, 80, 80));
190                         pen.setStyle(RkPen::PenStyle::DotLine);
191                         painter.setPen(pen);
192                         painter.drawLine(x + 1, y, x + W(), y);
193                         RkRect rect(x - 28,  y -  rectH / 2, 22, rectH);
194                         painter.setPen(RkPen(RkColor(110, 110, 110)));
195                         std::ostringstream ss;
196                         ss << std::fixed << std::setprecision(1) << i * step - amplitude / 2;
197                         painter.drawText(rect, ss.str(), Rk::Alignment::AlignRight);
198                 }
199         } else if (type() == Type::Frequency || type() == Type::FilterCutOff) {
200                 std::vector<int> values {20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 16000};
201                 for (auto value : values) {
202                         int x = getOrigin().x();
203                         int y = getOrigin().y() - H() * (log10(value) - log10(20)) / (log10(envelopeAmplitude()) - log10(20));
204                         if (y < 0)
205                                 break;
206 
207                         RkPen pen(RkColor(80, 80, 80));
208                         pen.setStyle(RkPen::PenStyle::DotLine);
209                         painter.setPen(pen);
210                         if (value != 20)
211                                 painter.drawLine(x, y, x + W(), y);
212 
213                         RkRect rect;
214                         if (value == 20)
215                                 rect = RkRect(x - 28, y - rectH / 2, 22, rectH);
216                         else
217                                 rect = RkRect(x - 28, y - rectH / 2, 22, rectH);
218                         painter.setPen(RkPen(RkColor(110, 110, 110)));
219                         std::string text;
220                         if (value >= 1000)
221                                 text = std::to_string(value / 1000) + "k";
222                         else
223                                 text = std::to_string(value);
224                         painter.drawText(rect, text, Rk::Alignment::AlignRight);
225                 }
226         }
227 
228 }
229 
drawPoints(RkPainter & painter)230 void Envelope::drawPoints(RkPainter &painter)
231 {
232         RkPen pen;
233         pen.setWidth(2);
234         pen.setColor(RkColor(200, 200, 200, 150));
235         RkPoint origin = getOrigin();
236 	for (decltype(envelopePoints.size()) i = 0; i < envelopePoints.size(); i++) {
237                 if (pointSelected && i == selectedPointIndex) {
238                         RkPen penSeleted;
239                         penSeleted.setWidth(2);
240                         penSeleted.setColor(RkColor(255, 255, 255, 255));
241                         painter.setPen(penSeleted);
242                 } else if (!pointSelected && isOverPoint && i == overPointIndex) {
243                         RkPen penSeleted;
244                         penSeleted.setWidth(2);
245                         penSeleted.setColor(RkColor(230, 230, 230, 200));
246                         painter.setPen(penSeleted);
247                 } else {
248                         painter.setPen(pen);
249                 }
250                 RkPoint scaledPoint = scaleUp(envelopePoints[i]);
251                 scaledPoint = RkPoint(scaledPoint.x() + origin.x(), origin.y() - scaledPoint.y());
252 		drawPoint(painter, scaledPoint);
253                 scaledPoint = RkPoint(scaledPoint.x(), scaledPoint.y() - 1.4 * getPointRadius());
254                 drawPointValue(painter, scaledPoint, envelopePoints[i].y());
255         }
256 }
257 
drawPoint(RkPainter & painter,const RkPoint & point)258 void Envelope::drawPoint(RkPainter &painter, const RkPoint &point)
259 {
260         painter.drawCircle(point, getPointRadius());
261         painter.drawCircle(point, getDotRadius());
262 }
263 
drawPointValue(RkPainter & painter,const RkPoint & point,double value)264 void Envelope::drawPointValue(RkPainter &painter, const RkPoint &point, double value)
265 {
266         if (type() == Envelope::Type::Amplitude
267             || type() == Type::DistortionDrive
268             || type() == Type::DistortionVolume) {
269                 value *= envelopeAmplitude();
270                 std::ostringstream ss;
271 		if (type() == Type::DistortionDrive || type() == Type::DistortionVolume)
272 			ss << std::setprecision(2) << value * pow(10, 36.0 / 20);
273 		else
274 			ss << std::setprecision(2) << value;
275                 painter.drawText(point.x(), point.y(), ss.str());
276         } else if (type() == Type::PitchShift) {
277                 value *= envelopeAmplitude();
278                 std::ostringstream ss;
279                 ss << std::fixed << std::setprecision(1) << 2 * value - envelopeAmplitude();
280                 painter.drawText(point.x(), point.y(), ss.str());
281         } else if (type() == Envelope::Type::Frequency || type() == Type::FilterCutOff) {
282                 value *= envelopeAmplitude();
283                 if (value < 20)
284                         painter.drawText(point.x(), point.y(), "20Hz " + frequencyToNote(20));
285                 if (value >= 20 && value < 1000) {
286                         painter.drawText(point.x(), point.y(), std::to_string(std::llround(value))
287                                          + "Hz " + frequencyToNote(value));
288                 } else if (value >= 1000 && value <= 20000) {
289                         std::ostringstream ss;
290                         ss.precision(1);
291                         ss << std::fixed << value / 1000;
292                         painter.drawText(point.x(), point.y(), ss.str() + "kHz " + frequencyToNote(value));
293                 }
294         }
295 }
296 
drawLines(RkPainter & painter)297 void Envelope::drawLines(RkPainter &painter)
298 {
299         if (envelopePoints.size() < 2)
300                 return;
301 
302         std::vector<RkPoint> points;
303         auto origin = getOrigin();
304 	for (const auto& point : envelopePoints) {
305                 auto scaledPoint = scaleUp(point);
306 	        points.push_back(RkPoint(origin.x() + scaledPoint.x(),
307                                         origin.y() - scaledPoint.y()));
308 	}
309 
310 	auto pen = painter.pen();
311 	pen.setWidth(2);
312         pen.setColor(RkColor(200, 200, 200, 150));
313 	painter.setPen(pen);
314 	painter.drawPolyline(points);
315 }
316 
overPoint(const RkPoint & point)317 void Envelope::overPoint(const RkPoint &point)
318 {
319         overPointIndex = 0;
320         isOverPoint = false;
321 	for (decltype(envelopePoints.size()) i = 0; i < envelopePoints.size(); i++) {
322 		if (hasPoint(envelopePoints[i], point)) {
323                         overPointIndex = i;
324                         isOverPoint = true;
325 			break;
326 		}
327         }
328 }
329 
hasOverPoint() const330 bool Envelope::hasOverPoint() const
331 {
332         return isOverPoint;
333 }
334 
hasSelected(void) const335 bool Envelope::hasSelected(void) const
336 {
337 	return pointSelected;
338 }
339 
selectPoint(const RkPoint & point)340 void Envelope::selectPoint(const RkPoint &point)
341 {
342         std::vector<RkRealPoint>::size_type index = 0;
343 	for (const auto& p : envelopePoints) {
344 		if (hasPoint(p, point)) {
345                         selectedPointIndex = overPointIndex = index;
346                         pointSelected = isOverPoint = true;
347 			break;
348 		}
349                 index++;
350         }
351 }
352 
unselectPoint(void)353 void Envelope::unselectPoint(void)
354 {
355         pointSelected = false;
356 }
357 
getLeftPointLimit(void) const358 double Envelope::getLeftPointLimit(void) const
359 {
360         double x;
361 	if (!pointSelected || envelopePoints.empty() || selectedPointIndex < 1)
362 		x = 0;
363 	else
364 		x = envelopePoints[selectedPointIndex - 1].x();
365 
366 	return x;
367 }
368 
getRightPointLimit(void) const369 double Envelope::getRightPointLimit(void) const
370 {
371 	double x;
372 	if (!pointSelected || envelopePoints.empty())
373 		x = 0;
374 	else if (selectedPointIndex >= envelopePoints.size() - 1)
375 		x = 1;
376 	else
377 		x = envelopePoints[selectedPointIndex + 1].x();
378 
379 	return x;
380 }
381 
moveSelectedPoint(int x,int y)382 void Envelope::moveSelectedPoint(int x, int y)
383 {
384         if (!pointSelected || envelopePoints.empty())
385 		return;
386 
387         auto scaledPoint = scaleDown(RkPoint(x, y));
388         auto &selectedPoint = envelopePoints[selectedPointIndex];
389 	if (scaledPoint.x() < getLeftPointLimit())
390                 selectedPoint.setX(getLeftPointLimit());
391 	else if (scaledPoint.x() > getRightPointLimit())
392                 selectedPoint.setX(getRightPointLimit());
393 	else
394                 selectedPoint.setX(scaledPoint.x());
395 
396 	if (y < 0)
397                 selectedPoint.setY(0);
398 	else if (y > H())
399                 selectedPoint.setY(1);
400 	else
401                 selectedPoint.setY(scaledPoint.y());
402 
403         pointUpdatedEvent(selectedPointIndex, selectedPoint.x(), selectedPoint.y());
404 }
405 
setPoints(const std::vector<RkRealPoint> & points)406 void Envelope::setPoints(const std::vector<RkRealPoint> &points)
407 {
408         removePoints();
409         for (const auto &point : points)
410                 envelopePoints.push_back(point);
411 }
412 
addPoint(const RkPoint & point)413 void Envelope::addPoint(const RkPoint &point)
414 {
415         auto scaledPoint = scaleDown(point);
416         if (scaledPoint.y() < 0)
417                 scaledPoint.setY(0);
418         else if (scaledPoint.y() > 1)
419                 scaledPoint.setY(1);
420 
421   	if (scaledPoint.x() > 1) {
422 	        scaledPoint.setX(1);
423 		envelopePoints.push_back(scaledPoint);
424 	} else if (scaledPoint.x() < 0) {
425                 scaledPoint.setX(0);
426                 envelopePoints.insert(envelopePoints.begin(), scaledPoint);
427         } else if (envelopePoints.empty()) {
428                 envelopePoints.push_back(scaledPoint);
429 	} else if (scaledPoint.x() <= envelopePoints[0].x()) {
430                 envelopePoints.insert(envelopePoints.begin(), scaledPoint);
431 	} else if (scaledPoint.x() >= envelopePoints.back().x()) {
432                 envelopePoints.push_back(scaledPoint);
433 	} else {
434 		for (auto it = envelopePoints.begin(); it != envelopePoints.end(); ++it) {
435 			if (scaledPoint.x() <= it->x()) {
436                                 envelopePoints.insert(it, scaledPoint);
437                                 break;
438 			}
439 		}
440 	}
441         pointAddedEvent(scaledPoint.x(), scaledPoint.y());
442 }
443 
removePoint(const RkPoint & point)444 void Envelope::removePoint(const RkPoint &point)
445 {
446         for (decltype(envelopePoints.size()) i = 0; i < envelopePoints.size(); i++) {
447 		if (hasPoint(envelopePoints[i], point)) {
448 			if (i != 0 && i != envelopePoints.size() - 1) {
449 				envelopePoints.erase(envelopePoints.begin() + i);
450                                 pointRemovedEvent(i);
451 			}
452 			break;
453 		}
454         }
455 }
456 
setCategory(Envelope::Category cat)457 void Envelope::setCategory(Envelope::Category cat)
458 {
459         envelopeCategory = cat;
460 }
461 
setType(Type type)462 void Envelope::setType(Type type)
463 {
464         if (isSupportedType(type)) {
465                 envelopeType = type;
466                 updatePoints();
467         }
468 }
469 
category(void) const470 Envelope::Category Envelope::category(void) const
471 {
472 	return envelopeCategory;
473 }
474 
type(void) const475 Envelope::Type Envelope::type(void) const
476 {
477 	return envelopeType;
478 }
479 
isSupportedType(Type type) const480 bool Envelope::isSupportedType(Type type) const
481 {
482         if (supportedTypes.find(type) != supportedTypes.end())
483                 return true;
484         return false;
485 }
486 
addSupportedType(Type type)487 void Envelope::addSupportedType(Type type)
488 {
489         if (!isSupportedType(type))
490                 supportedTypes.insert(type);
491 }
492 
removeSupportedType(Type type)493 void Envelope::removeSupportedType(Type type)
494 {
495         if (isSupportedType(type))
496                 supportedTypes.erase(type);
497 }
498 
removePoints()499 void Envelope::removePoints()
500 {
501         envelopePoints.clear();
502 }
503 
getDrawingArea()504 const RkRect& Envelope::getDrawingArea()
505 {
506         return drawingArea;
507 }
508 
setDrawingArea(const RkRect & rect)509 void Envelope::setDrawingArea(const RkRect &rect)
510 {
511         drawingArea = rect;
512 }
513 
scaleDown(const RkPoint & point)514 RkRealPoint Envelope::scaleDown(const RkPoint &point)
515 {
516         RkRealPoint scaledPoint;
517         if (type() == Type::Amplitude
518             || type() == Type::DistortionDrive
519             || type() == Type::DistortionVolume
520             || type() == Type::PitchShift) {
521                 scaledPoint = RkRealPoint(static_cast<double>(point.x()) / W(),
522                                           static_cast<double>(point.y()) / H());
523         } else {
524                 scaledPoint.setX(static_cast<double>(point.x()) / W());
525                 double logVal = (static_cast<double>(point.y()) / H()) * (log10(envelopeAmplitude()) - log10(20));
526                 double val = pow(10, logVal + log10(20));
527                 scaledPoint.setY(val / envelopeAmplitude());
528         }
529 
530         return scaledPoint;
531 }
532 
scaleUp(const RkRealPoint & point)533 RkPoint Envelope::scaleUp(const RkRealPoint &point)
534 {
535         int x, y;
536         if (type() == Type::Amplitude
537             || type() == Type::DistortionDrive
538             || type() == Type::DistortionVolume
539             || type() == Type::PitchShift) {
540                 x = point.x() * W();
541                 y = point.y() * H();
542         } else {
543                 x = static_cast<int>(point.x() * W());
544                 double logRange = log10(envelopeAmplitude()) - log10(20);
545                 double k = 0;
546                 if (point.y() > 0) {
547                         double logValue = log10(envelopeAmplitude() * point.y()) - log10(20);
548                         if (logValue > 0)
549                                 k = logValue / logRange;
550                 }
551                 y = k * H();
552         }
553         return RkPoint(x, y);
554 }
555 
hasPoint(const RkRealPoint & point,const RkPoint & p)556 bool Envelope::hasPoint(const RkRealPoint &point, const RkPoint &p)
557 {
558         RkPoint scaledPoint = scaleUp(point);
559         int r = getPointRadius();
560 	if (pow(p.x() - scaledPoint.x(), 2) + pow(p.y() - scaledPoint.y(), 2) < pow(r, 2))
561                 return true;
562 
563 	return false;
564 }
565 
getPointRadius() const566 int Envelope::getPointRadius() const
567 {
568         return pointRadius;
569 }
570 
setPointRadius(int radius)571 void Envelope::setPointRadius(int radius)
572 {
573         pointRadius = radius;
574 }
575 
getDotRadius() const576 int Envelope::getDotRadius() const
577 {
578         return dotRadius;
579 }
580 
setDotRadius(int radius)581 void Envelope::setDotRadius(int radius)
582 {
583         dotRadius = radius;
584 }
585 
frequencyToNote(rk_real f)586 std::string Envelope::frequencyToNote(rk_real f)
587 {
588         if (f < 16.35160 || f > 7902.133)
589                 return "";
590 
591         int n = 0;
592         while (f > 32.70320) {
593                 f /= 2;
594                 n++;
595         }
596 
597         int octave = n;
598         std::vector<double> pitches {
599 		        16.35160,
600                         17.32391,
601                         18.35405,
602 			19.44544,
603                         20.60172,
604                         21.82676,
605                         23.12465,
606                         24.49971,
607                         25.95654,
608                         27.50000,
609                         29.13524,
610                         30.86771};
611 	std::vector<std::string> notes{
612 		        "C",
613 			"C#",
614 			"D",
615 			"D#",
616 			"E",
617 			"F",
618 			"F#",
619 			"G",
620 			"G#",
621 			"A",
622 			"A#",
623 			"B"};
624         n = 12;
625         while (--n && pitches[n] > f);
626         if (n < 11 && f > (pitches[n + 1] - pitches[n]) / 2)
627                 n++;
628 
629         return "(" + notes[n] + std::to_string(octave) + ")";
630 }
631 
632