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