annotate layer/NoteLayer.cpp @ 1605:ae2d5f8ff005

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