comparison layer/NoteLayer.cpp @ 30:ea6fe8cfcdd5

* Add the Note layer for pianoroll-type display of note-type data * Complete the MIDI file importer (well, nearly -- it would be nice to be able to import the non-note data as other sorts of models, and that's not done yet). * Minor refactoring in RealTime etc
author Chris Cannam
date Fri, 10 Feb 2006 17:51:36 +0000
parents
children 1bdf285c4eac
comparison
equal deleted inserted replaced
29:9f55af9676b4 30:ea6fe8cfcdd5
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 A waveform viewer and audio annotation editor.
5 Chris Cannam, Queen Mary University of London, 2005-2006
6
7 This is experimental software. Not for distribution.
8 */
9
10 #include "NoteLayer.h"
11
12 #include "base/Model.h"
13 #include "base/RealTime.h"
14 #include "base/Profiler.h"
15 #include "base/Pitch.h"
16 #include "base/View.h"
17
18 #include "model/NoteModel.h"
19
20 #include <QPainter>
21 #include <QPainterPath>
22 #include <QMouseEvent>
23
24 #include <iostream>
25 #include <cmath>
26
27 NoteLayer::NoteLayer(View *w) :
28 Layer(w),
29 m_model(0),
30 m_editing(false),
31 m_originalPoint(0, 0.0, 0, tr("New Point")),
32 m_editingPoint(0, 0.0, 0, tr("New Point")),
33 m_editingCommand(0),
34 m_colour(Qt::black),
35 m_verticalScale(MinMaxRangeScale)
36 {
37 m_view->addLayer(this);
38 }
39
40 void
41 NoteLayer::setModel(NoteModel *model)
42 {
43 if (m_model == model) return;
44 m_model = model;
45
46 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
47 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
48 this, SIGNAL(modelChanged(size_t, size_t)));
49
50 connect(m_model, SIGNAL(completionChanged()),
51 this, SIGNAL(modelCompletionChanged()));
52
53 std::cerr << "NoteLayer::setModel(" << model << ")" << std::endl;
54
55 emit modelReplaced();
56 }
57
58 Layer::PropertyList
59 NoteLayer::getProperties() const
60 {
61 PropertyList list;
62 list.push_back(tr("Colour"));
63 list.push_back(tr("Vertical Scale"));
64 return list;
65 }
66
67 Layer::PropertyType
68 NoteLayer::getPropertyType(const PropertyName &) const
69 {
70 return ValueProperty;
71 }
72
73 int
74 NoteLayer::getPropertyRangeAndValue(const PropertyName &name,
75 int *min, int *max) const
76 {
77 //!!! factor this colour handling stuff out into a colour manager class
78
79 int deft = 0;
80
81 if (name == tr("Colour")) {
82
83 if (min) *min = 0;
84 if (max) *max = 5;
85
86 if (m_colour == Qt::black) deft = 0;
87 else if (m_colour == Qt::darkRed) deft = 1;
88 else if (m_colour == Qt::darkBlue) deft = 2;
89 else if (m_colour == Qt::darkGreen) deft = 3;
90 else if (m_colour == QColor(200, 50, 255)) deft = 4;
91 else if (m_colour == QColor(255, 150, 50)) deft = 5;
92
93 } else if (name == tr("Vertical Scale")) {
94
95 if (min) *min = 0;
96 if (max) *max = 2;
97
98 deft = int(m_verticalScale);
99
100 } else {
101
102 deft = Layer::getPropertyRangeAndValue(name, min, max);
103 }
104
105 return deft;
106 }
107
108 QString
109 NoteLayer::getPropertyValueLabel(const PropertyName &name,
110 int value) const
111 {
112 if (name == tr("Colour")) {
113 switch (value) {
114 default:
115 case 0: return tr("Black");
116 case 1: return tr("Red");
117 case 2: return tr("Blue");
118 case 3: return tr("Green");
119 case 4: return tr("Purple");
120 case 5: return tr("Orange");
121 }
122 } else if (name == tr("Vertical Scale")) {
123 switch (value) {
124 default:
125 case 0: return tr("Note Range In Use");
126 case 1: return tr("MIDI Note Range");
127 case 2: return tr("Frequency");
128 }
129 }
130 return tr("<unknown>");
131 }
132
133 void
134 NoteLayer::setProperty(const PropertyName &name, int value)
135 {
136 if (name == tr("Colour")) {
137 switch (value) {
138 default:
139 case 0: setBaseColour(Qt::black); break;
140 case 1: setBaseColour(Qt::darkRed); break;
141 case 2: setBaseColour(Qt::darkBlue); break;
142 case 3: setBaseColour(Qt::darkGreen); break;
143 case 4: setBaseColour(QColor(200, 50, 255)); break;
144 case 5: setBaseColour(QColor(255, 150, 50)); break;
145 }
146 } else if (name == tr("Vertical Scale")) {
147 setVerticalScale(VerticalScale(value));
148 }
149 }
150
151 void
152 NoteLayer::setBaseColour(QColor colour)
153 {
154 if (m_colour == colour) return;
155 m_colour = colour;
156 emit layerParametersChanged();
157 }
158
159 void
160 NoteLayer::setVerticalScale(VerticalScale scale)
161 {
162 if (m_verticalScale == scale) return;
163 m_verticalScale = scale;
164 emit layerParametersChanged();
165 }
166
167 bool
168 NoteLayer::isLayerScrollable() const
169 {
170 QPoint discard;
171 return !m_view->shouldIlluminateLocalFeatures(this, discard);
172 }
173
174 NoteModel::PointList
175 NoteLayer::getLocalPoints(int x) const
176 {
177 if (!m_model) return NoteModel::PointList();
178
179 long frame = getFrameForX(x);
180
181 NoteModel::PointList onPoints =
182 m_model->getPoints(frame);
183
184 if (!onPoints.empty()) {
185 return onPoints;
186 }
187
188 NoteModel::PointList prevPoints =
189 m_model->getPreviousPoints(frame);
190 NoteModel::PointList nextPoints =
191 m_model->getNextPoints(frame);
192
193 NoteModel::PointList usePoints = prevPoints;
194
195 if (prevPoints.empty()) {
196 usePoints = nextPoints;
197 } else if (prevPoints.begin()->frame < m_view->getStartFrame() &&
198 !(nextPoints.begin()->frame > m_view->getEndFrame())) {
199 usePoints = nextPoints;
200 } else if (nextPoints.begin()->frame - frame <
201 frame - prevPoints.begin()->frame) {
202 usePoints = nextPoints;
203 }
204
205 if (!usePoints.empty()) {
206 int fuzz = 2;
207 int px = getXForFrame(usePoints.begin()->frame);
208 if ((px > x && px - x > fuzz) ||
209 (px < x && x - px > fuzz + 1)) {
210 usePoints.clear();
211 }
212 }
213
214 return usePoints;
215 }
216
217 QString
218 NoteLayer::getFeatureDescription(QPoint &pos) const
219 {
220 int x = pos.x();
221
222 if (!m_model || !m_model->getSampleRate()) return "";
223
224 NoteModel::PointList points = getLocalPoints(x);
225
226 if (points.empty()) {
227 if (!m_model->isReady()) {
228 return tr("In progress");
229 } else {
230 return tr("No local points");
231 }
232 }
233
234 Note note(0);
235 NoteModel::PointList::iterator i;
236
237 for (i = points.begin(); i != points.end(); ++i) {
238
239 int y = getYForValue(i->value);
240 int h = 3;
241
242 if (m_model->getValueQuantization() != 0.0) {
243 h = y - getYForValue(i->value + m_model->getValueQuantization());
244 if (h < 3) h = 3;
245 }
246
247 if (pos.y() >= y - h && pos.y() <= y) {
248 note = *i;
249 break;
250 }
251 }
252
253 if (i == points.end()) return tr("No local points");
254
255 RealTime rt = RealTime::frame2RealTime(note.frame,
256 m_model->getSampleRate());
257 RealTime rd = RealTime::frame2RealTime(note.duration,
258 m_model->getSampleRate());
259
260 QString text;
261
262 if (note.label == "") {
263 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
264 .arg(rt.toText(true).c_str())
265 .arg(note.value)
266 .arg(rd.toText(true).c_str());
267 } else {
268 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
269 .arg(rt.toText(true).c_str())
270 .arg(note.value)
271 .arg(rd.toText(true).c_str())
272 .arg(note.label);
273 }
274
275 pos = QPoint(getXForFrame(note.frame),
276 getYForValue(note.value));
277 return text;
278 }
279
280 bool
281 NoteLayer::snapToFeatureFrame(int &frame,
282 size_t &resolution,
283 SnapType snap) const
284 {
285 if (!m_model) {
286 return Layer::snapToFeatureFrame(frame, resolution, snap);
287 }
288
289 resolution = m_model->getResolution();
290 NoteModel::PointList points;
291
292 if (snap == SnapNeighbouring) {
293
294 points = getLocalPoints(getXForFrame(frame));
295 if (points.empty()) return false;
296 frame = points.begin()->frame;
297 return true;
298 }
299
300 points = m_model->getPoints(frame, frame);
301 int snapped = frame;
302 bool found = false;
303
304 for (NoteModel::PointList::const_iterator i = points.begin();
305 i != points.end(); ++i) {
306
307 if (snap == SnapRight) {
308
309 if (i->frame > frame) {
310 snapped = i->frame;
311 found = true;
312 break;
313 }
314
315 } else if (snap == SnapLeft) {
316
317 if (i->frame <= frame) {
318 snapped = i->frame;
319 found = true; // don't break, as the next may be better
320 } else {
321 break;
322 }
323
324 } else { // nearest
325
326 NoteModel::PointList::const_iterator j = i;
327 ++j;
328
329 if (j == points.end()) {
330
331 snapped = i->frame;
332 found = true;
333 break;
334
335 } else if (j->frame >= frame) {
336
337 if (j->frame - frame < frame - i->frame) {
338 snapped = j->frame;
339 } else {
340 snapped = i->frame;
341 }
342 found = true;
343 break;
344 }
345 }
346 }
347
348 frame = snapped;
349 return found;
350 }
351
352 int
353 NoteLayer::getYForValue(float value) const
354 {
355 float min, max, h = m_view->height();
356
357 switch (m_verticalScale) {
358
359 case MIDIRangeScale:
360 min = 0.0;
361 max = 127.0;
362 break;
363
364 case MinMaxRangeScale:
365 min = m_model->getValueMinimum();
366 max = m_model->getValueMaximum();
367 break;
368
369 case FrequencyScale:
370 std::cerr << "FrequencyScale: value in = " << value << std::endl;
371 min = m_model->getValueMinimum();
372 min = Pitch::getFrequencyForPitch(lrintf(min), min - lrintf(min));
373 max = m_model->getValueMaximum();
374 max = Pitch::getFrequencyForPitch(lrintf(max), max - lrintf(max));
375 value = Pitch::getFrequencyForPitch(lrintf(value), value - lrintf(value));
376 std::cerr << "FrequencyScale: min = " << min << ", max = " << max << ", value = " << value << std::endl;
377 break;
378 }
379
380 if (max < min) max = min;
381 max = max + 1.0;
382
383 return int(h - ((value - min) * h) / (max - min)) - 1;
384 }
385
386 float
387 NoteLayer::getValueForY(int y) const
388 {
389 float min = m_model->getValueMinimum();
390 float max = m_model->getValueMaximum();
391 if (max == min) max = min + 1.0;
392
393 int h = m_view->height();
394
395 return min + (float(h - y) * float(max - min)) / h;
396 }
397
398 void
399 NoteLayer::paint(QPainter &paint, QRect rect) const
400 {
401 if (!m_model || !m_model->isOK()) return;
402
403 int sampleRate = m_model->getSampleRate();
404 if (!sampleRate) return;
405
406 // Profiler profiler("NoteLayer::paint", true);
407
408 int x0 = rect.left(), x1 = rect.right();
409 long frame0 = getFrameForX(x0);
410 long frame1 = getFrameForX(x1);
411
412 NoteModel::PointList points(m_model->getPoints(frame0, frame1));
413 if (points.empty()) return;
414
415 paint.setPen(m_colour);
416
417 QColor brushColour(m_colour);
418 brushColour.setAlpha(80);
419
420 // std::cerr << "NoteLayer::paint: resolution is "
421 // << m_model->getResolution() << " frames" << std::endl;
422
423 float min = m_model->getValueMinimum();
424 float max = m_model->getValueMaximum();
425 if (max == min) max = min + 1.0;
426
427 int origin = int(nearbyint(m_view->height() -
428 (-min * m_view->height()) / (max - min)));
429
430 QPoint localPos;
431 long illuminateFrame = -1;
432
433 if (m_view->shouldIlluminateLocalFeatures(this, localPos)) {
434 NoteModel::PointList localPoints =
435 getLocalPoints(localPos.x());
436 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
437 }
438
439 paint.save();
440 paint.setRenderHint(QPainter::Antialiasing, false);
441
442 for (NoteModel::PointList::const_iterator i = points.begin();
443 i != points.end(); ++i) {
444
445 const NoteModel::Point &p(*i);
446
447 int x = getXForFrame(p.frame);
448 int y = getYForValue(p.value);
449 int w = getXForFrame(p.frame + p.duration) - x;
450 int h = 3;
451
452 if (m_model->getValueQuantization() != 0.0) {
453 h = y - getYForValue(p.value + m_model->getValueQuantization());
454 if (h < 3) h = 3;
455 }
456
457 if (w < 1) w = 1;
458 paint.setPen(m_colour);
459 paint.setBrush(brushColour);
460
461 if (illuminateFrame == p.frame) {
462 if (localPos.y() >= y - h && localPos.y() < y) {
463 paint.setPen(Qt::black);//!!!
464 paint.setBrush(Qt::black);//!!!
465 }
466 }
467
468 paint.drawRect(x, y - h, w, h);
469
470 /// if (p.label != "") {
471 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
472 /// }
473 }
474
475 paint.restore();
476 }
477
478 void
479 NoteLayer::drawStart(QMouseEvent *e)
480 {
481 std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
482
483 if (!m_model) return;
484
485 long frame = getFrameForX(e->x());
486 if (frame < 0) frame = 0;
487 frame = frame / m_model->getResolution() * m_model->getResolution();
488
489 float value = getValueForY(e->y());
490
491 m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point"));
492 m_originalPoint = m_editingPoint;
493
494 if (m_editingCommand) m_editingCommand->finish();
495 m_editingCommand = new NoteModel::EditCommand(m_model,
496 tr("Draw Point"));
497 m_editingCommand->addPoint(m_editingPoint);
498
499 m_editing = true;
500 }
501
502 void
503 NoteLayer::drawDrag(QMouseEvent *e)
504 {
505 std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
506
507 if (!m_model || !m_editing) return;
508
509 long frame = getFrameForX(e->x());
510 if (frame < 0) frame = 0;
511 frame = frame / m_model->getResolution() * m_model->getResolution();
512
513 float value = getValueForY(e->y());
514
515 m_editingCommand->deletePoint(m_editingPoint);
516 m_editingPoint.frame = frame;
517 m_editingPoint.value = value;
518 m_editingCommand->addPoint(m_editingPoint);
519 }
520
521 void
522 NoteLayer::drawEnd(QMouseEvent *e)
523 {
524 std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
525 if (!m_model || !m_editing) return;
526 m_editingCommand->finish();
527 m_editingCommand = 0;
528 m_editing = false;
529 }
530
531 void
532 NoteLayer::editStart(QMouseEvent *e)
533 {
534 std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
535
536 if (!m_model) return;
537
538 NoteModel::PointList points = getLocalPoints(e->x());
539 if (points.empty()) return;
540
541 m_editingPoint = *points.begin();
542 m_originalPoint = m_editingPoint;
543
544 if (m_editingCommand) {
545 m_editingCommand->finish();
546 m_editingCommand = 0;
547 }
548
549 m_editing = true;
550 }
551
552 void
553 NoteLayer::editDrag(QMouseEvent *e)
554 {
555 std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
556
557 if (!m_model || !m_editing) return;
558
559 long frame = getFrameForX(e->x());
560 if (frame < 0) frame = 0;
561 frame = frame / m_model->getResolution() * m_model->getResolution();
562
563 float value = getValueForY(e->y());
564
565 if (!m_editingCommand) {
566 m_editingCommand = new NoteModel::EditCommand(m_model,
567 tr("Drag Point"));
568 }
569
570 m_editingCommand->deletePoint(m_editingPoint);
571 m_editingPoint.frame = frame;
572 m_editingPoint.value = value;
573 m_editingCommand->addPoint(m_editingPoint);
574 }
575
576 void
577 NoteLayer::editEnd(QMouseEvent *e)
578 {
579 std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
580 if (!m_model || !m_editing) return;
581
582 if (m_editingCommand) {
583
584 QString newName = m_editingCommand->getName();
585
586 if (m_editingPoint.frame != m_originalPoint.frame) {
587 if (m_editingPoint.value != m_originalPoint.value) {
588 newName = tr("Edit Point");
589 } else {
590 newName = tr("Relocate Point");
591 }
592 } else {
593 newName = tr("Change Point Value");
594 }
595
596 m_editingCommand->setName(newName);
597 m_editingCommand->finish();
598 }
599
600 m_editingCommand = 0;
601 m_editing = false;
602 }
603
604 QString
605 NoteLayer::toXmlString(QString indent, QString extraAttributes) const
606 {
607 return Layer::toXmlString(indent, extraAttributes +
608 QString(" colour=\"%1\" verticalScale=\"%2\"")
609 .arg(encodeColour(m_colour)).arg(m_verticalScale));
610 }
611
612 void
613 NoteLayer::setProperties(const QXmlAttributes &attributes)
614 {
615 QString colourSpec = attributes.value("colour");
616 if (colourSpec != "") {
617 QColor colour(colourSpec);
618 if (colour.isValid()) {
619 setBaseColour(QColor(colourSpec));
620 }
621 }
622
623 bool ok;
624 VerticalScale scale = (VerticalScale)
625 attributes.value("verticalScale").toInt(&ok);
626 if (ok) setVerticalScale(scale);
627 }
628
629