1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "NoteLayer.h"
17
18 #include "data/model/Model.h"
19 #include "base/RealTime.h"
20 #include "base/Profiler.h"
21 #include "base/Pitch.h"
22 #include "base/LogRange.h"
23 #include "base/RangeMapper.h"
24 #include "view/View.h"
25
26 #include "ColourDatabase.h"
27 #include "PianoScale.h"
28 #include "LinearNumericalScale.h"
29 #include "LogNumericalScale.h"
30 #include "PaintAssistant.h"
31
32 #include "data/model/NoteModel.h"
33
34 #include "widgets/ItemEditDialog.h"
35 #include "widgets/TextAbbrev.h"
36
37 #include <QPainter>
38 #include <QPainterPath>
39 #include <QMouseEvent>
40 #include <QTextStream>
41 #include <QMessageBox>
42
43 #include <iostream>
44 #include <cmath>
45 #include <utility>
46
47 //#define DEBUG_NOTE_LAYER 1
48
NoteLayer()49 NoteLayer::NoteLayer() :
50 SingleColourLayer(),
51 m_modelUsesHz(true),
52 m_editing(false),
53 m_dragPointX(0),
54 m_dragPointY(0),
55 m_dragStartX(0),
56 m_dragStartY(0),
57 m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
58 m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
59 m_editingCommand(nullptr),
60 m_editIsOpen(false),
61 m_verticalScale(AutoAlignScale),
62 m_scaleMinimum(0),
63 m_scaleMaximum(0)
64 {
65 SVDEBUG << "constructed NoteLayer" << endl;
66 }
67
68 int
getCompletion(LayerGeometryProvider *) const69 NoteLayer::getCompletion(LayerGeometryProvider *) const
70 {
71 auto model = ModelById::get(m_model);
72 if (model) return model->getCompletion();
73 else return 0;
74 }
75
76 void
setModel(ModelId modelId)77 NoteLayer::setModel(ModelId modelId)
78 {
79 auto newModel = ModelById::getAs<NoteModel>(modelId);
80
81 if (!modelId.isNone() && !newModel) {
82 throw std::logic_error("Not a NoteModel");
83 }
84
85 if (m_model == modelId) return;
86 m_model = modelId;
87
88 if (newModel) {
89 connectSignals(m_model);
90
91 QString unit = newModel->getScaleUnits();
92 m_modelUsesHz = (unit.toLower() == "hz");
93 }
94
95 m_scaleMinimum = 0;
96 m_scaleMaximum = 0;
97
98 emit modelReplaced();
99 }
100
101 Layer::PropertyList
getProperties() const102 NoteLayer::getProperties() const
103 {
104 PropertyList list = SingleColourLayer::getProperties();
105 list.push_back("Vertical Scale");
106 list.push_back("Scale Units");
107 return list;
108 }
109
110 QString
getPropertyLabel(const PropertyName & name) const111 NoteLayer::getPropertyLabel(const PropertyName &name) const
112 {
113 if (name == "Vertical Scale") return tr("Vertical Scale");
114 if (name == "Scale Units") return tr("Scale Units");
115 return SingleColourLayer::getPropertyLabel(name);
116 }
117
118 Layer::PropertyType
getPropertyType(const PropertyName & name) const119 NoteLayer::getPropertyType(const PropertyName &name) const
120 {
121 if (name == "Scale Units") return UnitsProperty;
122 if (name == "Vertical Scale") return ValueProperty;
123 return SingleColourLayer::getPropertyType(name);
124 }
125
126 QString
getPropertyGroupName(const PropertyName & name) const127 NoteLayer::getPropertyGroupName(const PropertyName &name) const
128 {
129 if (name == "Vertical Scale" || name == "Scale Units") {
130 return tr("Scale");
131 }
132 return SingleColourLayer::getPropertyGroupName(name);
133 }
134
135 QString
getScaleUnits() const136 NoteLayer::getScaleUnits() const
137 {
138 return "Hz";
139 }
140
141 int
getPropertyRangeAndValue(const PropertyName & name,int * min,int * max,int * deflt) const142 NoteLayer::getPropertyRangeAndValue(const PropertyName &name,
143 int *min, int *max, int *deflt) const
144 {
145 int val = 0;
146
147 if (name == "Vertical Scale") {
148
149 if (min) *min = 0;
150 if (max) *max = 3;
151 if (deflt) *deflt = int(AutoAlignScale);
152
153 val = int(m_verticalScale);
154
155 } else if (name == "Scale Units") {
156
157 if (deflt) *deflt = 0;
158 auto model = ModelById::getAs<NoteModel>(m_model);
159 if (model) {
160 val = UnitDatabase::getInstance()->getUnitId
161 (model->getScaleUnits());
162 }
163
164 } else {
165
166 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
167 }
168
169 return val;
170 }
171
172 QString
getPropertyValueLabel(const PropertyName & name,int value) const173 NoteLayer::getPropertyValueLabel(const PropertyName &name,
174 int value) const
175 {
176 if (name == "Vertical Scale") {
177 switch (value) {
178 default:
179 case 0: return tr("Auto-Align");
180 case 1: return tr("Linear");
181 case 2: return tr("Log");
182 case 3: return tr("MIDI Notes");
183 }
184 }
185 return SingleColourLayer::getPropertyValueLabel(name, value);
186 }
187
188 void
setProperty(const PropertyName & name,int value)189 NoteLayer::setProperty(const PropertyName &name, int value)
190 {
191 if (name == "Vertical Scale") {
192 setVerticalScale(VerticalScale(value));
193 } else if (name == "Scale Units") {
194 auto model = ModelById::getAs<NoteModel>(m_model);
195 if (model) {
196 QString unit = UnitDatabase::getInstance()->getUnitById(value);
197 model->setScaleUnits(unit);
198 m_modelUsesHz = (unit.toLower() == "hz");
199 emit modelChanged(m_model);
200 }
201 } else {
202 return SingleColourLayer::setProperty(name, value);
203 }
204 }
205
206 void
setVerticalScale(VerticalScale scale)207 NoteLayer::setVerticalScale(VerticalScale scale)
208 {
209 if (m_verticalScale == scale) return;
210 m_verticalScale = scale;
211 emit layerParametersChanged();
212 }
213
214 bool
isLayerScrollable(const LayerGeometryProvider * v) const215 NoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
216 {
217 QPoint discard;
218 return !v->shouldIlluminateLocalFeatures(this, discard);
219 }
220
221 double
valueOf(const Event & e) const222 NoteLayer::valueOf(const Event &e) const
223 {
224 return convertValueFromEventValue(e.getValue());
225 }
226
227 Event
eventWithValue(const Event & e,double value) const228 NoteLayer::eventWithValue(const Event &e, double value) const
229 {
230 return e.withValue(convertValueToEventValue(value));
231 }
232
233 double
convertValueFromEventValue(float eventValue) const234 NoteLayer::convertValueFromEventValue(float eventValue) const
235 {
236 if (m_modelUsesHz) {
237 return eventValue;
238 } else {
239 double v = eventValue;
240 if (v < 0) v = 0;
241 if (v > 127) v = 127;
242 int p = int(round(v));
243 double c = 100.0 * (v - p);
244 return Pitch::getFrequencyForPitch(p, c);
245 }
246 }
247
248 float
convertValueToEventValue(double value) const249 NoteLayer::convertValueToEventValue(double value) const
250 {
251 if (m_modelUsesHz) {
252 return float(value);
253 } else {
254 float c = 0;
255 int p = Pitch::getPitchForFrequency(value, &c);
256 return float(p) + c / 100.f;
257 }
258 }
259
260 bool
getValueExtents(double & min,double & max,bool & logarithmic,QString & unit) const261 NoteLayer::getValueExtents(double &min, double &max,
262 bool &logarithmic, QString &unit) const
263 {
264 auto model = ModelById::getAs<NoteModel>(m_model);
265 if (!model) return false;
266
267 min = convertValueFromEventValue(model->getValueMinimum());
268 max = convertValueFromEventValue(model->getValueMaximum());
269 min /= 1.06;
270 max *= 1.06;
271 unit = "Hz";
272
273 if (m_verticalScale != LinearScale) {
274 logarithmic = true;
275 }
276
277 return true;
278 }
279
280 bool
getDisplayExtents(double & min,double & max) const281 NoteLayer::getDisplayExtents(double &min, double &max) const
282 {
283 auto model = ModelById::getAs<NoteModel>(m_model);
284 if (!model || shouldAutoAlign()) return false;
285
286 if (m_verticalScale == MIDIRangeScale) {
287 min = Pitch::getFrequencyForPitch(0);
288 max = Pitch::getFrequencyForPitch(127);
289 return true;
290 }
291
292 if (m_scaleMinimum == m_scaleMaximum) {
293 QString unit;
294 bool log = false;
295 getValueExtents(min, max, log, unit);
296 } else {
297 min = m_scaleMinimum;
298 max = m_scaleMaximum;
299 }
300
301 #ifdef DEBUG_NOTE_LAYER
302 SVCERR << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
303 #endif
304
305 return true;
306 }
307
308 bool
setDisplayExtents(double min,double max)309 NoteLayer::setDisplayExtents(double min, double max)
310 {
311 if (m_model.isNone()) return false;
312
313 if (min == max) {
314 if (min == 0.f) {
315 max = 1.f;
316 } else {
317 max = min * 1.0001;
318 }
319 }
320
321 m_scaleMinimum = min;
322 m_scaleMaximum = max;
323
324 #ifdef DEBUG_NOTE_LAYER
325 SVCERR << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
326 #endif
327
328 emit layerParametersChanged();
329 return true;
330 }
331
332 int
getVerticalZoomSteps(int & defaultStep) const333 NoteLayer::getVerticalZoomSteps(int &defaultStep) const
334 {
335 if (shouldAutoAlign() || m_model.isNone()) return 0;
336 defaultStep = 0;
337 return 100;
338 }
339
340 int
getCurrentVerticalZoomStep() const341 NoteLayer::getCurrentVerticalZoomStep() const
342 {
343 if (shouldAutoAlign() || m_model.isNone()) return 0;
344
345 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
346 if (!mapper) return 0;
347
348 double dmin, dmax;
349 getDisplayExtents(dmin, dmax);
350
351 int nr = mapper->getPositionForValue(dmax - dmin);
352
353 delete mapper;
354
355 return 100 - nr;
356 }
357
358 //!!! lots of duplication with TimeValueLayer
359
360 void
setVerticalZoomStep(int step)361 NoteLayer::setVerticalZoomStep(int step)
362 {
363 if (shouldAutoAlign() || m_model.isNone()) return;
364
365 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
366 if (!mapper) return;
367
368 double min, max;
369 bool logarithmic;
370 QString unit;
371 getValueExtents(min, max, logarithmic, unit);
372
373 double dmin, dmax;
374 getDisplayExtents(dmin, dmax);
375
376 double newdist = mapper->getValueForPosition(100 - step);
377
378 double newmin, newmax;
379
380 if (logarithmic) {
381
382 // see SpectrogramLayer::setVerticalZoomStep
383
384 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
385 newmin = newmax - newdist;
386
387 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
388
389 } else {
390 double dmid = (dmax + dmin) / 2;
391 newmin = dmid - newdist / 2;
392 newmax = dmid + newdist / 2;
393 }
394
395 if (newmin < min) {
396 newmax += (min - newmin);
397 newmin = min;
398 }
399 if (newmax > max) {
400 newmax = max;
401 }
402
403 #ifdef DEBUG_NOTE_LAYER
404 SVCERR << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
405 #endif
406
407 setDisplayExtents(newmin, newmax);
408 }
409
410 RangeMapper *
getNewVerticalZoomRangeMapper() const411 NoteLayer::getNewVerticalZoomRangeMapper() const
412 {
413 if (m_model.isNone()) return nullptr;
414
415 RangeMapper *mapper;
416
417 double min, max;
418 bool logarithmic;
419 QString unit;
420 getValueExtents(min, max, logarithmic, unit);
421
422 if (min == max) return nullptr;
423
424 if (logarithmic) {
425 mapper = new LogRangeMapper(0, 100, min, max, unit);
426 } else {
427 mapper = new LinearRangeMapper(0, 100, min, max, unit);
428 }
429
430 return mapper;
431 }
432
433 EventVector
getLocalPoints(LayerGeometryProvider * v,int x) const434 NoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
435 {
436 auto model = ModelById::getAs<NoteModel>(m_model);
437 if (!model) return {};
438
439 sv_frame_t frame = v->getFrameForX(x);
440
441 EventVector local = model->getEventsCovering(frame);
442 if (!local.empty()) return local;
443
444 int fuzz = ViewManager::scalePixelSize(2);
445 sv_frame_t start = v->getFrameForX(x - fuzz);
446 sv_frame_t end = v->getFrameForX(x + fuzz);
447
448 local = model->getEventsStartingWithin(frame, end - frame);
449 if (!local.empty()) return local;
450
451 local = model->getEventsSpanning(start, frame - start);
452 if (!local.empty()) return local;
453
454 return {};
455 }
456
457 bool
getPointToDrag(LayerGeometryProvider * v,int x,int y,Event & point) const458 NoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
459 {
460 auto model = ModelById::getAs<NoteModel>(m_model);
461 if (!model) return false;
462
463 sv_frame_t frame = v->getFrameForX(x);
464
465 EventVector onPoints = model->getEventsCovering(frame);
466 if (onPoints.empty()) return false;
467
468 int nearestDistance = -1;
469 for (const auto &p: onPoints) {
470 int distance = getYForValue(v, valueOf(p)) - y;
471 if (distance < 0) distance = -distance;
472 if (nearestDistance == -1 || distance < nearestDistance) {
473 nearestDistance = distance;
474 point = p;
475 }
476 }
477
478 return true;
479 }
480
481 QString
getFeatureDescription(LayerGeometryProvider * v,QPoint & pos) const482 NoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
483 {
484 int x = pos.x();
485
486 auto model = ModelById::getAs<NoteModel>(m_model);
487 if (!model || !model->getSampleRate()) return "";
488
489 EventVector points = getLocalPoints(v, x);
490
491 if (points.empty()) {
492 if (!model->isReady()) {
493 return tr("In progress");
494 } else {
495 return tr("No local points");
496 }
497 }
498
499 Event note;
500 EventVector::iterator i;
501
502 for (i = points.begin(); i != points.end(); ++i) {
503
504 int y = getYForValue(v, valueOf(*i));
505 int h = 3;
506
507 if (model->getValueQuantization() != 0.0) {
508 h = y - getYForValue
509 (v, convertValueFromEventValue(i->getValue() +
510 model->getValueQuantization()));
511 if (h < 3) h = 3;
512 }
513
514 if (pos.y() >= y - h && pos.y() <= y) {
515 note = *i;
516 break;
517 }
518 }
519
520 if (i == points.end()) return tr("No local points");
521
522 RealTime rt = RealTime::frame2RealTime(note.getFrame(),
523 model->getSampleRate());
524 RealTime rd = RealTime::frame2RealTime(note.getDuration(),
525 model->getSampleRate());
526
527 QString pitchText;
528
529 if (m_modelUsesHz) {
530
531 float value = note.getValue();
532
533 pitchText = tr("%1 Hz (%2, %3)")
534 .arg(value)
535 .arg(Pitch::getPitchLabelForFrequency(value))
536 .arg(Pitch::getPitchForFrequency(value));
537
538 } else {
539
540 float eventValue = note.getValue();
541 double value = convertValueFromEventValue(eventValue);
542
543 int mnote = int(lrint(eventValue));
544 int cents = int(lrint((eventValue - float(mnote)) * 100));
545
546 pitchText = tr("%1 (%2, %3 Hz)")
547 .arg(Pitch::getPitchLabel(mnote, cents))
548 .arg(eventValue)
549 .arg(value);
550 }
551
552 QString text;
553
554 if (note.getLabel() == "") {
555 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
556 .arg(rt.toText(true).c_str())
557 .arg(pitchText)
558 .arg(rd.toText(true).c_str());
559 } else {
560 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
561 .arg(rt.toText(true).c_str())
562 .arg(pitchText)
563 .arg(rd.toText(true).c_str())
564 .arg(note.getLabel());
565 }
566
567 pos = QPoint(v->getXForFrame(note.getFrame()),
568 getYForValue(v, valueOf(note)));
569 return text;
570 }
571
572 bool
snapToFeatureFrame(LayerGeometryProvider * v,sv_frame_t & frame,int & resolution,SnapType snap,int ycoord) const573 NoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
574 int &resolution,
575 SnapType snap, int ycoord) const
576 {
577 auto model = ModelById::getAs<NoteModel>(m_model);
578 if (!model) {
579 return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
580 }
581
582 // SnapLeft / SnapRight: return frame of nearest feature in that
583 // direction no matter how far away
584 //
585 // SnapNeighbouring: return frame of feature that would be used in
586 // an editing operation, i.e. closest feature in either direction
587 // but only if it is "close enough"
588
589 resolution = model->getResolution();
590
591 if (snap == SnapNeighbouring) {
592 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
593 if (points.empty()) return false;
594 frame = points.begin()->getFrame();
595 return true;
596 }
597
598 Event e;
599 if (model->getNearestEventMatching
600 (frame,
601 [](Event) { return true; },
602 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
603 e)) {
604 frame = e.getFrame();
605 return true;
606 }
607
608 return false;
609 }
610
611 void
getScaleExtents(LayerGeometryProvider * v,double & min,double & max,bool & log) const612 NoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
613 {
614 min = 0.0;
615 max = 0.0;
616 log = false;
617
618 auto model = ModelById::getAs<NoteModel>(m_model);
619 if (!model) return;
620
621 if (shouldAutoAlign()) {
622
623 if (!v->getVisibleExtentsForUnit("Hz", min, max, log)) {
624
625 QString unit;
626 getValueExtents(min, max, log, unit);
627
628 #ifdef DEBUG_NOTE_LAYER
629 SVCERR << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
630 #endif
631
632 } else if (log) {
633
634 LogRange::mapRange(min, max);
635
636 #ifdef DEBUG_NOTE_LAYER
637 SVCERR << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
638 #endif
639 }
640
641 } else {
642
643 getDisplayExtents(min, max);
644
645 if (m_verticalScale != LinearScale) {
646 LogRange::mapRange(min, max);
647 log = true;
648 }
649 }
650
651 if (max == min) max = min + 1.0;
652 }
653
654 int
getYForValue(LayerGeometryProvider * v,double val) const655 NoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
656 {
657 double min = 0.0, max = 0.0;
658 bool logarithmic = false;
659 int h = v->getPaintHeight();
660
661 getScaleExtents(v, min, max, logarithmic);
662
663 #ifdef DEBUG_NOTE_LAYER
664 SVCERR << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
665 #endif
666
667 if (logarithmic) {
668 val = LogRange::map(val);
669 #ifdef DEBUG_NOTE_LAYER
670 SVCERR << "logarithmic true, val now = " << val << endl;
671 #endif
672 }
673
674 int y = int(h - ((val - min) * h) / (max - min)) - 1;
675 #ifdef DEBUG_NOTE_LAYER
676 SVCERR << "y = " << y << endl;
677 #endif
678 return y;
679 }
680
681 double
getValueForY(LayerGeometryProvider * v,int y) const682 NoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
683 {
684 double min = 0.0, max = 0.0;
685 bool logarithmic = false;
686 int h = v->getPaintHeight();
687
688 getScaleExtents(v, min, max, logarithmic);
689
690 double val = min + (double(h - y) * double(max - min)) / h;
691
692 if (logarithmic) {
693 val = pow(10.0, val);
694 }
695
696 return val;
697 }
698
699 bool
shouldAutoAlign() const700 NoteLayer::shouldAutoAlign() const
701 {
702 if (m_model.isNone()) return false;
703 return (m_verticalScale == AutoAlignScale);
704 }
705
706 void
paint(LayerGeometryProvider * v,QPainter & paint,QRect rect) const707 NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
708 {
709 auto model = ModelById::getAs<NoteModel>(m_model);
710 if (!model || !model->isOK()) return;
711
712 sv_samplerate_t sampleRate = model->getSampleRate();
713 if (!sampleRate) return;
714
715 // Profiler profiler("NoteLayer::paint", true);
716
717 int x0 = rect.left();
718 int x1 = x0 + rect.width();
719
720 sv_frame_t frame0 = v->getFrameForX(x0);
721 sv_frame_t frame1 = v->getFrameForX(x1);
722
723 EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
724 if (points.empty()) return;
725
726 paint.setPen(getBaseQColor());
727
728 QColor brushColour(getBaseQColor());
729 brushColour.setAlpha(80);
730
731 // SVDEBUG << "NoteLayer::paint: resolution is "
732 // << model->getResolution() << " frames" << endl;
733
734 double min = convertValueFromEventValue(model->getValueMinimum());
735 double max = convertValueFromEventValue(model->getValueMaximum());
736 if (max == min) max = min + 1.0;
737
738 QPoint localPos;
739 Event illuminatePoint;
740 bool shouldIlluminate = false;
741
742 if (m_editing || m_editIsOpen) {
743 shouldIlluminate = true;
744 illuminatePoint = m_editingPoint;
745 } else if (v->shouldIlluminateLocalFeatures(this, localPos)) {
746 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
747 illuminatePoint);
748 }
749
750 paint.save();
751 paint.setRenderHint(QPainter::Antialiasing, false);
752
753 for (EventVector::const_iterator i = points.begin();
754 i != points.end(); ++i) {
755
756 const Event &p(*i);
757
758 int x = v->getXForFrame(p.getFrame());
759 int y = getYForValue(v, valueOf(p));
760 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
761 int h = 3;
762
763 if (model->getValueQuantization() != 0.0) {
764 h = y - getYForValue
765 (v, convertValueFromEventValue
766 (p.getValue() + model->getValueQuantization()));
767 if (h < 3) h = 3;
768 }
769
770 if (w < 1) w = 1;
771 paint.setPen(getBaseQColor());
772 paint.setBrush(brushColour);
773
774 if (shouldIlluminate && illuminatePoint == p) {
775
776 paint.setPen(v->getForeground());
777 paint.setBrush(v->getForeground());
778
779 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
780 // replacement (horizontalAdvance) was only added in Qt 5.11
781 // which is too new for us
782 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
783
784 QString vlabel;
785 if (m_modelUsesHz) {
786 vlabel = QString("%1%2")
787 .arg(p.getValue())
788 .arg(model->getScaleUnits());
789 } else {
790 vlabel = QString("%1 %2")
791 .arg(p.getValue())
792 .arg(model->getScaleUnits());
793 }
794
795 PaintAssistant::drawVisibleText(v, paint,
796 x - paint.fontMetrics().width(vlabel) - 2,
797 y + paint.fontMetrics().height()/2
798 - paint.fontMetrics().descent(),
799 vlabel, PaintAssistant::OutlinedText);
800
801 QString hlabel = RealTime::frame2RealTime
802 (p.getFrame(), model->getSampleRate()).toText(true).c_str();
803 PaintAssistant::drawVisibleText(v, paint,
804 x,
805 y - h/2 - paint.fontMetrics().descent() - 2,
806 hlabel, PaintAssistant::OutlinedText);
807 }
808
809 paint.drawRect(x, y - h/2, w, h);
810 }
811
812 paint.restore();
813 }
814
815 int
getVerticalScaleWidth(LayerGeometryProvider * v,bool,QPainter & paint) const816 NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
817 {
818 if (m_model.isNone()) {
819 return 0;
820 }
821
822 if (shouldAutoAlign() && !valueExtentsMatchMine(v)) {
823 return 0;
824 }
825
826 if (m_verticalScale != LinearScale) {
827 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
828 } else {
829 return LinearNumericalScale().getWidth(v, paint);
830 }
831 }
832
833 void
paintVerticalScale(LayerGeometryProvider * v,bool,QPainter & paint,QRect) const834 NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
835 {
836 auto model = ModelById::getAs<NoteModel>(m_model);
837 if (!model || model->isEmpty()) return;
838
839 QString unit;
840 double min, max;
841 bool logarithmic;
842
843 int w = getVerticalScaleWidth(v, false, paint);
844 int h = v->getPaintHeight();
845
846 getScaleExtents(v, min, max, logarithmic);
847
848 if (logarithmic) {
849 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
850 } else {
851 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
852 }
853
854 if (logarithmic) {
855 PianoScale().paintPianoVertical
856 (v, paint, QRect(w - 10, 0, 10, h),
857 LogRange::unmap(min),
858 LogRange::unmap(max));
859 paint.drawLine(w, 0, w, h);
860 }
861
862 if (getScaleUnits() != "") {
863 int mw = w - 5;
864 paint.drawText(5,
865 5 + paint.fontMetrics().ascent(),
866 TextAbbrev::abbreviate(getScaleUnits(),
867 paint.fontMetrics(),
868 mw));
869 }
870 }
871
872 void
drawStart(LayerGeometryProvider * v,QMouseEvent * e)873 NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
874 {
875 // SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
876
877 auto model = ModelById::getAs<NoteModel>(m_model);
878 if (!model) return;
879
880 sv_frame_t frame = v->getFrameForX(e->x());
881 if (frame < 0) frame = 0;
882 frame = frame / model->getResolution() * model->getResolution();
883
884 double value = getValueForY(v, e->y());
885 float eventValue = convertValueToEventValue(value);
886 eventValue = roundf(eventValue);
887
888 m_editingPoint = Event(frame, eventValue, 0, 0.8f, tr("New Point"));
889 m_originalPoint = m_editingPoint;
890
891 if (m_editingCommand) finish(m_editingCommand);
892 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
893 m_editingCommand->add(m_editingPoint);
894
895 m_editing = true;
896 }
897
898 void
drawDrag(LayerGeometryProvider * v,QMouseEvent * e)899 NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
900 {
901 // SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
902
903 auto model = ModelById::getAs<NoteModel>(m_model);
904 if (!model || !m_editing) return;
905
906 sv_frame_t frame = v->getFrameForX(e->x());
907 if (frame < 0) frame = 0;
908 frame = frame / model->getResolution() * model->getResolution();
909
910 double newValue = getValueForY(v, e->y());
911 float newEventValue = convertValueToEventValue(newValue);
912 newEventValue = roundf(newEventValue);
913
914 sv_frame_t newFrame = m_editingPoint.getFrame();
915 sv_frame_t newDuration = frame - newFrame;
916 if (newDuration < 0) {
917 newFrame = frame;
918 newDuration = -newDuration;
919 } else if (newDuration == 0) {
920 newDuration = 1;
921 }
922
923 m_editingCommand->remove(m_editingPoint);
924 m_editingPoint = m_editingPoint
925 .withFrame(newFrame)
926 .withDuration(newDuration)
927 .withValue(newEventValue);
928 m_editingCommand->add(m_editingPoint);
929 }
930
931 void
drawEnd(LayerGeometryProvider *,QMouseEvent *)932 NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
933 {
934 // SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
935 auto model = ModelById::getAs<NoteModel>(m_model);
936 if (!model || !m_editing) return;
937 finish(m_editingCommand);
938 m_editingCommand = nullptr;
939 m_editing = false;
940 }
941
942 void
eraseStart(LayerGeometryProvider * v,QMouseEvent * e)943 NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
944 {
945 auto model = ModelById::getAs<NoteModel>(m_model);
946 if (!model) return;
947
948 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
949
950 if (m_editingCommand) {
951 finish(m_editingCommand);
952 m_editingCommand = nullptr;
953 }
954
955 m_editing = true;
956 }
957
958 void
eraseDrag(LayerGeometryProvider *,QMouseEvent *)959 NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
960 {
961 }
962
963 void
eraseEnd(LayerGeometryProvider * v,QMouseEvent * e)964 NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
965 {
966 auto model = ModelById::getAs<NoteModel>(m_model);
967 if (!model || !m_editing) return;
968
969 m_editing = false;
970
971 Event p(0);
972 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
973 if (p.getFrame() != m_editingPoint.getFrame() ||
974 p.getValue() != m_editingPoint.getValue()) return;
975
976 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
977
978 m_editingCommand->remove(m_editingPoint);
979
980 finish(m_editingCommand);
981 m_editingCommand = nullptr;
982 m_editing = false;
983 }
984
985 void
editStart(LayerGeometryProvider * v,QMouseEvent * e)986 NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
987 {
988 // SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
989
990 auto model = ModelById::getAs<NoteModel>(m_model);
991 if (!model) return;
992
993 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
994 m_originalPoint = m_editingPoint;
995
996 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
997 m_dragPointY = getYForValue(v, valueOf(m_editingPoint));
998
999 if (m_editingCommand) {
1000 finish(m_editingCommand);
1001 m_editingCommand = nullptr;
1002 }
1003
1004 m_editing = true;
1005 m_dragStartX = e->x();
1006 m_dragStartY = e->y();
1007 }
1008
1009 void
editDrag(LayerGeometryProvider * v,QMouseEvent * e)1010 NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
1011 {
1012 // SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
1013
1014 auto model = ModelById::getAs<NoteModel>(m_model);
1015 if (!model || !m_editing) return;
1016
1017 int xdist = e->x() - m_dragStartX;
1018 int ydist = e->y() - m_dragStartY;
1019 int newx = m_dragPointX + xdist;
1020 int newy = m_dragPointY + ydist;
1021
1022 sv_frame_t frame = v->getFrameForX(newx);
1023 if (frame < 0) frame = 0;
1024 frame = frame / model->getResolution() * model->getResolution();
1025
1026 double newValue = getValueForY(v, newy);
1027 float newEventValue = convertValueToEventValue(newValue);
1028 newEventValue = roundf(newEventValue);
1029
1030 if (!m_editingCommand) {
1031 m_editingCommand = new ChangeEventsCommand
1032 (m_model.untyped, tr("Drag Point"));
1033 }
1034
1035 m_editingCommand->remove(m_editingPoint);
1036 m_editingPoint = m_editingPoint
1037 .withFrame(frame)
1038 .withValue(newEventValue);
1039 m_editingCommand->add(m_editingPoint);
1040 }
1041
1042 void
editEnd(LayerGeometryProvider *,QMouseEvent *)1043 NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
1044 {
1045 // SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
1046 auto model = ModelById::getAs<NoteModel>(m_model);
1047 if (!model || !m_editing) return;
1048
1049 if (m_editingCommand) {
1050
1051 QString newName = m_editingCommand->getName();
1052
1053 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
1054 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
1055 newName = tr("Edit Point");
1056 } else {
1057 newName = tr("Relocate Point");
1058 }
1059 } else {
1060 newName = tr("Change Point Value");
1061 }
1062
1063 m_editingCommand->setName(newName);
1064 finish(m_editingCommand);
1065 }
1066
1067 m_editingCommand = nullptr;
1068 m_editing = false;
1069 }
1070
1071 bool
editOpen(LayerGeometryProvider * v,QMouseEvent * e)1072 NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
1073 {
1074 auto model = ModelById::getAs<NoteModel>(m_model);
1075 if (!model) return false;
1076
1077 Event note(0);
1078 if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
1079
1080 ItemEditDialog *dialog = new ItemEditDialog
1081 (model->getSampleRate(),
1082 ItemEditDialog::ShowTime |
1083 ItemEditDialog::ShowDuration |
1084 ItemEditDialog::ShowValue |
1085 ItemEditDialog::ShowLevel |
1086 ItemEditDialog::ShowText,
1087 getScaleUnits());
1088
1089 dialog->setFrameTime(note.getFrame());
1090 dialog->setValue(note.getValue());
1091 dialog->setFrameDuration(note.getDuration());
1092 dialog->setText(note.getLabel());
1093
1094 m_editingPoint = note;
1095 m_editIsOpen = true;
1096
1097 if (dialog->exec() == QDialog::Accepted) {
1098
1099 Event newNote = note
1100 .withFrame(dialog->getFrameTime())
1101 .withValue(dialog->getValue())
1102 .withDuration(dialog->getFrameDuration())
1103 .withLabel(dialog->getText());
1104
1105 ChangeEventsCommand *command = new ChangeEventsCommand
1106 (m_model.untyped, tr("Edit Point"));
1107 command->remove(note);
1108 command->add(newNote);
1109 finish(command);
1110 }
1111
1112 m_editingPoint = 0;
1113 m_editIsOpen = false;
1114
1115 delete dialog;
1116 return true;
1117 }
1118
1119 void
moveSelection(Selection s,sv_frame_t newStartFrame)1120 NoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
1121 {
1122 auto model = ModelById::getAs<NoteModel>(m_model);
1123 if (!model) return;
1124
1125 ChangeEventsCommand *command =
1126 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
1127
1128 EventVector points =
1129 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1130
1131 for (Event p: points) {
1132 command->remove(p);
1133 Event moved = p.withFrame(p.getFrame() +
1134 newStartFrame - s.getStartFrame());
1135 command->add(moved);
1136 }
1137
1138 finish(command);
1139 }
1140
1141 void
resizeSelection(Selection s,Selection newSize)1142 NoteLayer::resizeSelection(Selection s, Selection newSize)
1143 {
1144 auto model = ModelById::getAs<NoteModel>(m_model);
1145 if (!model || !s.getDuration()) return;
1146
1147 ChangeEventsCommand *command =
1148 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
1149
1150 EventVector points =
1151 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1152
1153 double ratio = double(newSize.getDuration()) / double(s.getDuration());
1154 double oldStart = double(s.getStartFrame());
1155 double newStart = double(newSize.getStartFrame());
1156
1157 for (Event p: points) {
1158
1159 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
1160 double newDuration = double(p.getDuration()) * ratio;
1161
1162 Event newPoint = p
1163 .withFrame(lrint(newFrame))
1164 .withDuration(lrint(newDuration));
1165 command->remove(p);
1166 command->add(newPoint);
1167 }
1168
1169 finish(command);
1170 }
1171
1172 void
deleteSelection(Selection s)1173 NoteLayer::deleteSelection(Selection s)
1174 {
1175 auto model = ModelById::getAs<NoteModel>(m_model);
1176 if (!model) return;
1177
1178 ChangeEventsCommand *command =
1179 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
1180
1181 EventVector points =
1182 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1183
1184 for (Event p: points) {
1185 command->remove(p);
1186 }
1187
1188 finish(command);
1189 }
1190
1191 void
copy(LayerGeometryProvider * v,Selection s,Clipboard & to)1192 NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
1193 {
1194 auto model = ModelById::getAs<NoteModel>(m_model);
1195 if (!model) return;
1196
1197 EventVector points =
1198 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1199
1200 for (Event p: points) {
1201 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
1202 }
1203 }
1204
1205 bool
paste(LayerGeometryProvider * v,const Clipboard & from,sv_frame_t,bool)1206 NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
1207 sv_frame_t /* frameOffset */, bool /* interactive */)
1208 {
1209 auto model = ModelById::getAs<NoteModel>(m_model);
1210 if (!model) return false;
1211
1212 const EventVector &points = from.getPoints();
1213
1214 bool realign = false;
1215
1216 if (clipboardHasDifferentAlignment(v, from)) {
1217
1218 QMessageBox::StandardButton button =
1219 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
1220 tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"),
1221 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1222 QMessageBox::Yes);
1223
1224 if (button == QMessageBox::Cancel) {
1225 return false;
1226 }
1227
1228 if (button == QMessageBox::Yes) {
1229 realign = true;
1230 }
1231 }
1232
1233 ChangeEventsCommand *command =
1234 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
1235
1236 for (EventVector::const_iterator i = points.begin();
1237 i != points.end(); ++i) {
1238
1239 sv_frame_t frame = 0;
1240
1241 if (!realign) {
1242
1243 frame = i->getFrame();
1244
1245 } else {
1246
1247 if (i->hasReferenceFrame()) {
1248 frame = i->getReferenceFrame();
1249 frame = alignFromReference(v, frame);
1250 } else {
1251 frame = i->getFrame();
1252 }
1253 }
1254
1255 Event p = i->withFrame(frame);
1256
1257 Event newPoint = p;
1258 if (!p.hasValue()) {
1259 newPoint = newPoint.withValue((model->getValueMinimum() +
1260 model->getValueMaximum()) / 2);
1261 }
1262 if (!p.hasDuration()) {
1263 sv_frame_t nextFrame = frame;
1264 EventVector::const_iterator j = i;
1265 for (; j != points.end(); ++j) {
1266 if (j != i) break;
1267 }
1268 if (j != points.end()) {
1269 nextFrame = j->getFrame();
1270 }
1271 if (nextFrame == frame) {
1272 newPoint = newPoint.withDuration(model->getResolution());
1273 } else {
1274 newPoint = newPoint.withDuration(nextFrame - frame);
1275 }
1276 }
1277
1278 command->add(newPoint);
1279 }
1280
1281 finish(command);
1282 return true;
1283 }
1284
1285 void
addNoteOn(sv_frame_t frame,int pitch,int velocity)1286 NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
1287 {
1288 double value = Pitch::getFrequencyForPitch(pitch);
1289 float eventValue = convertValueToEventValue(value);
1290 m_pendingNoteOns.insert(Event(frame, eventValue, 0,
1291 float(velocity) / 127.f, QString()));
1292 }
1293
1294 void
addNoteOff(sv_frame_t frame,int pitch)1295 NoteLayer::addNoteOff(sv_frame_t frame, int pitch)
1296 {
1297 auto model = ModelById::getAs<NoteModel>(m_model);
1298
1299 for (NoteSet::iterator i = m_pendingNoteOns.begin();
1300 i != m_pendingNoteOns.end(); ++i) {
1301
1302 Event p = *i;
1303 double value = valueOf(p);
1304 int eventPitch = Pitch::getPitchForFrequency(value);
1305
1306 if (eventPitch == pitch) {
1307 m_pendingNoteOns.erase(i);
1308 Event note = p.withDuration(frame - p.getFrame());
1309 if (model) {
1310 ChangeEventsCommand *c = new ChangeEventsCommand
1311 (m_model.untyped, tr("Record Note"));
1312 c->add(note);
1313 // execute and bundle:
1314 CommandHistory::getInstance()->addCommand(c, true, true);
1315 }
1316 break;
1317 }
1318 }
1319 }
1320
1321 void
abandonNoteOns()1322 NoteLayer::abandonNoteOns()
1323 {
1324 m_pendingNoteOns.clear();
1325 }
1326
1327 int
getDefaultColourHint(bool darkbg,bool & impose)1328 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
1329 {
1330 impose = false;
1331 return ColourDatabase::getInstance()->getColourIndex
1332 (QString(darkbg ? "White" : "Black"));
1333 }
1334
1335 void
toXml(QTextStream & stream,QString indent,QString extraAttributes) const1336 NoteLayer::toXml(QTextStream &stream,
1337 QString indent, QString extraAttributes) const
1338 {
1339 SingleColourLayer::toXml(stream, indent, extraAttributes +
1340 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
1341 .arg(m_verticalScale)
1342 .arg(m_scaleMinimum)
1343 .arg(m_scaleMaximum));
1344 }
1345
1346 void
setProperties(const QXmlAttributes & attributes)1347 NoteLayer::setProperties(const QXmlAttributes &attributes)
1348 {
1349 SingleColourLayer::setProperties(attributes);
1350
1351 bool ok, alsoOk;
1352 VerticalScale scale = (VerticalScale)
1353 attributes.value("verticalScale").toInt(&ok);
1354 if (ok) setVerticalScale(scale);
1355
1356 float min = attributes.value("scaleMinimum").toFloat(&ok);
1357 float max = attributes.value("scaleMaximum").toFloat(&alsoOk);
1358 if (ok && alsoOk && min != max) setDisplayExtents(min, max);
1359 }
1360
1361
1362