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