FlexiNoteLayer.cpp
Go to the documentation of this file.
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 "FlexiNoteLayer.h"
17 
18 #include "data/model/Model.h"
19 #include "data/model/SparseTimeValueModel.h"
20 #include "base/RealTime.h"
21 #include "base/Profiler.h"
22 #include "base/Pitch.h"
23 #include "base/LogRange.h"
24 #include "base/RangeMapper.h"
25 
26 #include "ColourDatabase.h"
27 #include "LayerGeometryProvider.h"
28 #include "PianoScale.h"
29 #include "LinearNumericalScale.h"
30 #include "LogNumericalScale.h"
31 #include "PaintAssistant.h"
32 
33 #include "data/model/NoteModel.h"
34 
35 #include "view/View.h"
36 
37 #include "widgets/ItemEditDialog.h"
38 #include "widgets/TextAbbrev.h"
39 
40 #include <QPainter>
41 #include <QPainterPath>
42 #include <QMouseEvent>
43 #include <QTextStream>
44 #include <QMessageBox>
45 
46 #include <iostream>
47 #include <cmath>
48 #include <utility>
49 #include <limits> // GF: included to compile std::numerical_limits on linux
50 #include <vector>
51 
52 #define NOTE_HEIGHT 16
53 
56  m_editing(false),
57  m_intelligentActions(true),
58  m_dragPointX(0),
59  m_dragPointY(0),
60  m_dragStartX(0),
61  m_dragStartY(0),
62  m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
63  m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
64  m_greatestLeftNeighbourFrame(0),
65  m_smallestRightNeighbourFrame(0),
66  m_editingCommand(nullptr),
67  m_verticalScale(AutoAlignScale),
68  m_editMode(DragNote),
69  m_scaleMinimum(34),
70  m_scaleMaximum(77)
71 {
72 }
73 
74 void
75 FlexiNoteLayer::setModel(ModelId modelId)
76 {
77  auto newModel = ModelById::getAs<NoteModel>(modelId);
78 
79  if (!modelId.isNone() && !newModel) {
80  throw std::logic_error("Not a NoteModel");
81  }
82 
83  if (m_model == modelId) return;
84  m_model = modelId;
85 
86  if (newModel) {
88  }
89 
90  emit modelReplaced();
91 }
92 
93 Layer::PropertyList
95 {
96  PropertyList list = SingleColourLayer::getProperties();
97  list.push_back("Vertical Scale");
98  list.push_back("Scale Units");
99  return list;
100 }
101 
102 QString
103 FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const
104 {
105  if (name == "Vertical Scale") return tr("Vertical Scale");
106  if (name == "Scale Units") return tr("Scale Units");
108 }
109 
110 Layer::PropertyType
111 FlexiNoteLayer::getPropertyType(const PropertyName &name) const
112 {
113  if (name == "Scale Units") return UnitsProperty;
114  if (name == "Vertical Scale") return ValueProperty;
116 }
117 
118 QString
119 FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const
120 {
121  if (name == "Vertical Scale" || name == "Scale Units") {
122  return tr("Scale");
123  }
125 }
126 
127 QString
129 {
130  auto model = ModelById::getAs<NoteModel>(m_model);
131  if (model) return model->getScaleUnits();
132  else return "";
133 }
134 
135 int
137  int *min, int *max, int *deflt) const
138 {
139  int val = 0;
140 
141  if (name == "Vertical Scale") {
142 
143  if (min) *min = 0;
144  if (max) *max = 3;
145  if (deflt) *deflt = int(AutoAlignScale);
146 
147  val = int(m_verticalScale);
148 
149  } else if (name == "Scale Units") {
150 
151  if (deflt) *deflt = 0;
152  auto model = ModelById::getAs<NoteModel>(m_model);
153  if (model) {
154  val = UnitDatabase::getInstance()->getUnitId
155  (getScaleUnits());
156  }
157 
158  } else {
159 
160  val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
161  }
162 
163  return val;
164 }
165 
166 QString
167 FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name,
168  int value) const
169 {
170  if (name == "Vertical Scale") {
171  switch (value) {
172  default:
173  case 0: return tr("Auto-Align");
174  case 1: return tr("Linear");
175  case 2: return tr("Log");
176  case 3: return tr("MIDI Notes");
177  }
178  }
179  return SingleColourLayer::getPropertyValueLabel(name, value);
180 }
181 
182 void
183 FlexiNoteLayer::setProperty(const PropertyName &name, int value)
184 {
185  if (name == "Vertical Scale") {
187  } else if (name == "Scale Units") {
188  auto model = ModelById::getAs<NoteModel>(m_model);
189  if (model) {
190  model->setScaleUnits
191  (UnitDatabase::getInstance()->getUnitById(value));
192  emit modelChanged(m_model);
193  }
194  } else {
195  return SingleColourLayer::setProperty(name, value);
196  }
197 }
198 
199 void
201 {
202  if (m_verticalScale == scale) return;
203  m_verticalScale = scale;
204  emit layerParametersChanged();
205 }
206 
207 bool
209 {
210  QPoint discard;
211  return !v->shouldIlluminateLocalFeatures(this, discard);
212 }
213 
214 bool
216 {
217  QString unit = getScaleUnits();
218  return (unit != "Hz");
219 // if (unit == "" ||
220 // unit.startsWith("MIDI") ||
221 // unit.startsWith("midi")) return true;
222 // return false;
223 }
224 
225 int
227 {
228  auto model = ModelById::get(m_model);
229  if (model) return model->getCompletion();
230  else return 0;
231 }
232 
233 bool
234 FlexiNoteLayer::getValueExtents(double &min, double &max,
235  bool &logarithmic, QString &unit) const
236 {
237  auto model = ModelById::getAs<NoteModel>(m_model);
238  if (!model) return false;
239  min = model->getValueMinimum();
240  max = model->getValueMaximum();
241 
242  if (shouldConvertMIDIToHz()) {
243  unit = "Hz";
244  min = Pitch::getFrequencyForPitch(int(lrint(min)));
245  max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
246  } else unit = getScaleUnits();
247 
249  m_verticalScale == LogScale) logarithmic = true;
250 
251  return true;
252 }
253 
254 bool
255 FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
256 {
257  auto model = ModelById::getAs<NoteModel>(m_model);
258  if (!model || shouldAutoAlign()) {
259 // std::cerr << "No model or shouldAutoAlign()" << std::endl;
260  return false;
261  }
262 
264  min = Pitch::getFrequencyForPitch(0);
265  max = Pitch::getFrequencyForPitch(127);
266  return true;
267  }
268 
270  min = model->getValueMinimum();
271  max = model->getValueMaximum();
272  } else {
273  min = m_scaleMinimum;
274  max = m_scaleMaximum;
275  }
276 
277  if (shouldConvertMIDIToHz()) {
278  min = Pitch::getFrequencyForPitch(int(lrint(min)));
279  max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
280  }
281 
282 #ifdef DEBUG_NOTE_LAYER
283  cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
284 #endif
285 
286  return true;
287 }
288 
289 bool
290 FlexiNoteLayer::setDisplayExtents(double min, double max)
291 {
292  auto model = ModelById::getAs<NoteModel>(m_model);
293  if (!model) return false;
294 
295  if (min == max) {
296  if (min == 0.f) {
297  max = 1.f;
298  } else {
299  max = min * 1.0001f;
300  }
301  }
302 
303  m_scaleMinimum = min;
304  m_scaleMaximum = max;
305 
306 #ifdef DEBUG_NOTE_LAYER
307  cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
308 #endif
309 
310  emit layerParametersChanged();
311  return true;
312 }
313 
314 int
316 {
317  if (shouldAutoAlign()) return 0;
318  auto model = ModelById::getAs<NoteModel>(m_model);
319  if (!model) return 0;
320 
321  defaultStep = 0;
322  return 100;
323 }
324 
325 int
327 {
328  if (shouldAutoAlign()) return 0;
329  auto model = ModelById::getAs<NoteModel>(m_model);
330  if (!model) return 0;
331 
332  RangeMapper *mapper = getNewVerticalZoomRangeMapper();
333  if (!mapper) return 0;
334 
335  double dmin, dmax;
336  getDisplayExtents(dmin, dmax);
337 
338  int nr = mapper->getPositionForValue(dmax - dmin);
339 
340  delete mapper;
341 
342  return 100 - nr;
343 }
344 
346 
347 void
349 {
350  if (shouldAutoAlign()) return;
351  auto model = ModelById::getAs<NoteModel>(m_model);
352  if (!model) return;
353 
354  RangeMapper *mapper = getNewVerticalZoomRangeMapper();
355  if (!mapper) return;
356 
357  double min, max;
358  bool logarithmic;
359  QString unit;
360  getValueExtents(min, max, logarithmic, unit);
361 
362  double dmin, dmax;
363  getDisplayExtents(dmin, dmax);
364 
365  double newdist = mapper->getValueForPosition(100 - step);
366 
367  double newmin, newmax;
368 
369  if (logarithmic) {
370 
371  // see SpectrogramLayer::setVerticalZoomStep
372 
373  newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
374  newmin = newmax - newdist;
375 
376 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
377 
378  } else {
379  double dmid = (dmax + dmin) / 2;
380  newmin = dmid - newdist / 2;
381  newmax = dmid + newdist / 2;
382  }
383 
384  if (newmin < min) {
385  newmax += (min - newmin);
386  newmin = min;
387  }
388  if (newmax > max) {
389  newmax = max;
390  }
391 
392 #ifdef DEBUG_NOTE_LAYER
393  cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
394 #endif
395 
396  setDisplayExtents(newmin, newmax);
397 }
398 
399 RangeMapper *
401 {
402  auto model = ModelById::getAs<NoteModel>(m_model);
403  if (!model) return nullptr;
404 
405  RangeMapper *mapper;
406 
407  double min, max;
408  bool logarithmic;
409  QString unit;
410  getValueExtents(min, max, logarithmic, unit);
411 
412  if (min == max) return nullptr;
413 
414  if (logarithmic) {
415  mapper = new LogRangeMapper(0, 100, min, max, unit);
416  } else {
417  mapper = new LinearRangeMapper(0, 100, min, max, unit);
418  }
419 
420  return mapper;
421 }
422 
423 EventVector
425 {
426  auto model = ModelById::getAs<NoteModel>(m_model);
427  if (!model) return {};
428 
429  sv_frame_t frame = v->getFrameForX(x);
430 
431  EventVector local = model->getEventsCovering(frame);
432  if (!local.empty()) return local;
433 
434  int fuzz = ViewManager::scalePixelSize(2);
435  sv_frame_t start = v->getFrameForX(x - fuzz);
436  sv_frame_t end = v->getFrameForX(x + fuzz);
437 
438  local = model->getEventsStartingWithin(frame, end - frame);
439  if (!local.empty()) return local;
440 
441  local = model->getEventsSpanning(start, frame - start);
442  if (!local.empty()) return local;
443 
444  return {};
445 }
446 
447 bool
448 FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
449 {
450  auto model = ModelById::getAs<NoteModel>(m_model);
451  if (!model) return false;
452 
453  sv_frame_t frame = v->getFrameForX(x);
454 
455  EventVector onPoints = model->getEventsCovering(frame);
456  if (onPoints.empty()) return false;
457 
458  int nearestDistance = -1;
459  for (const auto &p: onPoints) {
460  int distance = getYForValue(v, p.getValue()) - y;
461  if (distance < 0) distance = -distance;
462  if (nearestDistance == -1 || distance < nearestDistance) {
463  nearestDistance = distance;
464  point = p;
465  }
466  }
467 
468  return true;
469 }
470 
471 bool
472 FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &point) const
473 {
474  // GF: find the note that is closest to the cursor
475  auto model = ModelById::getAs<NoteModel>(m_model);
476  if (!model) return false;
477 
478  sv_frame_t frame = v->getFrameForX(x);
479 
480  EventVector onPoints = model->getEventsCovering(frame);
481  if (onPoints.empty()) return false;
482 
483  int nearestDistance = -1;
484  for (const auto &p: onPoints) {
485  int distance = getYForValue(v, p.getValue()) - y;
486  if (distance < 0) distance = -distance;
487  if (nearestDistance == -1 || distance < nearestDistance) {
488  nearestDistance = distance;
489  point = p;
490  }
491  }
492 
493  return true;
494 }
495 
496 QString
498 {
499  int x = pos.x();
500 
501  auto model = ModelById::getAs<NoteModel>(m_model);
502  if (!model || !model->getSampleRate()) return "";
503 
504  EventVector points = getLocalPoints(v, x);
505 
506  if (points.empty()) {
507  if (!model->isReady()) {
508  return tr("In progress");
509  } else {
510  return tr("No local points");
511  }
512  }
513 
514  Event note(0);
515  EventVector::iterator i;
516 
517  for (i = points.begin(); i != points.end(); ++i) {
518 
519  int y = getYForValue(v, i->getValue());
520  int h = NOTE_HEIGHT; // GF: larger notes
521 
522  if (model->getValueQuantization() != 0.0) {
523  h = y - getYForValue
524  (v, i->getValue() + model->getValueQuantization());
525  if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
526  }
527 
528  // GF: this is not quite correct
529  if (pos.y() >= y - 4 && pos.y() <= y + h) {
530  note = *i;
531  break;
532  }
533  }
534 
535  if (i == points.end()) return tr("No local points");
536 
537  RealTime rt = RealTime::frame2RealTime(note.getFrame(),
538  model->getSampleRate());
539  RealTime rd = RealTime::frame2RealTime(note.getDuration(),
540  model->getSampleRate());
541 
542  QString pitchText;
543 
544  if (shouldConvertMIDIToHz()) {
545 
546  int mnote = int(lrint(note.getValue()));
547  int cents = int(lrint((note.getValue() - double(mnote)) * 100));
548  double freq = Pitch::getFrequencyForPitch(mnote, cents);
549  pitchText = tr("%1 (%2, %3 Hz)")
550  .arg(Pitch::getPitchLabel(mnote, cents))
551  .arg(mnote)
552  .arg(freq);
553 
554  } else if (getScaleUnits() == "Hz") {
555 
556  pitchText = tr("%1 Hz (%2, %3)")
557  .arg(note.getValue())
558  .arg(Pitch::getPitchLabelForFrequency(note.getValue()))
559  .arg(Pitch::getPitchForFrequency(note.getValue()));
560 
561  } else {
562  pitchText = tr("%1 %2")
563  .arg(note.getValue()).arg(getScaleUnits());
564  }
565 
566  QString text;
567 
568  if (note.getLabel() == "") {
569  text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
570  .arg(rt.toText(true).c_str())
571  .arg(pitchText)
572  .arg(rd.toText(true).c_str());
573  } else {
574  text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
575  .arg(rt.toText(true).c_str())
576  .arg(pitchText)
577  .arg(rd.toText(true).c_str())
578  .arg(note.getLabel());
579  }
580 
581  pos = QPoint(v->getXForFrame(note.getFrame()),
582  getYForValue(v, note.getValue()));
583  return text;
584 }
585 
586 bool
588  int &resolution,
589  SnapType snap, int ycoord) const
590 {
591  auto model = ModelById::getAs<NoteModel>(m_model);
592  if (!model) {
593  return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
594  }
595 
596  resolution = model->getResolution();
597  EventVector points;
598 
599  if (snap == SnapNeighbouring) {
600 
601  points = getLocalPoints(v, v->getXForFrame(frame));
602  if (points.empty()) return false;
603  frame = points.begin()->getFrame();
604  return true;
605  }
606 
607  points = model->getEventsCovering(frame);
608  sv_frame_t snapped = frame;
609  bool found = false;
610 
611  for (EventVector::const_iterator i = points.begin();
612  i != points.end(); ++i) {
613 
614  if (snap == SnapRight) {
615 
616  if (i->getFrame() > frame) {
617  snapped = i->getFrame();
618  found = true;
619  break;
620  } else if (i->getFrame() + i->getDuration() >= frame) {
621  snapped = i->getFrame() + i->getDuration();
622  found = true;
623  break;
624  }
625 
626  } else if (snap == SnapLeft) {
627 
628  if (i->getFrame() <= frame) {
629  snapped = i->getFrame();
630  found = true; // don't break, as the next may be better
631  } else {
632  break;
633  }
634 
635  } else { // nearest
636 
637  EventVector::const_iterator j = i;
638  ++j;
639 
640  if (j == points.end()) {
641 
642  snapped = i->getFrame();
643  found = true;
644  break;
645 
646  } else if (j->getFrame() >= frame) {
647 
648  if (j->getFrame() - frame < frame - i->getFrame()) {
649  snapped = j->getFrame();
650  } else {
651  snapped = i->getFrame();
652  }
653  found = true;
654  break;
655  }
656  }
657  }
658 
659  cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl;
660 
661  frame = snapped;
662  return found;
663 }
664 
665 void
666 FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
667 {
668  min = 0.0;
669  max = 0.0;
670  log = false;
671 
672  QString queryUnits;
673  if (shouldConvertMIDIToHz()) queryUnits = "Hz";
674  else queryUnits = getScaleUnits();
675 
676  if (shouldAutoAlign()) {
677 
678  if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) {
679 
680  auto model = ModelById::getAs<NoteModel>(m_model);
681  min = model->getValueMinimum();
682  max = model->getValueMaximum();
683 
684  if (shouldConvertMIDIToHz()) {
685  min = Pitch::getFrequencyForPitch(int(lrint(min)));
686  max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
687  }
688 
689 #ifdef DEBUG_NOTE_LAYER
690  cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
691 #endif
692 
693  } else if (log) {
694 
695  LogRange::mapRange(min, max);
696 
697 #ifdef DEBUG_NOTE_LAYER
698  cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
699 #endif
700  }
701 
702  } else {
703 
704  getDisplayExtents(min, max);
705 
707  min = Pitch::getFrequencyForPitch(0);
708  max = Pitch::getFrequencyForPitch(70);
709  } else if (shouldConvertMIDIToHz()) {
710  min = Pitch::getFrequencyForPitch(int(lrint(min)));
711  max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
712  }
713 
715  LogRange::mapRange(min, max);
716  log = true;
717  }
718  }
719 
720  if (max == min) max = min + 1.0;
721 }
722 
723 int
725 {
726  double min = 0.0, max = 0.0;
727  bool logarithmic = false;
728  int h = v->getPaintHeight();
729 
730  getScaleExtents(v, min, max, logarithmic);
731 
732 #ifdef DEBUG_NOTE_LAYER
733  cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
734 #endif
735 
736  if (shouldConvertMIDIToHz()) {
737  val = Pitch::getFrequencyForPitch(int(lrint(val)),
738  int(lrint((val - floor(val)) * 100.0)));
739 #ifdef DEBUG_NOTE_LAYER
740  cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
741 #endif
742  }
743 
744  if (logarithmic) {
745  val = LogRange::map(val);
746 #ifdef DEBUG_NOTE_LAYER
747  cerr << "logarithmic true, val now = " << val << endl;
748 #endif
749  }
750 
751  int y = int(h - ((val - min) * h) / (max - min)) - 1;
752 #ifdef DEBUG_NOTE_LAYER
753  cerr << "y = " << y << endl;
754 #endif
755  return y;
756 }
757 
758 double
760 {
761  double min = 0.0, max = 0.0;
762  bool logarithmic = false;
763  int h = v->getPaintHeight();
764 
765  getScaleExtents(v, min, max, logarithmic);
766 
767  double val = min + (double(h - y) * double(max - min)) / h;
768 
769  if (logarithmic) {
770  val = pow(10.f, val);
771  }
772 
773  if (shouldConvertMIDIToHz()) {
774  val = Pitch::getPitchForFrequency(val);
775  }
776 
777  return val;
778 }
779 
780 bool
782 {
783  return (m_verticalScale == AutoAlignScale);
784 }
785 
786 void
787 FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
788 {
789  auto model = ModelById::getAs<NoteModel>(m_model);
790  if (!model || !model->isOK()) return;
791 
792  sv_samplerate_t sampleRate = model->getSampleRate();
793  if (!sampleRate) return;
794 
795 // Profiler profiler("FlexiNoteLayer::paint", true);
796 
797  int x0 = rect.left(), x1 = rect.right();
798  sv_frame_t frame0 = v->getFrameForX(x0);
799  sv_frame_t frame1 = v->getFrameForX(x1);
800 
801  EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
802  if (points.empty()) return;
803 
804  paint.setPen(getBaseQColor());
805 
806  QColor brushColour(getBaseQColor());
807  brushColour.setAlpha(80);
808 
809 // SVDEBUG << "FlexiNoteLayer::paint: resolution is "
810 // << model->getResolution() << " frames" << endl;
811 
812  double min = model->getValueMinimum();
813  double max = model->getValueMaximum();
814  if (max == min) max = min + 1.0;
815 
816  QPoint localPos;
817  Event illuminatePoint(0);
818  bool shouldIlluminate = false;
819 
820  if (v->shouldIlluminateLocalFeatures(this, localPos)) {
821  shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
822  illuminatePoint);
823  }
824 
825  paint.save();
826  paint.setRenderHint(QPainter::Antialiasing, false);
827 
828  int noteNumber = -1;
829 
830  for (EventVector::const_iterator i = points.begin();
831  i != points.end(); ++i) {
832 
833  const Event &p(*i);
834 
835  if (noteNumber < 0) {
836  noteNumber = model->getIndexForEvent(p);
837  } else {
838  noteNumber ++;
839  }
840 
841  int x = v->getXForFrame(p.getFrame());
842  int y = getYForValue(v, p.getValue());
843  int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
844  int h = NOTE_HEIGHT; //GF: larger notes
845 
846  if (model->getValueQuantization() != 0.0) {
847  h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
848  if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
849  }
850 
851  if (w < 1) w = 1;
852  paint.setPen(getBaseQColor());
853  paint.setBrush(brushColour);
854 
855  if (shouldIlluminate && illuminatePoint == p) {
856 
857  paint.drawLine(x, -1, x, v->getPaintHeight() + 1);
858  paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1);
859 
860  paint.setPen(v->getForeground());
861 
862  QString vlabel = tr("freq: %1%2")
863  .arg(p.getValue()).arg(model->getScaleUnits());
865  (v, paint,
866  x,
867  y - h/2 - 2 - paint.fontMetrics().height()
868  - paint.fontMetrics().descent(),
870 
871  QString hlabel = tr("dur: %1")
872  .arg(RealTime::frame2RealTime
873  (p.getDuration(), model->getSampleRate()).toText(true)
874  .c_str());
876  (v, paint,
877  x,
878  y - h/2 - paint.fontMetrics().descent() - 2,
880 
881  QString llabel = QString("%1").arg(p.getLabel());
883  (v, paint,
884  x,
885  y + h + 2 + paint.fontMetrics().descent(),
887 
888  QString nlabel = QString("%1").arg(noteNumber);
890  (v, paint,
891  x + paint.fontMetrics().averageCharWidth() / 2,
892  y + h/2 - paint.fontMetrics().descent(),
894  }
895 
896  paint.drawRect(x, y - h/2, w, h);
897  }
898 
899  paint.restore();
900 }
901 
902 int
904 {
905  if (shouldAutoAlign()) {
906  return 0;
907  } else {
909  return LogNumericalScale().getWidth(v, paint) + 10; // for piano
910  } else {
911  return LinearNumericalScale().getWidth(v, paint);
912  }
913  }
914 }
915 
916 void
918 {
919  auto model = ModelById::getAs<NoteModel>(m_model);
920  if (!model || model->isEmpty()) return;
921 
922  QString unit;
923  double min, max;
924  bool logarithmic;
925 
926  int w = getVerticalScaleWidth(v, false, paint);
927  int h = v->getPaintHeight();
928 
929  getScaleExtents(v, min, max, logarithmic);
930 
931  if (logarithmic) {
932  LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
933  } else {
934  LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
935  }
936 
937  if (logarithmic && (getScaleUnits() == "Hz")) {
939  (v, paint, QRect(w - 10, 0, 10, h),
940  LogRange::unmap(min),
941  LogRange::unmap(max));
942  paint.drawLine(w, 0, w, h);
943  }
944 
945  if (getScaleUnits() != "") {
946  int mw = w - 5;
947  paint.drawText(5,
948  5 + paint.fontMetrics().ascent(),
950  paint.fontMetrics(),
951  mw));
952  }
953 }
954 
955 void
957 {
958 // SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
959 
960  auto model = ModelById::getAs<NoteModel>(m_model);
961  if (!model) return;
962 
963  sv_frame_t frame = v->getFrameForX(e->x());
964  if (frame < 0) frame = 0;
965  frame = frame / model->getResolution() * model->getResolution();
966 
967  double value = getValueForY(v, e->y());
968 
969  m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point"));
971 
973  m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
974  m_editingCommand->add(m_editingPoint);
975 
976  m_editing = true;
977 }
978 
979 void
981 {
982 // SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
983 
984  auto model = ModelById::getAs<NoteModel>(m_model);
985  if (!model || !m_editing) return;
986 
987  sv_frame_t frame = v->getFrameForX(e->x());
988  if (frame < 0) frame = 0;
989  frame = frame / model->getResolution() * model->getResolution();
990 
991  double newValue = getValueForY(v, e->y());
992 
993  sv_frame_t newFrame = m_editingPoint.getFrame();
994  sv_frame_t newDuration = frame - newFrame;
995  if (newDuration < 0) {
996  newFrame = frame;
997  newDuration = -newDuration;
998  } else if (newDuration == 0) {
999  newDuration = 1;
1000  }
1001 
1004  .withFrame(newFrame)
1005  .withValue(float(newValue))
1006  .withDuration(newDuration);
1008 }
1009 
1010 void
1012 {
1013 // SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
1014  auto model = ModelById::getAs<NoteModel>(m_model);
1015  if (!model || !m_editing) return;
1017  m_editingCommand = nullptr;
1018  m_editing = false;
1019 }
1020 
1021 void
1023 {
1024  auto model = ModelById::getAs<NoteModel>(m_model);
1025  if (!model) return;
1026 
1027  if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
1028 
1029  if (m_editingCommand) {
1031  m_editingCommand = nullptr;
1032  }
1033 
1034  m_editing = true;
1035 }
1036 
1037 void
1039 {
1040 }
1041 
1042 void
1044 {
1045  if (!m_editing) return;
1046  m_editing = false;
1047 
1048  Event p(0);
1049  if (!getPointToDrag(v, e->x(), e->y(), p)) return;
1050  if (p.getFrame() != m_editingPoint.getFrame() ||
1051  p.getValue() != m_editingPoint.getValue()) return;
1052 
1053  m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
1056  m_editingCommand = nullptr;
1057  m_editing = false;
1058 }
1059 
1060 void
1062 {
1063 // SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
1064  std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
1065 
1066  auto model = ModelById::getAs<NoteModel>(m_model);
1067  if (!model) return;
1068 
1069  if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
1071 
1072  if (m_editMode == RightBoundary) {
1074  (m_editingPoint.getFrame() + m_editingPoint.getDuration());
1075  } else {
1077  (m_editingPoint.getFrame());
1078  }
1079  m_dragPointY = getYForValue(v, m_editingPoint.getValue());
1080 
1081  if (m_editingCommand) {
1083  m_editingCommand = nullptr;
1084  }
1085 
1086  m_editing = true;
1087  m_dragStartX = e->x();
1088  m_dragStartY = e->y();
1089 
1090  sv_frame_t onset = m_originalPoint.getFrame();
1091  sv_frame_t offset =
1092  m_originalPoint.getFrame() +
1093  m_originalPoint.getDuration() - 1;
1094 
1096  m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
1097 
1098  EventVector allEvents = model->getAllEvents();
1099 
1100  for (auto currentNote: allEvents) {
1101 
1102  // left boundary
1103  if (currentNote.getFrame() + currentNote.getDuration() - 1 < onset) {
1105  currentNote.getFrame() + currentNote.getDuration() - 1;
1106  }
1107 
1108  // right boundary
1109  if (currentNote.getFrame() > offset) {
1110  m_smallestRightNeighbourFrame = currentNote.getFrame();
1111  break;
1112  }
1113  }
1114 
1115  std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl;
1116 }
1117 
1118 void
1120 {
1121 // SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
1122  std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
1123 
1124  auto model = ModelById::getAs<NoteModel>(m_model);
1125  if (!model || !m_editing) return;
1126 
1127  int xdist = e->x() - m_dragStartX;
1128  int ydist = e->y() - m_dragStartY;
1129  int newx = m_dragPointX + xdist;
1130  int newy = m_dragPointY + ydist;
1131 
1132  sv_frame_t dragFrame = v->getFrameForX(newx);
1133  if (dragFrame < 0) dragFrame = 0;
1134  dragFrame = dragFrame / model->getResolution() * model->getResolution();
1135 
1136  double value = getValueForY(v, newy);
1137 
1138  if (!m_editingCommand) {
1140  new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
1141  }
1143 
1144  std::cerr << "edit mode: " << m_editMode << " intelligent actions = "
1145  << m_intelligentActions << std::endl;
1146 
1147  switch (m_editMode) {
1148 
1149  case LeftBoundary : {
1150  // left
1151  if (m_intelligentActions &&
1152  dragFrame <= m_greatestLeftNeighbourFrame) {
1153  dragFrame = m_greatestLeftNeighbourFrame + 1;
1154  }
1155  // right
1156  if (m_intelligentActions &&
1157  dragFrame >= m_originalPoint.getFrame() + m_originalPoint.getDuration()) {
1158  dragFrame = m_originalPoint.getFrame() + m_originalPoint.getDuration() - 1;
1159  }
1161  .withFrame(dragFrame)
1162  .withDuration(m_originalPoint.getFrame() -
1163  dragFrame + m_originalPoint.getDuration());
1164  break;
1165  }
1166 
1167  case RightBoundary : {
1168  // left
1169  if (m_intelligentActions &&
1170  dragFrame <= m_greatestLeftNeighbourFrame) {
1171  dragFrame = m_greatestLeftNeighbourFrame + 1;
1172  }
1173  if (m_intelligentActions &&
1174  dragFrame >= m_smallestRightNeighbourFrame) {
1175  dragFrame = m_smallestRightNeighbourFrame - 1;
1176  }
1178  .withDuration(dragFrame - m_originalPoint.getFrame() + 1);
1179  break;
1180  }
1181 
1182  case DragNote : {
1183  // left
1184  if (m_intelligentActions &&
1185  dragFrame <= m_greatestLeftNeighbourFrame) {
1186  dragFrame = m_greatestLeftNeighbourFrame + 1;
1187  }
1188  // right
1189  if (m_intelligentActions &&
1190  dragFrame + m_originalPoint.getDuration() >= m_smallestRightNeighbourFrame) {
1191  dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.getDuration();
1192  }
1193 
1195  .withFrame(dragFrame)
1196  .withValue(float(value));
1197 
1198  // Re-analyse region within +/- 1 semitone of the dragged value
1199  float cents = 0;
1200  int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.getValue(), &cents);
1201  double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents);
1202  double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents);
1203 
1204  emit reAnalyseRegion(m_editingPoint.getFrame(),
1205  m_editingPoint.getFrame() +
1206  m_editingPoint.getDuration(),
1207  float(lower), float(higher));
1208  break;
1209  }
1210 
1211  case SplitNote: // nothing
1212  break;
1213  }
1214 
1216 
1217  std::cerr << "added new point(" << m_editingPoint.getFrame() << "," << m_editingPoint.getDuration() << ")" << std::endl;
1218 }
1219 
1220 void
1222 {
1223  std::cerr << "FlexiNoteLayer::editEnd("
1224  << e->x() << "," << e->y() << ")" << std::endl;
1225 
1226  auto model = ModelById::getAs<NoteModel>(m_model);
1227  if (!model || !m_editing) return;
1228 
1229  if (m_editingCommand) {
1230 
1231  QString newName = m_editingCommand->getName();
1232 
1233  if (m_editMode == DragNote) {
1235  emit materialiseReAnalysis();
1236  }
1237 
1241 
1242  if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
1243  if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
1244  newName = tr("Edit Point");
1245  } else {
1246  newName = tr("Relocate Point");
1247  }
1248  } else {
1249  newName = tr("Change Point Value");
1250  }
1251 
1252  m_editingCommand->setName(newName);
1254  }
1255 
1256  m_editingCommand = nullptr;
1257  m_editing = false;
1258 }
1259 
1260 void
1262 {
1263  auto model = ModelById::getAs<NoteModel>(m_model);
1264  if (!model) return;
1265 
1266  // GF: note splitting starts (!! remove printing soon)
1267  std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
1268 
1269  if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
1270  // m_originalPoint = m_editingPoint;
1271  //
1272  // m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
1273  // m_dragPointY = getYForValue(v, m_editingPoint.getValue());
1274 
1275  if (m_editingCommand) {
1277  m_editingCommand = nullptr;
1278  }
1279 
1280  m_editing = true;
1281  m_dragStartX = e->x();
1282  m_dragStartY = e->y();
1283 }
1284 
1285 void
1287 {
1288  auto model = ModelById::getAs<NoteModel>(m_model);
1289  // GF: note splitting ends. (!! remove printing soon)
1290  std::cerr << "splitEnd" << std::endl;
1291  if (!model || !m_editing || m_editMode != SplitNote) return;
1292 
1293  int xdist = e->x() - m_dragStartX;
1294  int ydist = e->y() - m_dragStartY;
1295  if (xdist != 0 || ydist != 0) {
1296  std::cerr << "mouse moved" << std::endl;
1297  return;
1298  }
1299 
1300  sv_frame_t frame = v->getFrameForX(e->x());
1301 
1302  splitNotesAt(v, frame, e);
1303 }
1304 
1305 void
1307 {
1308  splitNotesAt(v, frame, nullptr);
1309 }
1310 
1311 void
1312 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
1313 {
1314  auto model = ModelById::getAs<NoteModel>(m_model);
1315  if (!model) return;
1316 
1317  EventVector onPoints = model->getEventsCovering(frame);
1318  if (onPoints.empty()) return;
1319 
1320  Event note(*onPoints.begin());
1321 
1322  auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
1323  command->remove(note);
1324 
1325  if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
1326 
1327  int gap = 0; // MM: I prefer a gap of 0, but we can decide later
1328 
1329  Event newNote1(note.getFrame(), note.getValue(),
1330  frame - note.getFrame() - gap,
1331  note.getLevel(), note.getLabel());
1332 
1333  Event newNote2(frame, note.getValue(),
1334  note.getDuration() - newNote1.getDuration(),
1335  note.getLevel(), note.getLabel());
1336 
1337  if (m_intelligentActions) {
1338  if (updateNoteValueFromPitchCurve(v, newNote1)) {
1339  command->add(newNote1);
1340  }
1341  if (updateNoteValueFromPitchCurve(v, newNote2)) {
1342  command->add(newNote2);
1343  }
1344  } else {
1345  command->add(newNote1);
1346  command->add(newNote2);
1347  }
1348  }
1349 
1350  finish(command);
1351 }
1352 
1353 void
1355 {
1356  auto model = ModelById::getAs<NoteModel>(m_model);
1357  std::cerr << "addNote" << std::endl;
1358  if (!model) return;
1359 
1360  sv_frame_t duration = 10000;
1361 
1362  sv_frame_t frame = v->getFrameForX(e->x());
1363  double value = getValueForY(v, e->y());
1364 
1365  EventVector noteList = model->getAllEvents();
1366 
1367  if (m_intelligentActions) {
1368  sv_frame_t smallestRightNeighbourFrame = 0;
1369  for (EventVector::const_iterator i = noteList.begin();
1370  i != noteList.end(); ++i) {
1371  Event currentNote = *i;
1372  if (currentNote.getFrame() > frame) {
1373  smallestRightNeighbourFrame = currentNote.getFrame();
1374  break;
1375  }
1376  }
1377  if (smallestRightNeighbourFrame > 0) {
1378  duration = std::min(smallestRightNeighbourFrame - frame + 1, duration);
1379  duration = (duration > 0) ? duration : 0;
1380  }
1381  }
1382 
1383  if (!m_intelligentActions ||
1384  (model->getEventsCovering(frame).empty() && duration > 0)) {
1385  Event newNote(frame, float(value), duration, 100.f, tr("new note"));
1386  auto command = new ChangeEventsCommand(m_model.untyped, tr("Add Point"));
1387  command->add(newNote);
1388  finish(command);
1389  }
1390 }
1391 
1392 ModelId
1394 {
1395  // Better than we used to do, but still not very satisfactory
1396 
1397 // cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
1398 
1399  for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
1400  Layer *layer = v->getView()->getLayer(i);
1401  if (layer &&
1402  layer->getLayerPresentationName() != "candidate") {
1403 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
1404  auto modelId = layer->getModel();
1405  auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
1406  if (model && model->getScaleUnits() == "Hz") {
1407 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
1408  return modelId;
1409  }
1410  }
1411  }
1412 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
1413  return {};
1414 }
1415 
1416 void
1418 {
1419  auto model = ModelById::getAs<NoteModel>(m_model);
1420  if (!model) return;
1421 
1422  EventVector points =
1423  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1424 
1425  auto command = new ChangeEventsCommand(m_model.untyped, tr("Snap Notes"));
1426 
1427  cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
1428 
1429  for (EventVector::iterator i = points.begin();
1430  i != points.end(); ++i) {
1431 
1432  Event note(*i);
1433 
1434  cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.getFrame() << " to " << note.getFrame() + note.getDuration() << endl;
1435 
1436  if (!s.contains(note.getFrame()) &&
1437  !s.contains(note.getFrame() + note.getDuration() - 1)) {
1438  continue;
1439  }
1440 
1441  cerr << "snapSelectedNotesToPitchTrack: making new note" << endl;
1442  Event newNote(note);
1443 
1444  command->remove(note);
1445 
1446  if (updateNoteValueFromPitchCurve(v, newNote)) {
1447  command->add(newNote);
1448  }
1449  }
1450 
1451  finish(command);
1452 }
1453 
1454 void
1455 FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
1456 {
1457  auto model = ModelById::getAs<NoteModel>(m_model);
1458  if (!model) return;
1459 
1460  EventVector points;
1461  if (inclusive) {
1462  points = model->getEventsSpanning(s.getStartFrame(), s.getDuration());
1463  } else {
1464  points = model->getEventsWithin(s.getStartFrame(), s.getDuration());
1465  }
1466 
1467  EventVector::iterator i = points.begin();
1468  if (i == points.end()) return;
1469 
1470  auto command = new ChangeEventsCommand(m_model.untyped, tr("Merge Notes"));
1471 
1472  Event newNote(*i);
1473 
1474  while (i != points.end()) {
1475 
1476  if (inclusive) {
1477  if (i->getFrame() >= s.getEndFrame()) break;
1478  } else {
1479  if (i->getFrame() + i->getDuration() > s.getEndFrame()) break;
1480  }
1481 
1482  newNote = newNote.withDuration
1483  (i->getFrame() + i->getDuration() - newNote.getFrame());
1484  command->remove(*i);
1485 
1486  ++i;
1487  }
1488 
1489  updateNoteValueFromPitchCurve(v, newNote);
1490  command->add(newNote);
1491  finish(command);
1492 }
1493 
1494 bool
1496 {
1497  ModelId modelId = getAssociatedPitchModel(v);
1498  auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
1499  if (!model) return false;
1500 
1501  std::cerr << model->getTypeName() << std::endl;
1502 
1503  EventVector dataPoints =
1504  model->getEventsWithin(note.getFrame(), note.getDuration());
1505 
1506  std::cerr << "frame " << note.getFrame() << ": " << dataPoints.size() << " candidate points" << std::endl;
1507 
1508  if (dataPoints.empty()) return false;
1509 
1510  std::vector<double> pitchValues;
1511 
1512  for (EventVector::const_iterator i =
1513  dataPoints.begin(); i != dataPoints.end(); ++i) {
1514  pitchValues.push_back(i->getValue());
1515  }
1516 
1517  if (pitchValues.empty()) return false;
1518 
1519  sort(pitchValues.begin(), pitchValues.end());
1520  int size = int(pitchValues.size());
1521  double median;
1522 
1523  if (size % 2 == 0) {
1524  median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2;
1525  } else {
1526  median = pitchValues[size/2];
1527  }
1528 
1529  std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.getValue() << " to median " << median << std::endl;
1530 
1531  note = note.withValue(float(median));
1532 
1533  return true;
1534 }
1535 
1536 void
1538 {
1539  // GF: context sensitive cursors
1540  // v->getView()->setCursor(Qt::ArrowCursor);
1541  Event note(0);
1542  if (!getNoteToEdit(v, e->x(), e->y(), note)) {
1543  // v->getView()->setCursor(Qt::UpArrowCursor);
1544  return;
1545  }
1546 
1547  bool closeToLeft = false, closeToRight = false,
1548  closeToTop = false, closeToBottom = false;
1549  getRelativeMousePosition(v, note, e->x(), e->y(),
1550  closeToLeft, closeToRight,
1551  closeToTop, closeToBottom);
1552 
1553  if (closeToLeft) {
1554  v->getView()->setCursor(Qt::SizeHorCursor);
1556  cerr << "edit mode -> LeftBoundary" << endl;
1557  } else if (closeToRight) {
1558  v->getView()->setCursor(Qt::SizeHorCursor);
1560  cerr << "edit mode -> RightBoundary" << endl;
1561  } else if (closeToTop) {
1562  v->getView()->setCursor(Qt::CrossCursor);
1563  m_editMode = DragNote;
1564  cerr << "edit mode -> DragNote" << endl;
1565  } else if (closeToBottom) {
1566  v->getView()->setCursor(Qt::UpArrowCursor);
1568  cerr << "edit mode -> SplitNote" << endl;
1569  } else {
1570  v->getView()->setCursor(Qt::ArrowCursor);
1571  }
1572 }
1573 
1574 void
1575 FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
1576 {
1577  // GF: TODO: consolidate the tolerance values
1578 
1579  int ctol = 0;
1580  int noteStartX = v->getXForFrame(note.getFrame());
1581  int noteEndX = v->getXForFrame(note.getFrame() + note.getDuration());
1582  int noteValueY = getYForValue(v,note.getValue());
1583  int noteStartY = noteValueY - (NOTE_HEIGHT / 2);
1584  int noteEndY = noteValueY + (NOTE_HEIGHT / 2);
1585 
1586  bool closeToNote = false;
1587 
1588  if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true;
1589  if (!closeToNote) return;
1590 
1591  int tol = NOTE_HEIGHT / 2;
1592 
1593  if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true;
1594  if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true;
1595  if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true;
1596  if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true;
1597 
1598 // cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl;
1599 }
1600 
1601 
1602 bool
1604 {
1605  std::cerr << "Opening note editor dialog" << std::endl;
1606  auto model = ModelById::getAs<NoteModel>(m_model);
1607  if (!model) return false;
1608 
1609  Event note(0);
1610  if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
1611 
1612  ItemEditDialog *dialog = new ItemEditDialog
1613  (model->getSampleRate(),
1619  getScaleUnits());
1620 
1621  dialog->setFrameTime(note.getFrame());
1622  dialog->setValue(note.getValue());
1623  dialog->setFrameDuration(note.getDuration());
1624  dialog->setText(note.getLabel());
1625 
1626  if (dialog->exec() == QDialog::Accepted) {
1627 
1628  Event newNote = note
1629  .withFrame(dialog->getFrameTime())
1630  .withValue(dialog->getValue())
1631  .withDuration(dialog->getFrameDuration())
1632  .withLabel(dialog->getText());
1633 
1634  auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
1635  command->remove(note);
1636  command->add(newNote);
1637  finish(command);
1638  }
1639 
1640  delete dialog;
1641  return true;
1642 }
1643 
1644 void
1645 FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
1646 {
1647  auto model = ModelById::getAs<NoteModel>(m_model);
1648  if (!model) return;
1649 
1650  auto command = new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
1651 
1652  EventVector points =
1653  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1654 
1655  for (Event p: points) {
1656  command->remove(p);
1657  Event moved = p.withFrame(p.getFrame() +
1658  newStartFrame - s.getStartFrame());
1659  command->add(moved);
1660  }
1661 
1662  finish(command);
1663 }
1664 
1665 void
1666 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
1667 {
1668  auto model = ModelById::getAs<NoteModel>(m_model);
1669  if (!model || !s.getDuration()) return;
1670 
1671  auto command = new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
1672 
1673  EventVector points =
1674  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1675 
1676  double ratio = double(newSize.getDuration()) / double(s.getDuration());
1677  double oldStart = double(s.getStartFrame());
1678  double newStart = double(newSize.getStartFrame());
1679 
1680  for (Event p: points) {
1681 
1682  double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
1683  double newDuration = double(p.getDuration()) * ratio;
1684 
1685  Event newPoint = p
1686  .withFrame(lrint(newFrame))
1687  .withDuration(lrint(newDuration));
1688  command->remove(p);
1689  command->add(newPoint);
1690  }
1691 
1692  finish(command);
1693 }
1694 
1695 void
1697 {
1698  auto model = ModelById::getAs<NoteModel>(m_model);
1699  if (!model) return;
1700 
1701  auto command =
1702  new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
1703 
1704  EventVector points =
1705  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1706 
1707  for (Event p: points) {
1708  command->remove(p);
1709  }
1710 
1711  finish(command);
1712 }
1713 
1714 void
1716 {
1717  auto model = ModelById::getAs<NoteModel>(m_model);
1718  if (!model) return;
1719 
1720  auto command =
1721  new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
1722 
1723  EventVector points =
1724  model->getEventsSpanning(s.getStartFrame(), s.getDuration());
1725 
1726  for (Event p: points) {
1727  command->remove(p);
1728  }
1729 
1730  finish(command);
1731 }
1732 
1733 void
1734 FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
1735 {
1736  auto model = ModelById::getAs<NoteModel>(m_model);
1737  if (!model) return;
1738 
1739  EventVector points =
1740  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1741 
1742  for (Event p: points) {
1743  to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
1744  }
1745 }
1746 
1747 bool
1749  sv_frame_t /*frameOffset */, bool /* interactive */)
1750 {
1751  auto model = ModelById::getAs<NoteModel>(m_model);
1752  if (!model) return false;
1753 
1754  const EventVector &points = from.getPoints();
1755 
1756  bool realign = false;
1757 
1758  if (clipboardHasDifferentAlignment(v, from)) {
1759 
1760  QMessageBox::StandardButton button =
1761  QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
1762  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?"),
1763  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1764  QMessageBox::Yes);
1765 
1766  if (button == QMessageBox::Cancel) {
1767  return false;
1768  }
1769 
1770  if (button == QMessageBox::Yes) {
1771  realign = true;
1772  }
1773  }
1774 
1775  auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
1776 
1777  for (EventVector::const_iterator i = points.begin();
1778  i != points.end(); ++i) {
1779 
1780  sv_frame_t frame = 0;
1781 
1782  if (!realign) {
1783 
1784  frame = i->getFrame();
1785 
1786  } else {
1787 
1788  if (i->hasReferenceFrame()) {
1789  frame = i->getReferenceFrame();
1790  frame = alignFromReference(v, frame);
1791  } else {
1792  frame = i->getFrame();
1793  }
1794  }
1795 
1796  Event p = i->withFrame(frame);
1797 
1798  Event newPoint = p;
1799  if (!p.hasValue()) {
1800  newPoint = newPoint.withValue((model->getValueMinimum() +
1801  model->getValueMaximum()) / 2);
1802  }
1803  if (!p.hasDuration()) {
1804  sv_frame_t nextFrame = frame;
1805  EventVector::const_iterator j = i;
1806  for (; j != points.end(); ++j) {
1807  if (j != i) break;
1808  }
1809  if (j != points.end()) {
1810  nextFrame = j->getFrame();
1811  }
1812  if (nextFrame == frame) {
1813  newPoint = newPoint.withDuration(model->getResolution());
1814  } else {
1815  newPoint = newPoint.withDuration(nextFrame - frame);
1816  }
1817  }
1818 
1819  command->add(newPoint);
1820  }
1821 
1822  finish(command);
1823  return true;
1824 }
1825 
1826 void
1827 FlexiNoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
1828 {
1829  m_pendingNoteOns.insert(Event(frame, float(pitch), 0,
1830  float(velocity / 127.0), ""));
1831 }
1832 
1833 void
1834 FlexiNoteLayer::addNoteOff(sv_frame_t frame, int pitch)
1835 {
1836  for (NoteSet::iterator i = m_pendingNoteOns.begin();
1837  i != m_pendingNoteOns.end(); ++i) {
1838 
1839  Event p = *i;
1840 
1841  if (lrintf(p.getValue()) == pitch) {
1842  m_pendingNoteOns.erase(i);
1843  Event note = p.withDuration(frame - p.getFrame());
1844  auto c = new ChangeEventsCommand
1845  (m_model.untyped, tr("Record Note"));
1846  c->add(note);
1847  // execute and bundle:
1848  CommandHistory::getInstance()->addCommand(c, true, true);
1849  break;
1850  }
1851  }
1852 }
1853 
1854 void
1856 {
1857  m_pendingNoteOns.clear();
1858 }
1859 
1860 int
1861 FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
1862 {
1863  impose = false;
1865  (QString(darkbg ? "White" : "Black"));
1866 }
1867 
1868 void
1869 FlexiNoteLayer::toXml(QTextStream &stream,
1870  QString indent, QString extraAttributes) const
1871 {
1872  SingleColourLayer::toXml(stream, indent, extraAttributes +
1873  QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
1874  .arg(m_verticalScale)
1875  .arg(m_scaleMinimum)
1876  .arg(m_scaleMaximum));
1877 }
1878 
1879 void
1880 FlexiNoteLayer::setProperties(const QXmlAttributes &attributes)
1881 {
1883 
1884  bool ok;
1885  VerticalScale scale = (VerticalScale)
1886  attributes.value("verticalScale").toInt(&ok);
1887  if (ok) setVerticalScale(scale);
1888 }
1889 
1890 void
1892 {
1893  auto model = ModelById::getAs<NoteModel>(m_model);
1894  if (!model) return;
1895 
1896  double minf = std::numeric_limits<double>::max();
1897  double maxf = 0;
1898  bool hasNotes = 0;
1899  EventVector allPoints = model->getAllEvents();
1900  for (EventVector::const_iterator i = allPoints.begin();
1901  i != allPoints.end(); ++i) {
1902  hasNotes = 1;
1903  Event note = *i;
1904  if (note.getValue() < minf) minf = note.getValue();
1905  if (note.getValue() > maxf) maxf = note.getValue();
1906  }
1907 
1908  std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
1909 
1910  if (hasNotes) {
1911  v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5);
1912  // MM: this is a hack because we rely on
1913  // * this layer being automatically aligned to layer 1
1914  // * layer one is a log frequency layer.
1915  }
1916 }
1917 
1918 
float getValue() const
void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override
QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
void editEnd(LayerGeometryProvider *v, QMouseEvent *) override
virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &resolution, SnapType, int) const
Adjust the given frame to snap to the nearest feature, if possible.
Definition: Layer.h:226
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
The base class for visual representations of the data found in a Model.
Definition: Layer.h:54
int getCompletion(LayerGeometryProvider *) const override
Return the proportion of background work complete in drawing this view, as a percentage – in most ca...
void setFrameDuration(sv_frame_t frame)
virtual QColor getForeground() const =0
bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &) const
int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override
int getDefaultColourHint(bool dark, bool &impose) override
void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override
PropertyList getProperties() const override
virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const =0
void splitStart(LayerGeometryProvider *v, QMouseEvent *) override
static int scalePixelSize(int pixels)
Take a "design pixel" size and scale it for the actual display.
bool setDisplayExtents(double min, double max) override
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
void modelReplaced()
static QString abbreviate(QString text, int maxLength, Policy policy=ElideEnd, bool fuzzy=true, QString ellipsis="")
Abbreviate the given text to the given maximum length (including ellipsis), using the given abbreviat...
Definition: TextAbbrev.cpp:79
virtual int getLayerCount() const
Return the number of layers, regardless of whether visible or dormant, i.e.
Definition: View.h:197
QString getPropertyValueLabel(const PropertyName &, int value) const override
void setVerticalRangeToNoteRange(LayerGeometryProvider *v)
void setProperty(const PropertyName &, int value) override
void setModel(ModelId model)
void moveSelection(Selection s, sv_frame_t newStartFrame) override
void addCommand(Command *command)
Add a command to the command history.
bool shouldConvertMIDIToHz() const
void setFrameTime(sv_frame_t frame)
void paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minlog, double maxlog)
QString getPropertyGroupName(const PropertyName &) const override
void setVerticalScale(VerticalScale scale)
bool getValueExtents(double &min, double &max, bool &log, QString &unit) const override
Return the minimum and maximum values for the y axis of the model in this layer, as well as whether t...
sv_frame_t getFrameTime() const
virtual sv_frame_t getFrameForX(int x) const =0
Return the closest frame to the given pixel x-coordinate.
virtual void deleteSelectionInclusive(Selection s)
PropertyList getProperties() const override
void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override
void eraseEnd(LayerGeometryProvider *v, QMouseEvent *) override
void eraseStart(LayerGeometryProvider *v, QMouseEvent *) override
void drawEnd(LayerGeometryProvider *v, QMouseEvent *) override
sv_frame_t m_greatestLeftNeighbourFrame
int getWidth(LayerGeometryProvider *v, QPainter &paint)
virtual QColor getBaseQColor() const
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override
Paint the given rectangle of this layer onto the given view using the given painter, superimposing it on top of any existing material in that view.
int getWidth(LayerGeometryProvider *v, QPainter &paint)
void deleteSelection(Selection s) override
int getVerticalZoomSteps(int &defaultStep) const override
Get the number of vertical zoom steps available for this layer.
void modelChanged(ModelId)
void setText(QString text)
Interface for classes that provide geometry information (such as size, start frame, and a large number of other properties) about the disposition of a layer.
bool isLayerScrollable(const LayerGeometryProvider *v) const override
This should return true if the layer can safely be scrolled automatically by a given view (simply cop...
bool editOpen(LayerGeometryProvider *v, QMouseEvent *) override
Open an editor on the item under the mouse (e.g.
void splitEnd(LayerGeometryProvider *v, QMouseEvent *) override
int getColourIndex(QString name) const
Return the index of the colour with the given name, if found in the database.
virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
Definition: Layer.cpp:198
QString getPropertyGroupName(const PropertyName &) const override
virtual QString getLayerPresentationName() const
Definition: Layer.cpp:99
void finish(ChangeEventsCommand *command)
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
int getCurrentVerticalZoomStep() const override
Get the current vertical zoom step.
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
void layerParametersChanged()
bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap, int ycoord) const override
Adjust the given frame to snap to the nearest feature, if possible.
void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const
Definition: Layer.cpp:209
void addNote(LayerGeometryProvider *v, QMouseEvent *e) override
bool shouldAutoAlign() const
void paintPianoVertical(LayerGeometryProvider *v, QPainter &paint, QRect rect, double minf, double maxf)
Definition: PianoScale.cpp:31
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
SnapType
Definition: Layer.h:195
virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
Definition: Layer.cpp:187
void paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minf, double maxf)
static CommandHistory * getInstance()
void connectSignals(ModelId)
Definition: Layer.cpp:49
QString getPropertyLabel(const PropertyName &) const override
virtual int getPaintHeight() const
bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive) override
Paste from the given clipboard onto the layer at the given frame offset.
VerticalScale m_verticalScale
virtual Layer * getLayer(int n)
Return the nth layer, counted in stacking order.
Definition: View.h:205
PropertyType getPropertyType(const PropertyName &) const override
void abandonNoteOns()
Abandon all pending note-on events.
bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const
void editStart(LayerGeometryProvider *v, QMouseEvent *) override
double getValueForY(LayerGeometryProvider *v, int y) const override
QString getText() const
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
void setVerticalZoomStep(int) override
!! lots of duplication with TimeValueLayer
sv_frame_t m_smallestRightNeighbourFrame
virtual bool setDisplayExtents(double, double)
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
Definition: Layer.h:521
void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame)
ModelId getAssociatedPitchModel(LayerGeometryProvider *v) const
void eraseDrag(LayerGeometryProvider *v, QMouseEvent *) override
void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float)
virtual void mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *)
QString getPropertyValueLabel(const PropertyName &, int value) const override
EditMode m_editMode
void editDrag(LayerGeometryProvider *v, QMouseEvent *) override
virtual ModelId getModel() const =0
Return the ID of the model represented in this layer.
static void drawVisibleText(const LayerGeometryProvider *, QPainter &p, int x, int y, QString text, TextStyle style)
ChangeEventsCommand * m_editingCommand
void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const
bool getDisplayExtents(double &min, double &max) const override
Return the minimum and maximum values within the visible area for the y axis of this layer...
QString getScaleUnits() const override
void drawStart(LayerGeometryProvider *v, QMouseEvent *) override
virtual bool getVisibleExtentsForUnit(QString unit, double &min, double &max, bool &log) const =0
Return the visible vertical extents for the given unit, if any.
#define NOTE_HEIGHT
void resizeSelection(Selection s, Selection newSize) override
int getYForValue(LayerGeometryProvider *v, double value) const override
VerticalScaleLayer methods.
void addNoteOff(sv_frame_t frame, int pitch)
Add a note-off.
EventVector getLocalPoints(LayerGeometryProvider *v, int) const
NoteSet m_pendingNoteOns
void setProperty(const PropertyName &, int value) override
virtual int getXForFrame(sv_frame_t frame) const =0
Return the pixel x-coordinate corresponding to a given sample frame (which may be negative)...
void addNoteOn(sv_frame_t frame, int pitch, int velocity)
Add a note-on.
sv_frame_t getFrameDuration() const
bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const
QString getPropertyLabel(const PropertyName &) const override
PropertyType getPropertyType(const PropertyName &) const override
RangeMapper * getNewVerticalZoomRangeMapper() const override
Create and return a range mapper for vertical zoom step values.
static ColourDatabase * getInstance()
virtual View * getView()=0
void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
void setValue(float value)
void materialiseReAnalysis()
void getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const