annotate layer/NoteLayer.cpp @ 432:8b2b497d302c

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