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