annotate layer/NoteLayer.cpp @ 1496:d09345e578a7

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