annotate layer/NoteLayer.cpp @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
parents e6362cf5ff1d
children 045063dcd2bc
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@30 717 int x0 = rect.left(), x1 = rect.right();
Chris@905 718 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@905 719 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@30 720
Chris@1471 721 EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
Chris@30 722 if (points.empty()) return;
Chris@30 723
Chris@287 724 paint.setPen(getBaseQColor());
Chris@30 725
Chris@287 726 QColor brushColour(getBaseQColor());
Chris@30 727 brushColour.setAlpha(80);
Chris@30 728
Chris@587 729 // SVDEBUG << "NoteLayer::paint: resolution is "
Chris@1471 730 // << model->getResolution() << " frames" << endl;
Chris@30 731
Chris@1551 732 double min = convertValueFromEventValue(model->getValueMinimum());
Chris@1551 733 double max = convertValueFromEventValue(model->getValueMaximum());
Chris@30 734 if (max == min) max = min + 1.0;
Chris@30 735
Chris@30 736 QPoint localPos;
Chris@1425 737 Event illuminatePoint;
Chris@551 738 bool shouldIlluminate = false;
Chris@30 739
Chris@1551 740 if (m_editing || m_editIsOpen) {
Chris@1551 741 shouldIlluminate = true;
Chris@1551 742 illuminatePoint = m_editingPoint;
Chris@1551 743 } else if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@551 744 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@551 745 illuminatePoint);
Chris@30 746 }
Chris@30 747
Chris@30 748 paint.save();
Chris@30 749 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 750
Chris@1425 751 for (EventVector::const_iterator i = points.begin();
Chris@1266 752 i != points.end(); ++i) {
Chris@30 753
Chris@1425 754 const Event &p(*i);
Chris@30 755
Chris@1425 756 int x = v->getXForFrame(p.getFrame());
Chris@1551 757 int y = getYForValue(v, valueOf(p));
Chris@1425 758 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@1266 759 int h = 3;
Chris@1266 760
Chris@1471 761 if (model->getValueQuantization() != 0.0) {
Chris@1551 762 h = y - getYForValue
Chris@1551 763 (v, convertValueFromEventValue
Chris@1551 764 (p.getValue() + model->getValueQuantization()));
Chris@1266 765 if (h < 3) h = 3;
Chris@1266 766 }
Chris@30 767
Chris@1266 768 if (w < 1) w = 1;
Chris@1266 769 paint.setPen(getBaseQColor());
Chris@1266 770 paint.setBrush(brushColour);
Chris@30 771
Chris@1425 772 if (shouldIlluminate && illuminatePoint == p) {
Chris@551 773
Chris@551 774 paint.setPen(v->getForeground());
Chris@551 775 paint.setBrush(v->getForeground());
Chris@551 776
Chris@1471 777 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1471 778 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1471 779 // which is too new for us
Chris@1471 780 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1471 781
Chris@1551 782 QString vlabel;
Chris@1551 783 if (m_modelUsesHz) {
Chris@1551 784 vlabel = QString("%1%2")
Chris@1551 785 .arg(p.getValue())
Chris@1551 786 .arg(model->getScaleUnits());
Chris@1551 787 } else {
Chris@1551 788 vlabel = QString("%1 %2")
Chris@1551 789 .arg(p.getValue())
Chris@1551 790 .arg(model->getScaleUnits());
Chris@1551 791 }
Chris@1551 792
Chris@1078 793 PaintAssistant::drawVisibleText(v, paint,
Chris@551 794 x - paint.fontMetrics().width(vlabel) - 2,
Chris@551 795 y + paint.fontMetrics().height()/2
Chris@551 796 - paint.fontMetrics().descent(),
Chris@1078 797 vlabel, PaintAssistant::OutlinedText);
Chris@551 798
Chris@551 799 QString hlabel = RealTime::frame2RealTime
Chris@1471 800 (p.getFrame(), model->getSampleRate()).toText(true).c_str();
Chris@1078 801 PaintAssistant::drawVisibleText(v, paint,
Chris@551 802 x,
Chris@551 803 y - h/2 - paint.fontMetrics().descent() - 2,
Chris@1078 804 hlabel, PaintAssistant::OutlinedText);
Chris@1266 805 }
Chris@1266 806
Chris@1266 807 paint.drawRect(x, y - h/2, w, h);
Chris@30 808 }
Chris@30 809
Chris@30 810 paint.restore();
Chris@30 811 }
Chris@30 812
Chris@692 813 int
Chris@918 814 NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@692 815 {
Chris@1471 816 if (m_model.isNone()) {
Chris@697 817 return 0;
Chris@1315 818 }
Chris@1315 819
Chris@1315 820 if (shouldAutoAlign() && !valueExtentsMatchMine(v)) {
Chris@1315 821 return 0;
Chris@1315 822 }
Chris@1315 823
Chris@1551 824 if (m_verticalScale != LinearScale) {
Chris@1315 825 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
Chris@1315 826 } else {
Chris@1315 827 return LinearNumericalScale().getWidth(v, paint);
Chris@697 828 }
Chris@692 829 }
Chris@692 830
Chris@692 831 void
Chris@918 832 NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@692 833 {
Chris@1471 834 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 835 if (!model || model->isEmpty()) return;
Chris@701 836
Chris@701 837 QString unit;
Chris@905 838 double min, max;
Chris@701 839 bool logarithmic;
Chris@701 840
Chris@701 841 int w = getVerticalScaleWidth(v, false, paint);
Chris@918 842 int h = v->getPaintHeight();
Chris@701 843
Chris@701 844 getScaleExtents(v, min, max, logarithmic);
Chris@701 845
Chris@701 846 if (logarithmic) {
Chris@701 847 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 848 } else {
Chris@701 849 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 850 }
Chris@701 851
Chris@1551 852 if (logarithmic) {
Chris@697 853 PianoScale().paintPianoVertical
Chris@701 854 (v, paint, QRect(w - 10, 0, 10, h),
Chris@701 855 LogRange::unmap(min),
Chris@701 856 LogRange::unmap(max));
Chris@701 857 paint.drawLine(w, 0, w, h);
Chris@701 858 }
Chris@701 859
Chris@701 860 if (getScaleUnits() != "") {
Chris@701 861 int mw = w - 5;
Chris@701 862 paint.drawText(5,
Chris@701 863 5 + paint.fontMetrics().ascent(),
Chris@701 864 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 865 paint.fontMetrics(),
Chris@701 866 mw));
Chris@697 867 }
Chris@692 868 }
Chris@692 869
Chris@30 870 void
Chris@918 871 NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 872 {
Chris@587 873 // SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 874
Chris@1471 875 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 876 if (!model) return;
Chris@30 877
Chris@905 878 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 879 if (frame < 0) frame = 0;
Chris@1471 880 frame = frame / model->getResolution() * model->getResolution();
Chris@30 881
Chris@905 882 double value = getValueForY(v, e->y());
Chris@1551 883 float eventValue = convertValueToEventValue(value);
Chris@1551 884 eventValue = roundf(eventValue);
Chris@30 885
Chris@1551 886 m_editingPoint = Event(frame, eventValue, 0, 0.8f, tr("New Point"));
Chris@30 887 m_originalPoint = m_editingPoint;
Chris@30 888
Chris@376 889 if (m_editingCommand) finish(m_editingCommand);
Chris@1471 890 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@1425 891 m_editingCommand->add(m_editingPoint);
Chris@30 892
Chris@30 893 m_editing = true;
Chris@30 894 }
Chris@30 895
Chris@30 896 void
Chris@918 897 NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 898 {
Chris@587 899 // SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 900
Chris@1471 901 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 902 if (!model || !m_editing) return;
Chris@30 903
Chris@905 904 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 905 if (frame < 0) frame = 0;
Chris@1471 906 frame = frame / model->getResolution() * model->getResolution();
Chris@30 907
Chris@905 908 double newValue = getValueForY(v, e->y());
Chris@1551 909 float newEventValue = convertValueToEventValue(newValue);
Chris@1551 910 newEventValue = roundf(newEventValue);
Chris@101 911
Chris@1425 912 sv_frame_t newFrame = m_editingPoint.getFrame();
Chris@905 913 sv_frame_t newDuration = frame - newFrame;
Chris@101 914 if (newDuration < 0) {
Chris@101 915 newFrame = frame;
Chris@101 916 newDuration = -newDuration;
Chris@101 917 } else if (newDuration == 0) {
Chris@101 918 newDuration = 1;
Chris@101 919 }
Chris@30 920
Chris@1425 921 m_editingCommand->remove(m_editingPoint);
Chris@1425 922 m_editingPoint = m_editingPoint
Chris@1425 923 .withFrame(newFrame)
Chris@1551 924 .withDuration(newDuration)
Chris@1551 925 .withValue(newEventValue);
Chris@1425 926 m_editingCommand->add(m_editingPoint);
Chris@30 927 }
Chris@30 928
Chris@30 929 void
Chris@918 930 NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@30 931 {
Chris@587 932 // SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1471 933 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 934 if (!model || !m_editing) return;
Chris@376 935 finish(m_editingCommand);
Chris@1408 936 m_editingCommand = nullptr;
Chris@30 937 m_editing = false;
Chris@30 938 }
Chris@30 939
Chris@30 940 void
Chris@918 941 NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 942 {
Chris@1471 943 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 944 if (!model) return;
Chris@335 945
Chris@550 946 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 947
Chris@335 948 if (m_editingCommand) {
Chris@1266 949 finish(m_editingCommand);
Chris@1408 950 m_editingCommand = nullptr;
Chris@335 951 }
Chris@335 952
Chris@335 953 m_editing = true;
Chris@335 954 }
Chris@335 955
Chris@335 956 void
Chris@918 957 NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 958 {
Chris@335 959 }
Chris@335 960
Chris@335 961 void
Chris@918 962 NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 963 {
Chris@1471 964 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 965 if (!model || !m_editing) return;
Chris@335 966
Chris@335 967 m_editing = false;
Chris@335 968
Chris@1425 969 Event p(0);
Chris@550 970 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1425 971 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1425 972 p.getValue() != m_editingPoint.getValue()) return;
Chris@550 973
Chris@1471 974 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@335 975
Chris@1425 976 m_editingCommand->remove(m_editingPoint);
Chris@335 977
Chris@376 978 finish(m_editingCommand);
Chris@1408 979 m_editingCommand = nullptr;
Chris@335 980 m_editing = false;
Chris@335 981 }
Chris@335 982
Chris@335 983 void
Chris@918 984 NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 985 {
Chris@587 986 // SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 987
Chris@1471 988 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 989 if (!model) return;
Chris@30 990
Chris@550 991 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@30 992 m_originalPoint = m_editingPoint;
Chris@30 993
Chris@1425 994 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
Chris@1551 995 m_dragPointY = getYForValue(v, valueOf(m_editingPoint));
Chris@551 996
Chris@30 997 if (m_editingCommand) {
Chris@1266 998 finish(m_editingCommand);
Chris@1408 999 m_editingCommand = nullptr;
Chris@30 1000 }
Chris@30 1001
Chris@30 1002 m_editing = true;
Chris@551 1003 m_dragStartX = e->x();
Chris@551 1004 m_dragStartY = e->y();
Chris@30 1005 }
Chris@30 1006
Chris@30 1007 void
Chris@918 1008 NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1009 {
Chris@587 1010 // SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 1011
Chris@1471 1012 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1013 if (!model || !m_editing) return;
Chris@30 1014
Chris@551 1015 int xdist = e->x() - m_dragStartX;
Chris@551 1016 int ydist = e->y() - m_dragStartY;
Chris@551 1017 int newx = m_dragPointX + xdist;
Chris@551 1018 int newy = m_dragPointY + ydist;
Chris@551 1019
Chris@905 1020 sv_frame_t frame = v->getFrameForX(newx);
Chris@30 1021 if (frame < 0) frame = 0;
Chris@1471 1022 frame = frame / model->getResolution() * model->getResolution();
Chris@30 1023
Chris@1551 1024 double newValue = getValueForY(v, newy);
Chris@1551 1025 float newEventValue = convertValueToEventValue(newValue);
Chris@1551 1026 newEventValue = roundf(newEventValue);
Chris@30 1027
Chris@30 1028 if (!m_editingCommand) {
Chris@1471 1029 m_editingCommand = new ChangeEventsCommand
Chris@1471 1030 (m_model.untyped, tr("Drag Point"));
Chris@30 1031 }
Chris@30 1032
Chris@1425 1033 m_editingCommand->remove(m_editingPoint);
Chris@1425 1034 m_editingPoint = m_editingPoint
Chris@1425 1035 .withFrame(frame)
Chris@1551 1036 .withValue(newEventValue);
Chris@1425 1037 m_editingCommand->add(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@1471 1044 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1045 if (!model || !m_editing) return;
Chris@30 1046
Chris@30 1047 if (m_editingCommand) {
Chris@30 1048
Chris@1266 1049 QString newName = m_editingCommand->getName();
Chris@30 1050
Chris@1425 1051 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1425 1052 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 1053 newName = tr("Edit Point");
Chris@1266 1054 } else {
Chris@1266 1055 newName = tr("Relocate Point");
Chris@1266 1056 }
Chris@1266 1057 } else {
Chris@1266 1058 newName = tr("Change Point Value");
Chris@1266 1059 }
Chris@30 1060
Chris@1266 1061 m_editingCommand->setName(newName);
Chris@1266 1062 finish(m_editingCommand);
Chris@30 1063 }
Chris@30 1064
Chris@1408 1065 m_editingCommand = nullptr;
Chris@30 1066 m_editing = false;
Chris@30 1067 }
Chris@30 1068
Chris@255 1069 bool
Chris@918 1070 NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 1071 {
Chris@1471 1072 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1073 if (!model) return false;
Chris@70 1074
Chris@1425 1075 Event note(0);
Chris@550 1076 if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
Chris@550 1077
Chris@70 1078 ItemEditDialog *dialog = new ItemEditDialog
Chris@1471 1079 (model->getSampleRate(),
Chris@70 1080 ItemEditDialog::ShowTime |
Chris@70 1081 ItemEditDialog::ShowDuration |
Chris@70 1082 ItemEditDialog::ShowValue |
Chris@1515 1083 ItemEditDialog::ShowLevel |
Chris@100 1084 ItemEditDialog::ShowText,
Chris@701 1085 getScaleUnits());
Chris@70 1086
Chris@1425 1087 dialog->setFrameTime(note.getFrame());
Chris@1425 1088 dialog->setValue(note.getValue());
Chris@1425 1089 dialog->setFrameDuration(note.getDuration());
Chris@1425 1090 dialog->setText(note.getLabel());
Chris@70 1091
Chris@1422 1092 m_editingPoint = note;
Chris@1422 1093 m_editIsOpen = true;
Chris@1422 1094
Chris@70 1095 if (dialog->exec() == QDialog::Accepted) {
Chris@70 1096
Chris@1425 1097 Event newNote = note
Chris@1425 1098 .withFrame(dialog->getFrameTime())
Chris@1425 1099 .withValue(dialog->getValue())
Chris@1425 1100 .withDuration(dialog->getFrameDuration())
Chris@1425 1101 .withLabel(dialog->getText());
Chris@70 1102
Chris@1427 1103 ChangeEventsCommand *command = new ChangeEventsCommand
Chris@1471 1104 (m_model.untyped, tr("Edit Point"));
Chris@1425 1105 command->remove(note);
Chris@1425 1106 command->add(newNote);
Chris@376 1107 finish(command);
Chris@70 1108 }
Chris@70 1109
Chris@1422 1110 m_editingPoint = 0;
Chris@1422 1111 m_editIsOpen = false;
Chris@1422 1112
Chris@70 1113 delete dialog;
Chris@255 1114 return true;
Chris@70 1115 }
Chris@70 1116
Chris@70 1117 void
Chris@905 1118 NoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 1119 {
Chris@1471 1120 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1121 if (!model) return;
Chris@99 1122
Chris@1427 1123 ChangeEventsCommand *command =
Chris@1471 1124 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 1125
Chris@1425 1126 EventVector points =
Chris@1471 1127 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1128
Chris@1425 1129 for (Event p: points) {
Chris@1425 1130 command->remove(p);
Chris@1425 1131 Event moved = p.withFrame(p.getFrame() +
Chris@1425 1132 newStartFrame - s.getStartFrame());
Chris@1425 1133 command->add(moved);
Chris@43 1134 }
Chris@43 1135
Chris@376 1136 finish(command);
Chris@43 1137 }
Chris@43 1138
Chris@43 1139 void
Chris@43 1140 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 1141 {
Chris@1471 1142 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1143 if (!model || !s.getDuration()) return;
Chris@99 1144
Chris@1427 1145 ChangeEventsCommand *command =
Chris@1471 1146 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 1147
Chris@1425 1148 EventVector points =
Chris@1471 1149 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1150
Chris@1425 1151 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1425 1152 double oldStart = double(s.getStartFrame());
Chris@1425 1153 double newStart = double(newSize.getStartFrame());
Chris@1425 1154
Chris@1425 1155 for (Event p: points) {
Chris@43 1156
Chris@1425 1157 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@1425 1158 double newDuration = double(p.getDuration()) * ratio;
Chris@43 1159
Chris@1425 1160 Event newPoint = p
Chris@1425 1161 .withFrame(lrint(newFrame))
Chris@1425 1162 .withDuration(lrint(newDuration));
Chris@1425 1163 command->remove(p);
Chris@1425 1164 command->add(newPoint);
Chris@43 1165 }
Chris@43 1166
Chris@376 1167 finish(command);
Chris@43 1168 }
Chris@43 1169
Chris@76 1170 void
Chris@76 1171 NoteLayer::deleteSelection(Selection s)
Chris@76 1172 {
Chris@1471 1173 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1174 if (!model) return;
Chris@99 1175
Chris@1427 1176 ChangeEventsCommand *command =
Chris@1471 1177 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
Chris@76 1178
Chris@1425 1179 EventVector points =
Chris@1471 1180 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1181
Chris@1425 1182 for (Event p: points) {
Chris@1425 1183 command->remove(p);
Chris@76 1184 }
Chris@76 1185
Chris@376 1186 finish(command);
Chris@76 1187 }
Chris@76 1188
Chris@76 1189 void
Chris@918 1190 NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 1191 {
Chris@1471 1192 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1193 if (!model) return;
Chris@99 1194
Chris@1425 1195 EventVector points =
Chris@1471 1196 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1197
Chris@1425 1198 for (Event p: points) {
Chris@1425 1199 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 1200 }
Chris@76 1201 }
Chris@76 1202
Chris@125 1203 bool
Chris@1425 1204 NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1425 1205 sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@76 1206 {
Chris@1471 1207 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1208 if (!model) return false;
Chris@99 1209
Chris@1423 1210 const EventVector &points = from.getPoints();
Chris@76 1211
Chris@360 1212 bool realign = false;
Chris@360 1213
Chris@360 1214 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 1215
Chris@360 1216 QMessageBox::StandardButton button =
Chris@918 1217 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 1218 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 1219 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 1220 QMessageBox::Yes);
Chris@360 1221
Chris@360 1222 if (button == QMessageBox::Cancel) {
Chris@360 1223 return false;
Chris@360 1224 }
Chris@360 1225
Chris@360 1226 if (button == QMessageBox::Yes) {
Chris@360 1227 realign = true;
Chris@360 1228 }
Chris@360 1229 }
Chris@360 1230
Chris@1427 1231 ChangeEventsCommand *command =
Chris@1471 1232 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 1233
Chris@1423 1234 for (EventVector::const_iterator i = points.begin();
Chris@76 1235 i != points.end(); ++i) {
Chris@1425 1236
Chris@905 1237 sv_frame_t frame = 0;
Chris@360 1238
Chris@360 1239 if (!realign) {
Chris@360 1240
Chris@360 1241 frame = i->getFrame();
Chris@360 1242
Chris@360 1243 } else {
Chris@360 1244
Chris@1423 1245 if (i->hasReferenceFrame()) {
Chris@360 1246 frame = i->getReferenceFrame();
Chris@360 1247 frame = alignFromReference(v, frame);
Chris@360 1248 } else {
Chris@360 1249 frame = i->getFrame();
Chris@360 1250 }
Chris@76 1251 }
Chris@360 1252
Chris@1533 1253 Event p = i->withFrame(frame);
Chris@1533 1254
Chris@1425 1255 Event newPoint = p;
Chris@1425 1256 if (!p.hasValue()) {
Chris@1471 1257 newPoint = newPoint.withValue((model->getValueMinimum() +
Chris@1471 1258 model->getValueMaximum()) / 2);
Chris@1425 1259 }
Chris@1425 1260 if (!p.hasDuration()) {
Chris@905 1261 sv_frame_t nextFrame = frame;
Chris@1423 1262 EventVector::const_iterator j = i;
Chris@125 1263 for (; j != points.end(); ++j) {
Chris@125 1264 if (j != i) break;
Chris@125 1265 }
Chris@125 1266 if (j != points.end()) {
Chris@125 1267 nextFrame = j->getFrame();
Chris@125 1268 }
Chris@125 1269 if (nextFrame == frame) {
Chris@1471 1270 newPoint = newPoint.withDuration(model->getResolution());
Chris@125 1271 } else {
Chris@1425 1272 newPoint = newPoint.withDuration(nextFrame - frame);
Chris@125 1273 }
Chris@125 1274 }
Chris@76 1275
Chris@1425 1276 command->add(newPoint);
Chris@76 1277 }
Chris@76 1278
Chris@376 1279 finish(command);
Chris@125 1280 return true;
Chris@76 1281 }
Chris@76 1282
Chris@507 1283 void
Chris@905 1284 NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
Chris@507 1285 {
Chris@1551 1286 double value = Pitch::getFrequencyForPitch(pitch);
Chris@1551 1287 float eventValue = convertValueToEventValue(value);
Chris@1551 1288 m_pendingNoteOns.insert(Event(frame, eventValue, 0,
Chris@1425 1289 float(velocity) / 127.f, QString()));
Chris@507 1290 }
Chris@507 1291
Chris@507 1292 void
Chris@905 1293 NoteLayer::addNoteOff(sv_frame_t frame, int pitch)
Chris@507 1294 {
Chris@1471 1295 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1471 1296
Chris@507 1297 for (NoteSet::iterator i = m_pendingNoteOns.begin();
Chris@507 1298 i != m_pendingNoteOns.end(); ++i) {
Chris@1425 1299
Chris@1425 1300 Event p = *i;
Chris@1551 1301 double value = valueOf(p);
Chris@1551 1302 int eventPitch = Pitch::getPitchForFrequency(value);
Chris@1425 1303
Chris@1551 1304 if (eventPitch == pitch) {
Chris@507 1305 m_pendingNoteOns.erase(i);
Chris@1425 1306 Event note = p.withDuration(frame - p.getFrame());
Chris@1471 1307 if (model) {
Chris@1427 1308 ChangeEventsCommand *c = new ChangeEventsCommand
Chris@1471 1309 (m_model.untyped, tr("Record Note"));
Chris@1425 1310 c->add(note);
Chris@507 1311 // execute and bundle:
Chris@507 1312 CommandHistory::getInstance()->addCommand(c, true, true);
Chris@507 1313 }
Chris@507 1314 break;
Chris@507 1315 }
Chris@507 1316 }
Chris@507 1317 }
Chris@507 1318
Chris@507 1319 void
Chris@507 1320 NoteLayer::abandonNoteOns()
Chris@507 1321 {
Chris@507 1322 m_pendingNoteOns.clear();
Chris@507 1323 }
Chris@507 1324
Chris@287 1325 int
Chris@287 1326 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 1327 {
Chris@287 1328 impose = false;
Chris@287 1329 return ColourDatabase::getInstance()->getColourIndex
Chris@287 1330 (QString(darkbg ? "White" : "Black"));
Chris@287 1331 }
Chris@287 1332
Chris@316 1333 void
Chris@316 1334 NoteLayer::toXml(QTextStream &stream,
Chris@316 1335 QString indent, QString extraAttributes) const
Chris@30 1336 {
Chris@316 1337 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@445 1338 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
Chris@445 1339 .arg(m_verticalScale)
Chris@445 1340 .arg(m_scaleMinimum)
Chris@445 1341 .arg(m_scaleMaximum));
Chris@30 1342 }
Chris@30 1343
Chris@30 1344 void
Chris@30 1345 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 1346 {
Chris@287 1347 SingleColourLayer::setProperties(attributes);
Chris@30 1348
Chris@445 1349 bool ok, alsoOk;
Chris@30 1350 VerticalScale scale = (VerticalScale)
Chris@1266 1351 attributes.value("verticalScale").toInt(&ok);
Chris@30 1352 if (ok) setVerticalScale(scale);
Chris@445 1353
Chris@445 1354 float min = attributes.value("scaleMinimum").toFloat(&ok);
Chris@445 1355 float max = attributes.value("scaleMaximum").toFloat(&alsoOk);
Chris@667 1356 if (ok && alsoOk && min != max) setDisplayExtents(min, max);
Chris@30 1357 }
Chris@30 1358
Chris@30 1359