annotate layer/NoteLayer.cpp @ 1269:f2894944c6b8

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