Mercurial > hg > svgui
comparison layer/FlexiNoteLayer.cpp @ 718:e5f4385615ac tony_integration
Merge from tonioni branch
author | Chris Cannam |
---|---|
date | Tue, 28 Jan 2014 15:02:09 +0000 |
parents | 137d3ff48f73 |
children | d50f91fe374e |
comparison
equal
deleted
inserted
replaced
704:b81f21f2c4c3 | 718:e5f4385615ac |
---|---|
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 << "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 m_editingCommand->addPoint(m_editingPoint); | |
1145 std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl; | |
1146 | |
1147 } | |
1148 | |
1149 void | |
1150 FlexiNoteLayer::editEnd(View *v, QMouseEvent *e) | |
1151 { | |
1152 // SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; | |
1153 std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; | |
1154 | |
1155 if (!m_model || !m_editing) return; | |
1156 | |
1157 if (m_editingCommand) { | |
1158 | |
1159 QString newName = m_editingCommand->getName(); | |
1160 | |
1161 if (m_editingPoint.frame != m_originalPoint.frame) { | |
1162 if (m_editingPoint.value != m_originalPoint.value) { | |
1163 newName = tr("Edit Point"); | |
1164 } else { | |
1165 newName = tr("Relocate Point"); | |
1166 } | |
1167 } else { | |
1168 newName = tr("Change Point Value"); | |
1169 } | |
1170 | |
1171 m_editingCommand->setName(newName); | |
1172 finish(m_editingCommand); | |
1173 } | |
1174 | |
1175 m_editingCommand = 0; | |
1176 m_editing = false; | |
1177 } | |
1178 | |
1179 void | |
1180 FlexiNoteLayer::splitStart(View *v, QMouseEvent *e) | |
1181 { | |
1182 // GF: note splitting starts (!! remove printing soon) | |
1183 std::cerr << "splitStart" << std::endl; | |
1184 if (!m_model) return; | |
1185 | |
1186 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; | |
1187 // m_originalPoint = m_editingPoint; | |
1188 // | |
1189 // m_dragPointX = v->getXForFrame(m_editingPoint.frame); | |
1190 // m_dragPointY = getYForValue(v, m_editingPoint.value); | |
1191 | |
1192 if (m_editingCommand) { | |
1193 finish(m_editingCommand); | |
1194 m_editingCommand = 0; | |
1195 } | |
1196 | |
1197 m_editing = true; | |
1198 m_dragStartX = e->x(); | |
1199 m_dragStartY = e->y(); | |
1200 | |
1201 } | |
1202 | |
1203 void | |
1204 FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e) | |
1205 { | |
1206 // GF: note splitting ends. (!! remove printing soon) | |
1207 std::cerr << "splitEnd" << std::endl; | |
1208 if (!m_model || !m_editing || m_editMode != SplitNote) return; | |
1209 | |
1210 int xdist = e->x() - m_dragStartX; | |
1211 int ydist = e->y() - m_dragStartY; | |
1212 if (xdist != 0 || ydist != 0) { | |
1213 std::cerr << "mouse moved" << std::endl; | |
1214 return; | |
1215 } | |
1216 | |
1217 // MM: simpler declaration | |
1218 FlexiNote note(0); | |
1219 if (!getPointToDrag(v, e->x(), e->y(), note)) return; | |
1220 | |
1221 long frame = v->getFrameForX(e->x()); | |
1222 | |
1223 int gap = 0; // MM: I prefer a gap of 0, but we can decide later | |
1224 | |
1225 // MM: changed this a bit, to make it slightly clearer (// GF: nice changes!) | |
1226 FlexiNote newNote1(note.frame, note.value, | |
1227 frame - note.frame - gap, | |
1228 note.level, note.label); | |
1229 | |
1230 FlexiNote newNote2(frame, note.value, | |
1231 note.duration - newNote1.duration, | |
1232 note.level, note.label); | |
1233 | |
1234 if (m_intelligentActions) { | |
1235 updateNoteValue(v,newNote1); | |
1236 updateNoteValue(v,newNote2); | |
1237 } | |
1238 | |
1239 FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand | |
1240 (m_model, tr("Edit Point")); | |
1241 command->deletePoint(note); | |
1242 if ((e->modifiers() & Qt::ShiftModifier)) { | |
1243 finish(command); | |
1244 return; | |
1245 } | |
1246 command->addPoint(newNote1); | |
1247 command->addPoint(newNote2); | |
1248 finish(command); | |
1249 | |
1250 } | |
1251 | |
1252 void | |
1253 FlexiNoteLayer::addNote(View *v, QMouseEvent *e) | |
1254 { | |
1255 std::cerr << "addNote" << std::endl; | |
1256 if (!m_model) return; | |
1257 | |
1258 long duration = 10000; | |
1259 | |
1260 long frame = v->getFrameForX(e->x()); | |
1261 float value = getValueForY(v, e->y()); | |
1262 | |
1263 if (m_intelligentActions) { | |
1264 long smallestRightNeighbourFrame = 0; | |
1265 for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); | |
1266 i != m_model->getPoints().end(); ++i) { | |
1267 FlexiNote currentNote = *i; | |
1268 if (currentNote.frame > frame) { | |
1269 smallestRightNeighbourFrame = currentNote.frame; | |
1270 break; | |
1271 } | |
1272 } | |
1273 | |
1274 duration = std::min(smallestRightNeighbourFrame - frame + 1, duration); | |
1275 duration = (duration > 0) ? duration : 0; | |
1276 } | |
1277 | |
1278 if (!m_intelligentActions || | |
1279 m_model->getPoints(frame).empty() && duration > 0) | |
1280 { | |
1281 FlexiNote newNote(frame, value, duration, 100, "new note"); | |
1282 FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand | |
1283 (m_model, tr("Add Point")); | |
1284 command->addPoint(newNote); | |
1285 finish(command); | |
1286 } | |
1287 } | |
1288 | |
1289 | |
1290 void | |
1291 FlexiNoteLayer::updateNoteValue(View *v, FlexiNoteModel::Point ¬e) const | |
1292 { | |
1293 //GF: update the note value conforming the median of pitch values in the underlying note layer | |
1294 Layer *layer = v->getLayer(1); // GF: !!! gross assumption about correct layer order | |
1295 SparseTimeValueModel *model = 0; | |
1296 if (layer && layer->getModel()) | |
1297 model = dynamic_cast<SparseTimeValueModel *>(layer->getModel()); | |
1298 | |
1299 if (!model) return; | |
1300 | |
1301 std::cerr << model->getTypeName() << std::endl; | |
1302 | |
1303 SparseModel<TimeValuePoint>::PointList dataPoints = model->getPoints(note.frame, note.frame + note.duration); | |
1304 if (dataPoints.empty()) return; | |
1305 | |
1306 // std::cerr << "frame " << note.frame << ": " << dataPoints.size() << " candidate points" << std::endl; | |
1307 | |
1308 std::vector<float> pitchValues; | |
1309 | |
1310 for (SparseModel<TimeValuePoint>::PointList::const_iterator i = dataPoints.begin(); | |
1311 i != dataPoints.end(); ++i) { | |
1312 pitchValues.push_back((*i).value); | |
1313 } | |
1314 sort(pitchValues.begin(), pitchValues.end()); | |
1315 size_t size = pitchValues.size(); | |
1316 double median; | |
1317 | |
1318 if (size % 2 == 0) { | |
1319 median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2; | |
1320 } else { | |
1321 median = pitchValues[size/2]; | |
1322 } | |
1323 | |
1324 note.value = median; | |
1325 } | |
1326 | |
1327 void | |
1328 FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e) | |
1329 { | |
1330 // GF: context sensitive cursors | |
1331 // v->setCursor(Qt::ArrowCursor); | |
1332 FlexiNoteModel::Point note(0); | |
1333 if (!getNoteToEdit(v, e->x(), e->y(), note)) { | |
1334 // v->setCursor(Qt::UpArrowCursor); | |
1335 return; | |
1336 } | |
1337 | |
1338 bool closeToLeft = false, closeToRight = false, closeToTop = false, closeToBottom = false; | |
1339 getRelativeMousePosition(v, note, e->x(), e->y(), closeToLeft, closeToRight, closeToTop, closeToBottom); | |
1340 // if (!closeToLeft) return; | |
1341 // if (closeToTop) v->setCursor(Qt::SizeVerCursor); | |
1342 | |
1343 if (closeToLeft) { v->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; } | |
1344 if (closeToRight) { v->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; } | |
1345 if (closeToTop) { v->setCursor(Qt::CrossCursor); m_editMode = DragNote; return; } | |
1346 if (closeToBottom) { v->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; } | |
1347 | |
1348 v->setCursor(Qt::ArrowCursor); | |
1349 | |
1350 // std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl; | |
1351 // v->setCursor(Qt::SizeHorCursor); | |
1352 | |
1353 } | |
1354 | |
1355 void | |
1356 FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const | |
1357 { | |
1358 // GF: TODO: consoloidate the tolerance values | |
1359 if (!m_model) return; | |
1360 | |
1361 int ctol = 0; | |
1362 int noteStartX = v->getXForFrame(note.frame); | |
1363 int noteEndX = v->getXForFrame(note.frame + note.duration); | |
1364 int noteValueY = getYForValue(v,note.value); | |
1365 int noteStartY = noteValueY - (NOTE_HEIGHT / 2); | |
1366 int noteEndY = noteValueY + (NOTE_HEIGHT / 2); | |
1367 | |
1368 bool closeToNote = false; | |
1369 | |
1370 if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true; | |
1371 if (!closeToNote) return; | |
1372 | |
1373 int tol = NOTE_HEIGHT / 2; | |
1374 | |
1375 if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true; | |
1376 if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true; | |
1377 if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true; | |
1378 if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true; | |
1379 | |
1380 // cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl; | |
1381 } | |
1382 | |
1383 | |
1384 bool | |
1385 FlexiNoteLayer::editOpen(View *v, QMouseEvent *e) | |
1386 { | |
1387 std::cerr << "Opening note editor dialog" << std::endl; | |
1388 if (!m_model) return false; | |
1389 | |
1390 FlexiNoteModel::Point note(0); | |
1391 if (!getPointToDrag(v, e->x(), e->y(), note)) return false; | |
1392 | |
1393 // FlexiNoteModel::Point note = *points.begin(); | |
1394 | |
1395 ItemEditDialog *dialog = new ItemEditDialog | |
1396 (m_model->getSampleRate(), | |
1397 ItemEditDialog::ShowTime | | |
1398 ItemEditDialog::ShowDuration | | |
1399 ItemEditDialog::ShowValue | | |
1400 ItemEditDialog::ShowText, | |
1401 getScaleUnits()); | |
1402 | |
1403 dialog->setFrameTime(note.frame); | |
1404 dialog->setValue(note.value); | |
1405 dialog->setFrameDuration(note.duration); | |
1406 dialog->setText(note.label); | |
1407 | |
1408 if (dialog->exec() == QDialog::Accepted) { | |
1409 | |
1410 FlexiNoteModel::Point newNote = note; | |
1411 newNote.frame = dialog->getFrameTime(); | |
1412 newNote.value = dialog->getValue(); | |
1413 newNote.duration = dialog->getFrameDuration(); | |
1414 newNote.label = dialog->getText(); | |
1415 | |
1416 FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand | |
1417 (m_model, tr("Edit Point")); | |
1418 command->deletePoint(note); | |
1419 command->addPoint(newNote); | |
1420 finish(command); | |
1421 } | |
1422 | |
1423 delete dialog; | |
1424 return true; | |
1425 } | |
1426 | |
1427 void | |
1428 FlexiNoteLayer::moveSelection(Selection s, size_t newStartFrame) | |
1429 { | |
1430 if (!m_model) return; | |
1431 | |
1432 FlexiNoteModel::EditCommand *command = | |
1433 new FlexiNoteModel::EditCommand(m_model, tr("Drag Selection")); | |
1434 | |
1435 FlexiNoteModel::PointList points = | |
1436 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | |
1437 | |
1438 for (FlexiNoteModel::PointList::iterator i = points.begin(); | |
1439 i != points.end(); ++i) { | |
1440 | |
1441 if (s.contains(i->frame)) { | |
1442 FlexiNoteModel::Point newPoint(*i); | |
1443 newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); | |
1444 command->deletePoint(*i); | |
1445 command->addPoint(newPoint); | |
1446 } | |
1447 } | |
1448 | |
1449 finish(command); | |
1450 } | |
1451 | |
1452 void | |
1453 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize) | |
1454 { | |
1455 if (!m_model) return; | |
1456 | |
1457 FlexiNoteModel::EditCommand *command = | |
1458 new FlexiNoteModel::EditCommand(m_model, tr("Resize Selection")); | |
1459 | |
1460 FlexiNoteModel::PointList points = | |
1461 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | |
1462 | |
1463 double ratio = | |
1464 double(newSize.getEndFrame() - newSize.getStartFrame()) / | |
1465 double(s.getEndFrame() - s.getStartFrame()); | |
1466 | |
1467 for (FlexiNoteModel::PointList::iterator i = points.begin(); | |
1468 i != points.end(); ++i) { | |
1469 | |
1470 if (s.contains(i->frame)) { | |
1471 | |
1472 double targetStart = i->frame; | |
1473 targetStart = newSize.getStartFrame() + | |
1474 double(targetStart - s.getStartFrame()) * ratio; | |
1475 | |
1476 double targetEnd = i->frame + i->duration; | |
1477 targetEnd = newSize.getStartFrame() + | |
1478 double(targetEnd - s.getStartFrame()) * ratio; | |
1479 | |
1480 FlexiNoteModel::Point newPoint(*i); | |
1481 newPoint.frame = lrint(targetStart); | |
1482 newPoint.duration = lrint(targetEnd - targetStart); | |
1483 command->deletePoint(*i); | |
1484 command->addPoint(newPoint); | |
1485 } | |
1486 } | |
1487 | |
1488 finish(command); | |
1489 } | |
1490 | |
1491 void | |
1492 FlexiNoteLayer::deleteSelection(Selection s) | |
1493 { | |
1494 if (!m_model) return; | |
1495 | |
1496 FlexiNoteModel::EditCommand *command = | |
1497 new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points")); | |
1498 | |
1499 FlexiNoteModel::PointList points = | |
1500 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | |
1501 | |
1502 for (FlexiNoteModel::PointList::iterator i = points.begin(); | |
1503 i != points.end(); ++i) { | |
1504 | |
1505 if (s.contains(i->frame)) { | |
1506 command->deletePoint(*i); | |
1507 } | |
1508 } | |
1509 | |
1510 finish(command); | |
1511 } | |
1512 | |
1513 void | |
1514 FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to) | |
1515 { | |
1516 if (!m_model) return; | |
1517 | |
1518 FlexiNoteModel::PointList points = | |
1519 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | |
1520 | |
1521 for (FlexiNoteModel::PointList::iterator i = points.begin(); | |
1522 i != points.end(); ++i) { | |
1523 if (s.contains(i->frame)) { | |
1524 Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label); | |
1525 point.setReferenceFrame(alignToReference(v, i->frame)); | |
1526 to.addPoint(point); | |
1527 } | |
1528 } | |
1529 } | |
1530 | |
1531 bool | |
1532 FlexiNoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */) | |
1533 { | |
1534 if (!m_model) return false; | |
1535 | |
1536 const Clipboard::PointList &points = from.getPoints(); | |
1537 | |
1538 bool realign = false; | |
1539 | |
1540 if (clipboardHasDifferentAlignment(v, from)) { | |
1541 | |
1542 QMessageBox::StandardButton button = | |
1543 QMessageBox::question(v, tr("Re-align pasted items?"), | |
1544 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?"), | |
1545 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, | |
1546 QMessageBox::Yes); | |
1547 | |
1548 if (button == QMessageBox::Cancel) { | |
1549 return false; | |
1550 } | |
1551 | |
1552 if (button == QMessageBox::Yes) { | |
1553 realign = true; | |
1554 } | |
1555 } | |
1556 | |
1557 FlexiNoteModel::EditCommand *command = | |
1558 new FlexiNoteModel::EditCommand(m_model, tr("Paste")); | |
1559 | |
1560 for (Clipboard::PointList::const_iterator i = points.begin(); | |
1561 i != points.end(); ++i) { | |
1562 | |
1563 if (!i->haveFrame()) continue; | |
1564 size_t frame = 0; | |
1565 | |
1566 if (!realign) { | |
1567 | |
1568 frame = i->getFrame(); | |
1569 | |
1570 } else { | |
1571 | |
1572 if (i->haveReferenceFrame()) { | |
1573 frame = i->getReferenceFrame(); | |
1574 frame = alignFromReference(v, frame); | |
1575 } else { | |
1576 frame = i->getFrame(); | |
1577 } | |
1578 } | |
1579 | |
1580 FlexiNoteModel::Point newPoint(frame); | |
1581 | |
1582 if (i->haveLabel()) newPoint.label = i->getLabel(); | |
1583 if (i->haveValue()) newPoint.value = i->getValue(); | |
1584 else newPoint.value = (m_model->getValueMinimum() + | |
1585 m_model->getValueMaximum()) / 2; | |
1586 if (i->haveLevel()) newPoint.level = i->getLevel(); | |
1587 if (i->haveDuration()) newPoint.duration = i->getDuration(); | |
1588 else { | |
1589 size_t nextFrame = frame; | |
1590 Clipboard::PointList::const_iterator j = i; | |
1591 for (; j != points.end(); ++j) { | |
1592 if (!j->haveFrame()) continue; | |
1593 if (j != i) break; | |
1594 } | |
1595 if (j != points.end()) { | |
1596 nextFrame = j->getFrame(); | |
1597 } | |
1598 if (nextFrame == frame) { | |
1599 newPoint.duration = m_model->getResolution(); | |
1600 } else { | |
1601 newPoint.duration = nextFrame - frame; | |
1602 } | |
1603 } | |
1604 | |
1605 command->addPoint(newPoint); | |
1606 } | |
1607 | |
1608 finish(command); | |
1609 return true; | |
1610 } | |
1611 | |
1612 void | |
1613 FlexiNoteLayer::addNoteOn(long frame, int pitch, int velocity) | |
1614 { | |
1615 m_pendingNoteOns.insert(FlexiNote(frame, pitch, 0, float(velocity) / 127.0, "")); | |
1616 } | |
1617 | |
1618 void | |
1619 FlexiNoteLayer::addNoteOff(long frame, int pitch) | |
1620 { | |
1621 for (FlexiNoteSet::iterator i = m_pendingNoteOns.begin(); | |
1622 i != m_pendingNoteOns.end(); ++i) { | |
1623 if (lrintf((*i).value) == pitch) { | |
1624 FlexiNote note(*i); | |
1625 m_pendingNoteOns.erase(i); | |
1626 note.duration = frame - note.frame; | |
1627 if (m_model) { | |
1628 FlexiNoteModel::AddPointCommand *c = new FlexiNoteModel::AddPointCommand | |
1629 (m_model, note, tr("Record FlexiNote")); | |
1630 // execute and bundle: | |
1631 CommandHistory::getInstance()->addCommand(c, true, true); | |
1632 } | |
1633 break; | |
1634 } | |
1635 } | |
1636 } | |
1637 | |
1638 void | |
1639 FlexiNoteLayer::abandonNoteOns() | |
1640 { | |
1641 m_pendingNoteOns.clear(); | |
1642 } | |
1643 | |
1644 int | |
1645 FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose) | |
1646 { | |
1647 impose = false; | |
1648 return ColourDatabase::getInstance()->getColourIndex | |
1649 (QString(darkbg ? "White" : "Black")); | |
1650 } | |
1651 | |
1652 void | |
1653 FlexiNoteLayer::toXml(QTextStream &stream, | |
1654 QString indent, QString extraAttributes) const | |
1655 { | |
1656 SingleColourLayer::toXml(stream, indent, extraAttributes + | |
1657 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ") | |
1658 .arg(m_verticalScale) | |
1659 .arg(m_scaleMinimum) | |
1660 .arg(m_scaleMaximum)); | |
1661 } | |
1662 | |
1663 void | |
1664 FlexiNoteLayer::setProperties(const QXmlAttributes &attributes) | |
1665 { | |
1666 SingleColourLayer::setProperties(attributes); | |
1667 | |
1668 bool ok, alsoOk; | |
1669 VerticalScale scale = (VerticalScale) | |
1670 attributes.value("verticalScale").toInt(&ok); | |
1671 if (ok) setVerticalScale(scale); | |
1672 | |
1673 float min = attributes.value("scaleMinimum").toFloat(&ok); | |
1674 float max = attributes.value("scaleMaximum").toFloat(&alsoOk); | |
1675 // if (ok && alsoOk && min != max) setDisplayExtents(min, max); | |
1676 } | |
1677 | |
1678 void | |
1679 FlexiNoteLayer::setVerticalRangeToNoteRange(View *v) | |
1680 { | |
1681 float minf = std::numeric_limits<float>::max(); | |
1682 float maxf = 0; | |
1683 bool hasNotes = 0; | |
1684 for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); | |
1685 i != m_model->getPoints().end(); ++i) { | |
1686 hasNotes = 1; | |
1687 FlexiNote note = *i; | |
1688 if (note.value < minf) minf = note.value; | |
1689 if (note.value > maxf) maxf = note.value; | |
1690 } | |
1691 | |
1692 std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl; | |
1693 | |
1694 if (hasNotes) { | |
1695 v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); | |
1696 // MM: this is a hack because we rely on | |
1697 // * this layer being automatically aligned to layer 1 | |
1698 // * layer one is a log frequency layer. | |
1699 } | |
1700 } | |
1701 | |
1702 |