annotate layer/NoteLayer.cpp @ 1431:af824022bffd single-point

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