annotate layer/NoteLayer.cpp @ 333:e74b56f07c73

* Some work on correct alignment when moving panes during playback * Overhaul alignment for playback frame values (view manager now always refers to reference-timeline values, only the play source deals in playback model timeline values) * When making a selection, ensure the selection regions shown in other panes (and used for playback constraints if appropriate) are aligned correctly. This may be the coolest feature ever implemented in any program ever.
author Chris Cannam
date Thu, 22 Nov 2007 14:17:19 +0000
parents 984c1975f1ff
children 2f83b6e3b8ca 64e84e5efb76
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@30 2
Chris@30 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@59 7 This file copyright 2006 Chris Cannam.
Chris@30 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@30 14 */
Chris@30 15
Chris@30 16 #include "NoteLayer.h"
Chris@30 17
Chris@128 18 #include "data/model/Model.h"
Chris@30 19 #include "base/RealTime.h"
Chris@30 20 #include "base/Profiler.h"
Chris@30 21 #include "base/Pitch.h"
Chris@197 22 #include "base/LogRange.h"
Chris@287 23 #include "base/ColourDatabase.h"
Chris@128 24 #include "view/View.h"
Chris@30 25
Chris@128 26 #include "data/model/NoteModel.h"
Chris@30 27
Chris@70 28 #include "widgets/ItemEditDialog.h"
Chris@70 29
Chris@42 30 #include "SpectrogramLayer.h" // for optional frequency alignment
Chris@42 31
Chris@30 32 #include <QPainter>
Chris@30 33 #include <QPainterPath>
Chris@30 34 #include <QMouseEvent>
Chris@316 35 #include <QTextStream>
Chris@30 36
Chris@30 37 #include <iostream>
Chris@30 38 #include <cmath>
Chris@30 39
Chris@44 40 NoteLayer::NoteLayer() :
Chris@287 41 SingleColourLayer(),
Chris@30 42 m_model(0),
Chris@30 43 m_editing(false),
Chris@30 44 m_originalPoint(0, 0.0, 0, tr("New Point")),
Chris@30 45 m_editingPoint(0, 0.0, 0, tr("New Point")),
Chris@30 46 m_editingCommand(0),
Chris@101 47 m_verticalScale(AutoAlignScale)
Chris@30 48 {
Chris@44 49
Chris@30 50 }
Chris@30 51
Chris@30 52 void
Chris@30 53 NoteLayer::setModel(NoteModel *model)
Chris@30 54 {
Chris@30 55 if (m_model == model) return;
Chris@30 56 m_model = model;
Chris@30 57
Chris@320 58 connectSignals(m_model);
Chris@30 59
Chris@101 60 // std::cerr << "NoteLayer::setModel(" << model << ")" << std::endl;
Chris@30 61
Chris@30 62 emit modelReplaced();
Chris@30 63 }
Chris@30 64
Chris@30 65 Layer::PropertyList
Chris@30 66 NoteLayer::getProperties() const
Chris@30 67 {
Chris@287 68 PropertyList list = SingleColourLayer::getProperties();
Chris@87 69 list.push_back("Vertical Scale");
Chris@100 70 list.push_back("Scale Units");
Chris@30 71 return list;
Chris@30 72 }
Chris@30 73
Chris@87 74 QString
Chris@87 75 NoteLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 76 {
Chris@87 77 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@116 78 if (name == "Scale Units") return tr("Scale Units");
Chris@287 79 return SingleColourLayer::getPropertyLabel(name);
Chris@87 80 }
Chris@87 81
Chris@30 82 Layer::PropertyType
Chris@100 83 NoteLayer::getPropertyType(const PropertyName &name) const
Chris@30 84 {
Chris@100 85 if (name == "Scale Units") return UnitsProperty;
Chris@287 86 if (name == "Vertical Scale") return ValueProperty;
Chris@287 87 return SingleColourLayer::getPropertyType(name);
Chris@30 88 }
Chris@30 89
Chris@198 90 QString
Chris@198 91 NoteLayer::getPropertyGroupName(const PropertyName &name) const
Chris@198 92 {
Chris@198 93 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@198 94 return tr("Scale");
Chris@198 95 }
Chris@287 96 return SingleColourLayer::getPropertyGroupName(name);
Chris@198 97 }
Chris@198 98
Chris@30 99 int
Chris@30 100 NoteLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 101 int *min, int *max, int *deflt) const
Chris@30 102 {
Chris@216 103 int val = 0;
Chris@30 104
Chris@287 105 if (name == "Vertical Scale") {
Chris@30 106
Chris@30 107 if (min) *min = 0;
Chris@101 108 if (max) *max = 3;
Chris@216 109 if (deflt) *deflt = int(AutoAlignScale);
Chris@30 110
Chris@216 111 val = int(m_verticalScale);
Chris@30 112
Chris@100 113 } else if (name == "Scale Units") {
Chris@100 114
Chris@216 115 if (deflt) *deflt = 0;
Chris@100 116 if (m_model) {
Chris@216 117 val = UnitDatabase::getInstance()->getUnitId
Chris@100 118 (m_model->getScaleUnits());
Chris@100 119 }
Chris@100 120
Chris@30 121 } else {
Chris@216 122
Chris@287 123 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@30 124 }
Chris@30 125
Chris@216 126 return val;
Chris@30 127 }
Chris@30 128
Chris@30 129 QString
Chris@30 130 NoteLayer::getPropertyValueLabel(const PropertyName &name,
Chris@287 131 int value) const
Chris@30 132 {
Chris@287 133 if (name == "Vertical Scale") {
Chris@30 134 switch (value) {
Chris@30 135 default:
Chris@101 136 case 0: return tr("Auto-Align");
Chris@198 137 case 1: return tr("Linear");
Chris@198 138 case 2: return tr("Log");
Chris@198 139 case 3: return tr("MIDI Notes");
Chris@30 140 }
Chris@30 141 }
Chris@287 142 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@30 143 }
Chris@30 144
Chris@30 145 void
Chris@30 146 NoteLayer::setProperty(const PropertyName &name, int value)
Chris@30 147 {
Chris@287 148 if (name == "Vertical Scale") {
Chris@30 149 setVerticalScale(VerticalScale(value));
Chris@100 150 } else if (name == "Scale Units") {
Chris@100 151 if (m_model) {
Chris@100 152 m_model->setScaleUnits
Chris@100 153 (UnitDatabase::getInstance()->getUnitById(value));
Chris@100 154 emit modelChanged();
Chris@100 155 }
Chris@287 156 } else {
Chris@287 157 return SingleColourLayer::setProperty(name, value);
Chris@30 158 }
Chris@30 159 }
Chris@30 160
Chris@30 161 void
Chris@30 162 NoteLayer::setVerticalScale(VerticalScale scale)
Chris@30 163 {
Chris@30 164 if (m_verticalScale == scale) return;
Chris@30 165 m_verticalScale = scale;
Chris@30 166 emit layerParametersChanged();
Chris@30 167 }
Chris@30 168
Chris@30 169 bool
Chris@44 170 NoteLayer::isLayerScrollable(const View *v) const
Chris@30 171 {
Chris@30 172 QPoint discard;
Chris@44 173 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@30 174 }
Chris@30 175
Chris@79 176 bool
Chris@101 177 NoteLayer::shouldConvertMIDIToHz() const
Chris@101 178 {
Chris@101 179 QString unit = m_model->getScaleUnits();
Chris@101 180 return (unit != "Hz");
Chris@101 181 // if (unit == "" ||
Chris@101 182 // unit.startsWith("MIDI") ||
Chris@101 183 // unit.startsWith("midi")) return true;
Chris@101 184 // return false;
Chris@101 185 }
Chris@101 186
Chris@101 187 bool
Chris@101 188 NoteLayer::getValueExtents(float &min, float &max,
Chris@101 189 bool &logarithmic, QString &unit) const
Chris@79 190 {
Chris@79 191 if (!m_model) return false;
Chris@79 192 min = m_model->getValueMinimum();
Chris@79 193 max = m_model->getValueMaximum();
Chris@101 194
Chris@105 195 if (shouldConvertMIDIToHz()) {
Chris@105 196 unit = "Hz";
Chris@105 197 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@105 198 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@105 199 } else unit = m_model->getScaleUnits();
Chris@101 200
Chris@101 201 if (m_verticalScale == MIDIRangeScale ||
Chris@101 202 m_verticalScale == LogScale) logarithmic = true;
Chris@101 203
Chris@101 204 return true;
Chris@101 205 }
Chris@101 206
Chris@101 207 bool
Chris@101 208 NoteLayer::getDisplayExtents(float &min, float &max) const
Chris@101 209 {
Chris@101 210 if (!m_model || m_verticalScale == AutoAlignScale) return false;
Chris@101 211
Chris@101 212 if (m_verticalScale == MIDIRangeScale) {
Chris@101 213 min = Pitch::getFrequencyForPitch(0);
Chris@101 214 max = Pitch::getFrequencyForPitch(127);
Chris@101 215 return true;
Chris@101 216 }
Chris@101 217
Chris@101 218 min = m_model->getValueMinimum();
Chris@101 219 max = m_model->getValueMaximum();
Chris@101 220
Chris@101 221 if (shouldConvertMIDIToHz()) {
Chris@101 222 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 223 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 224 }
Chris@101 225
Chris@79 226 return true;
Chris@79 227 }
Chris@79 228
Chris@30 229 NoteModel::PointList
Chris@44 230 NoteLayer::getLocalPoints(View *v, int x) const
Chris@30 231 {
Chris@30 232 if (!m_model) return NoteModel::PointList();
Chris@30 233
Chris@44 234 long frame = v->getFrameForX(x);
Chris@30 235
Chris@30 236 NoteModel::PointList onPoints =
Chris@30 237 m_model->getPoints(frame);
Chris@30 238
Chris@30 239 if (!onPoints.empty()) {
Chris@30 240 return onPoints;
Chris@30 241 }
Chris@30 242
Chris@30 243 NoteModel::PointList prevPoints =
Chris@30 244 m_model->getPreviousPoints(frame);
Chris@30 245 NoteModel::PointList nextPoints =
Chris@30 246 m_model->getNextPoints(frame);
Chris@30 247
Chris@30 248 NoteModel::PointList usePoints = prevPoints;
Chris@30 249
Chris@30 250 if (prevPoints.empty()) {
Chris@30 251 usePoints = nextPoints;
Chris@248 252 } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
Chris@44 253 !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@30 254 usePoints = nextPoints;
Chris@248 255 } else if (long(nextPoints.begin()->frame) - frame <
Chris@248 256 frame - long(prevPoints.begin()->frame)) {
Chris@30 257 usePoints = nextPoints;
Chris@30 258 }
Chris@30 259
Chris@30 260 if (!usePoints.empty()) {
Chris@30 261 int fuzz = 2;
Chris@44 262 int px = v->getXForFrame(usePoints.begin()->frame);
Chris@30 263 if ((px > x && px - x > fuzz) ||
Chris@30 264 (px < x && x - px > fuzz + 1)) {
Chris@30 265 usePoints.clear();
Chris@30 266 }
Chris@30 267 }
Chris@30 268
Chris@30 269 return usePoints;
Chris@30 270 }
Chris@30 271
Chris@30 272 QString
Chris@44 273 NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@30 274 {
Chris@30 275 int x = pos.x();
Chris@30 276
Chris@30 277 if (!m_model || !m_model->getSampleRate()) return "";
Chris@30 278
Chris@44 279 NoteModel::PointList points = getLocalPoints(v, x);
Chris@30 280
Chris@30 281 if (points.empty()) {
Chris@30 282 if (!m_model->isReady()) {
Chris@30 283 return tr("In progress");
Chris@30 284 } else {
Chris@30 285 return tr("No local points");
Chris@30 286 }
Chris@30 287 }
Chris@30 288
Chris@30 289 Note note(0);
Chris@30 290 NoteModel::PointList::iterator i;
Chris@30 291
Chris@30 292 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 293
Chris@44 294 int y = getYForValue(v, i->value);
Chris@30 295 int h = 3;
Chris@30 296
Chris@30 297 if (m_model->getValueQuantization() != 0.0) {
Chris@44 298 h = y - getYForValue(v, i->value + m_model->getValueQuantization());
Chris@30 299 if (h < 3) h = 3;
Chris@30 300 }
Chris@30 301
Chris@30 302 if (pos.y() >= y - h && pos.y() <= y) {
Chris@30 303 note = *i;
Chris@30 304 break;
Chris@30 305 }
Chris@30 306 }
Chris@30 307
Chris@30 308 if (i == points.end()) return tr("No local points");
Chris@30 309
Chris@30 310 RealTime rt = RealTime::frame2RealTime(note.frame,
Chris@30 311 m_model->getSampleRate());
Chris@30 312 RealTime rd = RealTime::frame2RealTime(note.duration,
Chris@30 313 m_model->getSampleRate());
Chris@30 314
Chris@101 315 QString pitchText;
Chris@101 316
Chris@101 317 if (shouldConvertMIDIToHz()) {
Chris@101 318
Chris@101 319 int mnote = lrintf(note.value);
Chris@101 320 int cents = lrintf((note.value - mnote) * 100);
Chris@101 321 float freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@234 322 pitchText = tr("%1 (%2 Hz)")
Chris@101 323 .arg(Pitch::getPitchLabel(mnote, cents)).arg(freq);
Chris@101 324
Chris@101 325 } else if (m_model->getScaleUnits() == "Hz") {
Chris@101 326
Chris@234 327 pitchText = tr("%1 Hz (%2)")
Chris@101 328 .arg(note.value)
Chris@101 329 .arg(Pitch::getPitchLabelForFrequency(note.value));
Chris@101 330
Chris@101 331 } else {
Chris@234 332 pitchText = tr("%1 %2")
Chris@101 333 .arg(note.value).arg(m_model->getScaleUnits());
Chris@101 334 }
Chris@101 335
Chris@30 336 QString text;
Chris@30 337
Chris@30 338 if (note.label == "") {
Chris@30 339 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@30 340 .arg(rt.toText(true).c_str())
Chris@101 341 .arg(pitchText)
Chris@30 342 .arg(rd.toText(true).c_str());
Chris@30 343 } else {
Chris@30 344 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@30 345 .arg(rt.toText(true).c_str())
Chris@101 346 .arg(pitchText)
Chris@30 347 .arg(rd.toText(true).c_str())
Chris@30 348 .arg(note.label);
Chris@30 349 }
Chris@30 350
Chris@44 351 pos = QPoint(v->getXForFrame(note.frame),
Chris@44 352 getYForValue(v, note.value));
Chris@30 353 return text;
Chris@30 354 }
Chris@30 355
Chris@30 356 bool
Chris@44 357 NoteLayer::snapToFeatureFrame(View *v, int &frame,
Chris@44 358 size_t &resolution,
Chris@44 359 SnapType snap) const
Chris@30 360 {
Chris@30 361 if (!m_model) {
Chris@44 362 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@30 363 }
Chris@30 364
Chris@30 365 resolution = m_model->getResolution();
Chris@30 366 NoteModel::PointList points;
Chris@30 367
Chris@30 368 if (snap == SnapNeighbouring) {
Chris@30 369
Chris@44 370 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@30 371 if (points.empty()) return false;
Chris@30 372 frame = points.begin()->frame;
Chris@30 373 return true;
Chris@30 374 }
Chris@30 375
Chris@30 376 points = m_model->getPoints(frame, frame);
Chris@30 377 int snapped = frame;
Chris@30 378 bool found = false;
Chris@30 379
Chris@30 380 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 381 i != points.end(); ++i) {
Chris@30 382
Chris@30 383 if (snap == SnapRight) {
Chris@30 384
Chris@30 385 if (i->frame > frame) {
Chris@30 386 snapped = i->frame;
Chris@30 387 found = true;
Chris@30 388 break;
Chris@30 389 }
Chris@30 390
Chris@30 391 } else if (snap == SnapLeft) {
Chris@30 392
Chris@30 393 if (i->frame <= frame) {
Chris@30 394 snapped = i->frame;
Chris@30 395 found = true; // don't break, as the next may be better
Chris@30 396 } else {
Chris@30 397 break;
Chris@30 398 }
Chris@30 399
Chris@30 400 } else { // nearest
Chris@30 401
Chris@30 402 NoteModel::PointList::const_iterator j = i;
Chris@30 403 ++j;
Chris@30 404
Chris@30 405 if (j == points.end()) {
Chris@30 406
Chris@30 407 snapped = i->frame;
Chris@30 408 found = true;
Chris@30 409 break;
Chris@30 410
Chris@30 411 } else if (j->frame >= frame) {
Chris@30 412
Chris@30 413 if (j->frame - frame < frame - i->frame) {
Chris@30 414 snapped = j->frame;
Chris@30 415 } else {
Chris@30 416 snapped = i->frame;
Chris@30 417 }
Chris@30 418 found = true;
Chris@30 419 break;
Chris@30 420 }
Chris@30 421 }
Chris@30 422 }
Chris@30 423
Chris@30 424 frame = snapped;
Chris@30 425 return found;
Chris@30 426 }
Chris@30 427
Chris@101 428 void
Chris@101 429 NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
Chris@30 430 {
Chris@101 431 min = 0.0;
Chris@101 432 max = 0.0;
Chris@101 433 log = false;
Chris@42 434
Chris@101 435 QString queryUnits;
Chris@101 436 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@101 437 else queryUnits = m_model->getScaleUnits();
Chris@30 438
Chris@101 439 if (m_verticalScale == AutoAlignScale) {
Chris@30 440
Chris@101 441 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@30 442
Chris@101 443 min = m_model->getValueMinimum();
Chris@101 444 max = m_model->getValueMaximum();
Chris@42 445
Chris@101 446 if (shouldConvertMIDIToHz()) {
Chris@101 447 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 448 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 449 }
Chris@42 450
Chris@105 451 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 452
Chris@101 453 } else if (log) {
Chris@101 454
Chris@197 455 LogRange::mapRange(min, max);
Chris@105 456
Chris@105 457 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 458
Chris@101 459 }
Chris@101 460
Chris@101 461 } else {
Chris@101 462
Chris@101 463 min = m_model->getValueMinimum();
Chris@101 464 max = m_model->getValueMaximum();
Chris@101 465
Chris@101 466 if (m_verticalScale == MIDIRangeScale) {
Chris@101 467 min = Pitch::getFrequencyForPitch(0);
Chris@101 468 max = Pitch::getFrequencyForPitch(127);
Chris@101 469 } else if (shouldConvertMIDIToHz()) {
Chris@101 470 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 471 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 472 }
Chris@101 473
Chris@101 474 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@197 475 LogRange::mapRange(min, max);
Chris@101 476 log = true;
Chris@101 477 }
Chris@30 478 }
Chris@30 479
Chris@101 480 if (max == min) max = min + 1.0;
Chris@101 481 }
Chris@30 482
Chris@101 483 int
Chris@101 484 NoteLayer::getYForValue(View *v, float val) const
Chris@101 485 {
Chris@101 486 float min = 0.0, max = 0.0;
Chris@101 487 bool logarithmic = false;
Chris@101 488 int h = v->height();
Chris@101 489
Chris@101 490 getScaleExtents(v, min, max, logarithmic);
Chris@101 491
Chris@101 492 // std::cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << std::endl;
Chris@101 493
Chris@101 494 if (shouldConvertMIDIToHz()) {
Chris@101 495 val = Pitch::getFrequencyForPitch(lrintf(val),
Chris@101 496 lrintf((val - lrintf(val)) * 100));
Chris@101 497 // std::cerr << "shouldConvertMIDIToHz true, val now = " << val << std::endl;
Chris@101 498 }
Chris@101 499
Chris@101 500 if (logarithmic) {
Chris@197 501 val = LogRange::map(val);
Chris@101 502 // std::cerr << "logarithmic true, val now = " << val << std::endl;
Chris@101 503 }
Chris@101 504
Chris@101 505 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@101 506 // std::cerr << "y = " << y << std::endl;
Chris@101 507 return y;
Chris@30 508 }
Chris@30 509
Chris@30 510 float
Chris@44 511 NoteLayer::getValueForY(View *v, int y) const
Chris@30 512 {
Chris@101 513 float min = 0.0, max = 0.0;
Chris@101 514 bool logarithmic = false;
Chris@44 515 int h = v->height();
Chris@30 516
Chris@101 517 getScaleExtents(v, min, max, logarithmic);
Chris@101 518
Chris@101 519 float val = min + (float(h - y) * float(max - min)) / h;
Chris@101 520
Chris@101 521 if (logarithmic) {
Chris@197 522 val = powf(10.f, val);
Chris@101 523 }
Chris@101 524
Chris@101 525 if (shouldConvertMIDIToHz()) {
Chris@101 526 val = Pitch::getPitchForFrequency(val);
Chris@101 527 }
Chris@101 528
Chris@101 529 return val;
Chris@30 530 }
Chris@30 531
Chris@30 532 void
Chris@44 533 NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@30 534 {
Chris@30 535 if (!m_model || !m_model->isOK()) return;
Chris@30 536
Chris@30 537 int sampleRate = m_model->getSampleRate();
Chris@30 538 if (!sampleRate) return;
Chris@30 539
Chris@30 540 // Profiler profiler("NoteLayer::paint", true);
Chris@30 541
Chris@30 542 int x0 = rect.left(), x1 = rect.right();
Chris@44 543 long frame0 = v->getFrameForX(x0);
Chris@44 544 long frame1 = v->getFrameForX(x1);
Chris@30 545
Chris@30 546 NoteModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@30 547 if (points.empty()) return;
Chris@30 548
Chris@287 549 paint.setPen(getBaseQColor());
Chris@30 550
Chris@287 551 QColor brushColour(getBaseQColor());
Chris@30 552 brushColour.setAlpha(80);
Chris@30 553
Chris@30 554 // std::cerr << "NoteLayer::paint: resolution is "
Chris@30 555 // << m_model->getResolution() << " frames" << std::endl;
Chris@30 556
Chris@30 557 float min = m_model->getValueMinimum();
Chris@30 558 float max = m_model->getValueMaximum();
Chris@30 559 if (max == min) max = min + 1.0;
Chris@30 560
Chris@30 561 QPoint localPos;
Chris@30 562 long illuminateFrame = -1;
Chris@30 563
Chris@44 564 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@30 565 NoteModel::PointList localPoints =
Chris@44 566 getLocalPoints(v, localPos.x());
Chris@30 567 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@30 568 }
Chris@30 569
Chris@30 570 paint.save();
Chris@30 571 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 572
Chris@30 573 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 574 i != points.end(); ++i) {
Chris@30 575
Chris@30 576 const NoteModel::Point &p(*i);
Chris@30 577
Chris@44 578 int x = v->getXForFrame(p.frame);
Chris@44 579 int y = getYForValue(v, p.value);
Chris@44 580 int w = v->getXForFrame(p.frame + p.duration) - x;
Chris@30 581 int h = 3;
Chris@30 582
Chris@30 583 if (m_model->getValueQuantization() != 0.0) {
Chris@44 584 h = y - getYForValue(v, p.value + m_model->getValueQuantization());
Chris@30 585 if (h < 3) h = 3;
Chris@30 586 }
Chris@30 587
Chris@30 588 if (w < 1) w = 1;
Chris@287 589 paint.setPen(getBaseQColor());
Chris@30 590 paint.setBrush(brushColour);
Chris@30 591
Chris@30 592 if (illuminateFrame == p.frame) {
Chris@30 593 if (localPos.y() >= y - h && localPos.y() < y) {
Chris@287 594 paint.setPen(v->getForeground());
Chris@287 595 paint.setBrush(v->getForeground());
Chris@30 596 }
Chris@30 597 }
Chris@30 598
Chris@124 599 paint.drawRect(x, y - h/2, w, h);
Chris@30 600
Chris@30 601 /// if (p.label != "") {
Chris@30 602 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
Chris@30 603 /// }
Chris@30 604 }
Chris@30 605
Chris@30 606 paint.restore();
Chris@30 607 }
Chris@30 608
Chris@30 609 void
Chris@44 610 NoteLayer::drawStart(View *v, QMouseEvent *e)
Chris@30 611 {
Chris@101 612 // std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 613
Chris@30 614 if (!m_model) return;
Chris@30 615
Chris@44 616 long frame = v->getFrameForX(e->x());
Chris@30 617 if (frame < 0) frame = 0;
Chris@30 618 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 619
Chris@44 620 float value = getValueForY(v, e->y());
Chris@30 621
Chris@30 622 m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point"));
Chris@30 623 m_originalPoint = m_editingPoint;
Chris@30 624
Chris@30 625 if (m_editingCommand) m_editingCommand->finish();
Chris@30 626 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 627 tr("Draw Point"));
Chris@30 628 m_editingCommand->addPoint(m_editingPoint);
Chris@30 629
Chris@30 630 m_editing = true;
Chris@30 631 }
Chris@30 632
Chris@30 633 void
Chris@44 634 NoteLayer::drawDrag(View *v, QMouseEvent *e)
Chris@30 635 {
Chris@101 636 // std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 637
Chris@30 638 if (!m_model || !m_editing) return;
Chris@30 639
Chris@44 640 long frame = v->getFrameForX(e->x());
Chris@30 641 if (frame < 0) frame = 0;
Chris@30 642 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 643
Chris@101 644 float newValue = getValueForY(v, e->y());
Chris@101 645
Chris@101 646 long newFrame = m_editingPoint.frame;
Chris@101 647 long newDuration = frame - newFrame;
Chris@101 648 if (newDuration < 0) {
Chris@101 649 newFrame = frame;
Chris@101 650 newDuration = -newDuration;
Chris@101 651 } else if (newDuration == 0) {
Chris@101 652 newDuration = 1;
Chris@101 653 }
Chris@30 654
Chris@30 655 m_editingCommand->deletePoint(m_editingPoint);
Chris@101 656 m_editingPoint.frame = newFrame;
Chris@101 657 m_editingPoint.value = newValue;
Chris@101 658 m_editingPoint.duration = newDuration;
Chris@30 659 m_editingCommand->addPoint(m_editingPoint);
Chris@30 660 }
Chris@30 661
Chris@30 662 void
Chris@248 663 NoteLayer::drawEnd(View *, QMouseEvent *)
Chris@30 664 {
Chris@101 665 // std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 666 if (!m_model || !m_editing) return;
Chris@30 667 m_editingCommand->finish();
Chris@30 668 m_editingCommand = 0;
Chris@30 669 m_editing = false;
Chris@30 670 }
Chris@30 671
Chris@30 672 void
Chris@44 673 NoteLayer::editStart(View *v, QMouseEvent *e)
Chris@30 674 {
Chris@101 675 // std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 676
Chris@30 677 if (!m_model) return;
Chris@30 678
Chris@44 679 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@30 680 if (points.empty()) return;
Chris@30 681
Chris@30 682 m_editingPoint = *points.begin();
Chris@30 683 m_originalPoint = m_editingPoint;
Chris@30 684
Chris@30 685 if (m_editingCommand) {
Chris@30 686 m_editingCommand->finish();
Chris@30 687 m_editingCommand = 0;
Chris@30 688 }
Chris@30 689
Chris@30 690 m_editing = true;
Chris@30 691 }
Chris@30 692
Chris@30 693 void
Chris@44 694 NoteLayer::editDrag(View *v, QMouseEvent *e)
Chris@30 695 {
Chris@101 696 // std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 697
Chris@30 698 if (!m_model || !m_editing) return;
Chris@30 699
Chris@44 700 long frame = v->getFrameForX(e->x());
Chris@30 701 if (frame < 0) frame = 0;
Chris@30 702 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 703
Chris@44 704 float value = getValueForY(v, e->y());
Chris@30 705
Chris@30 706 if (!m_editingCommand) {
Chris@30 707 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 708 tr("Drag Point"));
Chris@30 709 }
Chris@30 710
Chris@30 711 m_editingCommand->deletePoint(m_editingPoint);
Chris@30 712 m_editingPoint.frame = frame;
Chris@30 713 m_editingPoint.value = value;
Chris@30 714 m_editingCommand->addPoint(m_editingPoint);
Chris@30 715 }
Chris@30 716
Chris@30 717 void
Chris@248 718 NoteLayer::editEnd(View *, QMouseEvent *)
Chris@30 719 {
Chris@101 720 // std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 721 if (!m_model || !m_editing) return;
Chris@30 722
Chris@30 723 if (m_editingCommand) {
Chris@30 724
Chris@30 725 QString newName = m_editingCommand->getName();
Chris@30 726
Chris@30 727 if (m_editingPoint.frame != m_originalPoint.frame) {
Chris@30 728 if (m_editingPoint.value != m_originalPoint.value) {
Chris@30 729 newName = tr("Edit Point");
Chris@30 730 } else {
Chris@30 731 newName = tr("Relocate Point");
Chris@30 732 }
Chris@30 733 } else {
Chris@30 734 newName = tr("Change Point Value");
Chris@30 735 }
Chris@30 736
Chris@30 737 m_editingCommand->setName(newName);
Chris@30 738 m_editingCommand->finish();
Chris@30 739 }
Chris@30 740
Chris@30 741 m_editingCommand = 0;
Chris@30 742 m_editing = false;
Chris@30 743 }
Chris@30 744
Chris@255 745 bool
Chris@70 746 NoteLayer::editOpen(View *v, QMouseEvent *e)
Chris@70 747 {
Chris@255 748 if (!m_model) return false;
Chris@70 749
Chris@70 750 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@255 751 if (points.empty()) return false;
Chris@70 752
Chris@70 753 NoteModel::Point note = *points.begin();
Chris@70 754
Chris@70 755 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 756 (m_model->getSampleRate(),
Chris@70 757 ItemEditDialog::ShowTime |
Chris@70 758 ItemEditDialog::ShowDuration |
Chris@70 759 ItemEditDialog::ShowValue |
Chris@100 760 ItemEditDialog::ShowText,
Chris@100 761 m_model->getScaleUnits());
Chris@70 762
Chris@70 763 dialog->setFrameTime(note.frame);
Chris@70 764 dialog->setValue(note.value);
Chris@70 765 dialog->setFrameDuration(note.duration);
Chris@70 766 dialog->setText(note.label);
Chris@70 767
Chris@70 768 if (dialog->exec() == QDialog::Accepted) {
Chris@70 769
Chris@70 770 NoteModel::Point newNote = note;
Chris@70 771 newNote.frame = dialog->getFrameTime();
Chris@70 772 newNote.value = dialog->getValue();
Chris@70 773 newNote.duration = dialog->getFrameDuration();
Chris@70 774 newNote.label = dialog->getText();
Chris@70 775
Chris@70 776 NoteModel::EditCommand *command = new NoteModel::EditCommand
Chris@70 777 (m_model, tr("Edit Point"));
Chris@70 778 command->deletePoint(note);
Chris@70 779 command->addPoint(newNote);
Chris@70 780 command->finish();
Chris@70 781 }
Chris@70 782
Chris@70 783 delete dialog;
Chris@255 784 return true;
Chris@70 785 }
Chris@70 786
Chris@70 787 void
Chris@43 788 NoteLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@43 789 {
Chris@99 790 if (!m_model) return;
Chris@99 791
Chris@43 792 NoteModel::EditCommand *command =
Chris@43 793 new NoteModel::EditCommand(m_model, tr("Drag Selection"));
Chris@43 794
Chris@43 795 NoteModel::PointList points =
Chris@43 796 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 797
Chris@43 798 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 799 i != points.end(); ++i) {
Chris@43 800
Chris@43 801 if (s.contains(i->frame)) {
Chris@43 802 NoteModel::Point newPoint(*i);
Chris@43 803 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43 804 command->deletePoint(*i);
Chris@43 805 command->addPoint(newPoint);
Chris@43 806 }
Chris@43 807 }
Chris@43 808
Chris@43 809 command->finish();
Chris@43 810 }
Chris@43 811
Chris@43 812 void
Chris@43 813 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 814 {
Chris@99 815 if (!m_model) return;
Chris@99 816
Chris@43 817 NoteModel::EditCommand *command =
Chris@43 818 new NoteModel::EditCommand(m_model, tr("Resize Selection"));
Chris@43 819
Chris@43 820 NoteModel::PointList points =
Chris@43 821 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 822
Chris@43 823 double ratio =
Chris@43 824 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43 825 double(s.getEndFrame() - s.getStartFrame());
Chris@43 826
Chris@43 827 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 828 i != points.end(); ++i) {
Chris@43 829
Chris@43 830 if (s.contains(i->frame)) {
Chris@43 831
Chris@43 832 double targetStart = i->frame;
Chris@43 833 targetStart = newSize.getStartFrame() +
Chris@43 834 double(targetStart - s.getStartFrame()) * ratio;
Chris@43 835
Chris@43 836 double targetEnd = i->frame + i->duration;
Chris@43 837 targetEnd = newSize.getStartFrame() +
Chris@43 838 double(targetEnd - s.getStartFrame()) * ratio;
Chris@43 839
Chris@43 840 NoteModel::Point newPoint(*i);
Chris@43 841 newPoint.frame = lrint(targetStart);
Chris@43 842 newPoint.duration = lrint(targetEnd - targetStart);
Chris@43 843 command->deletePoint(*i);
Chris@43 844 command->addPoint(newPoint);
Chris@43 845 }
Chris@43 846 }
Chris@43 847
Chris@43 848 command->finish();
Chris@43 849 }
Chris@43 850
Chris@76 851 void
Chris@76 852 NoteLayer::deleteSelection(Selection s)
Chris@76 853 {
Chris@99 854 if (!m_model) return;
Chris@99 855
Chris@76 856 NoteModel::EditCommand *command =
Chris@76 857 new NoteModel::EditCommand(m_model, tr("Delete Selected Points"));
Chris@76 858
Chris@76 859 NoteModel::PointList points =
Chris@76 860 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 861
Chris@76 862 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 863 i != points.end(); ++i) {
Chris@76 864
Chris@76 865 if (s.contains(i->frame)) {
Chris@76 866 command->deletePoint(*i);
Chris@76 867 }
Chris@76 868 }
Chris@76 869
Chris@76 870 command->finish();
Chris@76 871 }
Chris@76 872
Chris@76 873 void
Chris@76 874 NoteLayer::copy(Selection s, Clipboard &to)
Chris@76 875 {
Chris@99 876 if (!m_model) return;
Chris@99 877
Chris@76 878 NoteModel::PointList points =
Chris@76 879 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 880
Chris@76 881 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 882 i != points.end(); ++i) {
Chris@76 883 if (s.contains(i->frame)) {
Chris@83 884 Clipboard::Point point(i->frame, i->value, i->duration, i->label);
Chris@76 885 to.addPoint(point);
Chris@76 886 }
Chris@76 887 }
Chris@76 888 }
Chris@76 889
Chris@125 890 bool
Chris@248 891 NoteLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
Chris@76 892 {
Chris@125 893 if (!m_model) return false;
Chris@99 894
Chris@76 895 const Clipboard::PointList &points = from.getPoints();
Chris@76 896
Chris@76 897 NoteModel::EditCommand *command =
Chris@76 898 new NoteModel::EditCommand(m_model, tr("Paste"));
Chris@76 899
Chris@76 900 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76 901 i != points.end(); ++i) {
Chris@76 902
Chris@76 903 if (!i->haveFrame()) continue;
Chris@76 904 size_t frame = 0;
Chris@76 905 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
Chris@76 906 frame = i->getFrame() + frameOffset;
Chris@76 907 }
Chris@76 908 NoteModel::Point newPoint(frame);
Chris@76 909
Chris@76 910 if (i->haveLabel()) newPoint.label = i->getLabel();
Chris@76 911 if (i->haveValue()) newPoint.value = i->getValue();
Chris@76 912 else newPoint.value = (m_model->getValueMinimum() +
Chris@76 913 m_model->getValueMaximum()) / 2;
Chris@76 914 if (i->haveDuration()) newPoint.duration = i->getDuration();
Chris@125 915 else {
Chris@125 916 size_t nextFrame = frame;
Chris@125 917 Clipboard::PointList::const_iterator j = i;
Chris@125 918 for (; j != points.end(); ++j) {
Chris@125 919 if (!j->haveFrame()) continue;
Chris@125 920 if (j != i) break;
Chris@125 921 }
Chris@125 922 if (j != points.end()) {
Chris@125 923 nextFrame = j->getFrame();
Chris@125 924 }
Chris@125 925 if (nextFrame == frame) {
Chris@125 926 newPoint.duration = m_model->getResolution();
Chris@125 927 } else {
Chris@125 928 newPoint.duration = nextFrame - frame;
Chris@125 929 }
Chris@125 930 }
Chris@76 931
Chris@76 932 command->addPoint(newPoint);
Chris@76 933 }
Chris@76 934
Chris@76 935 command->finish();
Chris@125 936 return true;
Chris@76 937 }
Chris@76 938
Chris@287 939 int
Chris@287 940 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 941 {
Chris@287 942 impose = false;
Chris@287 943 return ColourDatabase::getInstance()->getColourIndex
Chris@287 944 (QString(darkbg ? "White" : "Black"));
Chris@287 945 }
Chris@287 946
Chris@316 947 void
Chris@316 948 NoteLayer::toXml(QTextStream &stream,
Chris@316 949 QString indent, QString extraAttributes) const
Chris@30 950 {
Chris@316 951 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@316 952 QString(" verticalScale=\"%1\"")
Chris@316 953 .arg(m_verticalScale));
Chris@30 954 }
Chris@30 955
Chris@30 956 void
Chris@30 957 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 958 {
Chris@287 959 SingleColourLayer::setProperties(attributes);
Chris@30 960
Chris@30 961 bool ok;
Chris@30 962 VerticalScale scale = (VerticalScale)
Chris@30 963 attributes.value("verticalScale").toInt(&ok);
Chris@30 964 if (ok) setVerticalScale(scale);
Chris@30 965 }
Chris@30 966
Chris@30 967