annotate layer/NoteLayer.cpp @ 312:6de6f78b13a1

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