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