annotate layer/NoteLayer.cpp @ 1447:8afea53332f3 single-point

Add option to make pane sizes auto-resize-only (i.e. remove user control via a splitter); also place alignment views above panes instead of below, meaning the extra bit of space that we currently have for the pane without one at least goes to the primary pane
author Chris Cannam
date Tue, 30 Apr 2019 15:53:21 +0100
parents 5b9692768beb
children 696e569ff21b
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@1432 537 // SnapLeft / SnapRight: return frame of nearest feature in that
Chris@1432 538 // direction no matter how far away
Chris@1432 539 //
Chris@1432 540 // SnapNeighbouring: return frame of feature that would be used in
Chris@1432 541 // an editing operation, i.e. closest feature in either direction
Chris@1432 542 // but only if it is "close enough"
Chris@1432 543
Chris@30 544 resolution = m_model->getResolution();
Chris@30 545
Chris@30 546 if (snap == SnapNeighbouring) {
Chris@1432 547 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
Chris@1266 548 if (points.empty()) return false;
Chris@1424 549 frame = points.begin()->getFrame();
Chris@1266 550 return true;
Chris@30 551 }
Chris@30 552
Chris@1432 553 Event e;
Chris@1432 554 if (m_model->getNearestEventMatching
Chris@1432 555 (frame,
Chris@1432 556 [](Event) { return true; },
Chris@1432 557 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1432 558 e)) {
Chris@1432 559 frame = e.getFrame();
Chris@1432 560 return true;
Chris@30 561 }
Chris@30 562
Chris@1432 563 return false;
Chris@30 564 }
Chris@30 565
Chris@101 566 void
Chris@918 567 NoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
Chris@30 568 {
Chris@101 569 min = 0.0;
Chris@101 570 max = 0.0;
Chris@101 571 log = false;
Chris@42 572
Chris@101 573 QString queryUnits;
Chris@101 574 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@701 575 else queryUnits = getScaleUnits();
Chris@30 576
Chris@439 577 if (shouldAutoAlign()) {
Chris@30 578
Chris@101 579 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@30 580
Chris@101 581 min = m_model->getValueMinimum();
Chris@101 582 max = m_model->getValueMaximum();
Chris@42 583
Chris@101 584 if (shouldConvertMIDIToHz()) {
Chris@905 585 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@905 586 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 587 }
Chris@42 588
Chris@665 589 #ifdef DEBUG_NOTE_LAYER
Chris@682 590 cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@665 591 #endif
Chris@105 592
Chris@101 593 } else if (log) {
Chris@101 594
Chris@197 595 LogRange::mapRange(min, max);
Chris@105 596
Chris@665 597 #ifdef DEBUG_NOTE_LAYER
Chris@682 598 cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@665 599 #endif
Chris@105 600
Chris@101 601 }
Chris@101 602
Chris@101 603 } else {
Chris@101 604
Chris@439 605 getDisplayExtents(min, max);
Chris@101 606
Chris@101 607 if (m_verticalScale == MIDIRangeScale) {
Chris@101 608 min = Pitch::getFrequencyForPitch(0);
Chris@101 609 max = Pitch::getFrequencyForPitch(127);
Chris@101 610 } else if (shouldConvertMIDIToHz()) {
Chris@905 611 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@905 612 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 613 }
Chris@101 614
Chris@101 615 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@197 616 LogRange::mapRange(min, max);
Chris@101 617 log = true;
Chris@101 618 }
Chris@30 619 }
Chris@30 620
Chris@101 621 if (max == min) max = min + 1.0;
Chris@101 622 }
Chris@30 623
Chris@101 624 int
Chris@918 625 NoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
Chris@101 626 {
Chris@905 627 double min = 0.0, max = 0.0;
Chris@101 628 bool logarithmic = false;
Chris@918 629 int h = v->getPaintHeight();
Chris@101 630
Chris@101 631 getScaleExtents(v, min, max, logarithmic);
Chris@101 632
Chris@665 633 #ifdef DEBUG_NOTE_LAYER
Chris@682 634 cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
Chris@665 635 #endif
Chris@101 636
Chris@101 637 if (shouldConvertMIDIToHz()) {
Chris@905 638 val = Pitch::getFrequencyForPitch(int(lrint(val)),
Chris@905 639 int(lrint((val - rint(val)) * 100)));
Chris@665 640 #ifdef DEBUG_NOTE_LAYER
Chris@682 641 cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
Chris@665 642 #endif
Chris@101 643 }
Chris@101 644
Chris@101 645 if (logarithmic) {
Chris@197 646 val = LogRange::map(val);
Chris@665 647 #ifdef DEBUG_NOTE_LAYER
Chris@682 648 cerr << "logarithmic true, val now = " << val << endl;
Chris@665 649 #endif
Chris@101 650 }
Chris@101 651
Chris@101 652 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@665 653 #ifdef DEBUG_NOTE_LAYER
Chris@682 654 cerr << "y = " << y << endl;
Chris@665 655 #endif
Chris@101 656 return y;
Chris@30 657 }
Chris@30 658
Chris@905 659 double
Chris@918 660 NoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
Chris@30 661 {
Chris@905 662 double min = 0.0, max = 0.0;
Chris@101 663 bool logarithmic = false;
Chris@918 664 int h = v->getPaintHeight();
Chris@30 665
Chris@101 666 getScaleExtents(v, min, max, logarithmic);
Chris@101 667
Chris@905 668 double val = min + (double(h - y) * double(max - min)) / h;
Chris@101 669
Chris@101 670 if (logarithmic) {
Chris@905 671 val = pow(10.0, val);
Chris@101 672 }
Chris@101 673
Chris@101 674 if (shouldConvertMIDIToHz()) {
Chris@101 675 val = Pitch::getPitchForFrequency(val);
Chris@101 676 }
Chris@101 677
Chris@101 678 return val;
Chris@30 679 }
Chris@30 680
Chris@439 681 bool
Chris@439 682 NoteLayer::shouldAutoAlign() const
Chris@439 683 {
Chris@439 684 if (!m_model) return false;
Chris@439 685 return (m_verticalScale == AutoAlignScale);
Chris@439 686 }
Chris@439 687
Chris@30 688 void
Chris@916 689 NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@30 690 {
Chris@30 691 if (!m_model || !m_model->isOK()) return;
Chris@30 692
Chris@905 693 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@30 694 if (!sampleRate) return;
Chris@30 695
Chris@30 696 // Profiler profiler("NoteLayer::paint", true);
Chris@30 697
Chris@30 698 int x0 = rect.left(), x1 = rect.right();
Chris@905 699 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@905 700 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@30 701
Chris@1425 702 EventVector points(m_model->getEventsSpanning(frame0, frame1 - frame0));
Chris@30 703 if (points.empty()) return;
Chris@30 704
Chris@287 705 paint.setPen(getBaseQColor());
Chris@30 706
Chris@287 707 QColor brushColour(getBaseQColor());
Chris@30 708 brushColour.setAlpha(80);
Chris@30 709
Chris@587 710 // SVDEBUG << "NoteLayer::paint: resolution is "
Chris@1266 711 // << m_model->getResolution() << " frames" << endl;
Chris@30 712
Chris@905 713 double min = m_model->getValueMinimum();
Chris@905 714 double max = m_model->getValueMaximum();
Chris@30 715 if (max == min) max = min + 1.0;
Chris@30 716
Chris@30 717 QPoint localPos;
Chris@1425 718 Event illuminatePoint;
Chris@551 719 bool shouldIlluminate = false;
Chris@30 720
Chris@44 721 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@551 722 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@551 723 illuminatePoint);
Chris@1422 724 } else if (m_editIsOpen) {
Chris@1422 725 shouldIlluminate = true;
Chris@1422 726 illuminatePoint = m_editingPoint;
Chris@30 727 }
Chris@30 728
Chris@30 729 paint.save();
Chris@30 730 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 731
Chris@1425 732 for (EventVector::const_iterator i = points.begin();
Chris@1266 733 i != points.end(); ++i) {
Chris@30 734
Chris@1425 735 const Event &p(*i);
Chris@30 736
Chris@1425 737 int x = v->getXForFrame(p.getFrame());
Chris@1425 738 int y = getYForValue(v, p.getValue());
Chris@1425 739 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@1266 740 int h = 3;
Chris@1266 741
Chris@1266 742 if (m_model->getValueQuantization() != 0.0) {
Chris@1425 743 h = y - getYForValue(v, p.getValue() + m_model->getValueQuantization());
Chris@1266 744 if (h < 3) h = 3;
Chris@1266 745 }
Chris@30 746
Chris@1266 747 if (w < 1) w = 1;
Chris@1266 748 paint.setPen(getBaseQColor());
Chris@1266 749 paint.setBrush(brushColour);
Chris@30 750
Chris@1425 751 if (shouldIlluminate && illuminatePoint == p) {
Chris@551 752
Chris@551 753 paint.setPen(v->getForeground());
Chris@551 754 paint.setBrush(v->getForeground());
Chris@551 755
Chris@1425 756 QString vlabel = QString("%1%2").arg(p.getValue()).arg(getScaleUnits());
Chris@1078 757 PaintAssistant::drawVisibleText(v, paint,
Chris@551 758 x - paint.fontMetrics().width(vlabel) - 2,
Chris@551 759 y + paint.fontMetrics().height()/2
Chris@551 760 - paint.fontMetrics().descent(),
Chris@1078 761 vlabel, PaintAssistant::OutlinedText);
Chris@551 762
Chris@551 763 QString hlabel = RealTime::frame2RealTime
Chris@1425 764 (p.getFrame(), m_model->getSampleRate()).toText(true).c_str();
Chris@1078 765 PaintAssistant::drawVisibleText(v, paint,
Chris@551 766 x,
Chris@551 767 y - h/2 - paint.fontMetrics().descent() - 2,
Chris@1078 768 hlabel, PaintAssistant::OutlinedText);
Chris@1266 769 }
Chris@1266 770
Chris@1266 771 paint.drawRect(x, y - h/2, w, h);
Chris@30 772 }
Chris@30 773
Chris@30 774 paint.restore();
Chris@30 775 }
Chris@30 776
Chris@692 777 int
Chris@918 778 NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@692 779 {
Chris@1315 780 if (!m_model) {
Chris@697 781 return 0;
Chris@1315 782 }
Chris@1315 783
Chris@1315 784 if (shouldAutoAlign() && !valueExtentsMatchMine(v)) {
Chris@1315 785 return 0;
Chris@1315 786 }
Chris@1315 787
Chris@1315 788 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@1315 789 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
Chris@1315 790 } else {
Chris@1315 791 return LinearNumericalScale().getWidth(v, paint);
Chris@697 792 }
Chris@692 793 }
Chris@692 794
Chris@692 795 void
Chris@918 796 NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@692 797 {
Chris@1425 798 if (!m_model || m_model->isEmpty()) return;
Chris@701 799
Chris@701 800 QString unit;
Chris@905 801 double min, max;
Chris@701 802 bool logarithmic;
Chris@701 803
Chris@701 804 int w = getVerticalScaleWidth(v, false, paint);
Chris@918 805 int h = v->getPaintHeight();
Chris@701 806
Chris@701 807 getScaleExtents(v, min, max, logarithmic);
Chris@701 808
Chris@701 809 if (logarithmic) {
Chris@701 810 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 811 } else {
Chris@701 812 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 813 }
Chris@701 814
Chris@701 815 if (logarithmic && (getScaleUnits() == "Hz")) {
Chris@697 816 PianoScale().paintPianoVertical
Chris@701 817 (v, paint, QRect(w - 10, 0, 10, h),
Chris@701 818 LogRange::unmap(min),
Chris@701 819 LogRange::unmap(max));
Chris@701 820 paint.drawLine(w, 0, w, h);
Chris@701 821 }
Chris@701 822
Chris@701 823 if (getScaleUnits() != "") {
Chris@701 824 int mw = w - 5;
Chris@701 825 paint.drawText(5,
Chris@701 826 5 + paint.fontMetrics().ascent(),
Chris@701 827 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 828 paint.fontMetrics(),
Chris@701 829 mw));
Chris@697 830 }
Chris@692 831 }
Chris@692 832
Chris@30 833 void
Chris@918 834 NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 835 {
Chris@587 836 // SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 837
Chris@30 838 if (!m_model) return;
Chris@30 839
Chris@905 840 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 841 if (frame < 0) frame = 0;
Chris@30 842 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 843
Chris@905 844 double value = getValueForY(v, e->y());
Chris@30 845
Chris@1425 846 m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point"));
Chris@30 847 m_originalPoint = m_editingPoint;
Chris@30 848
Chris@376 849 if (m_editingCommand) finish(m_editingCommand);
Chris@1427 850 m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
Chris@1425 851 m_editingCommand->add(m_editingPoint);
Chris@30 852
Chris@30 853 m_editing = true;
Chris@30 854 }
Chris@30 855
Chris@30 856 void
Chris@918 857 NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 858 {
Chris@587 859 // SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 860
Chris@30 861 if (!m_model || !m_editing) return;
Chris@30 862
Chris@905 863 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 864 if (frame < 0) frame = 0;
Chris@30 865 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 866
Chris@905 867 double newValue = getValueForY(v, e->y());
Chris@101 868
Chris@1425 869 sv_frame_t newFrame = m_editingPoint.getFrame();
Chris@905 870 sv_frame_t newDuration = frame - newFrame;
Chris@101 871 if (newDuration < 0) {
Chris@101 872 newFrame = frame;
Chris@101 873 newDuration = -newDuration;
Chris@101 874 } else if (newDuration == 0) {
Chris@101 875 newDuration = 1;
Chris@101 876 }
Chris@30 877
Chris@1425 878 m_editingCommand->remove(m_editingPoint);
Chris@1425 879 m_editingPoint = m_editingPoint
Chris@1425 880 .withFrame(newFrame)
Chris@1425 881 .withValue(float(newValue))
Chris@1425 882 .withDuration(newDuration);
Chris@1425 883 m_editingCommand->add(m_editingPoint);
Chris@30 884 }
Chris@30 885
Chris@30 886 void
Chris@918 887 NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@30 888 {
Chris@587 889 // SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 890 if (!m_model || !m_editing) return;
Chris@376 891 finish(m_editingCommand);
Chris@1408 892 m_editingCommand = nullptr;
Chris@30 893 m_editing = false;
Chris@30 894 }
Chris@30 895
Chris@30 896 void
Chris@918 897 NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 898 {
Chris@335 899 if (!m_model) return;
Chris@335 900
Chris@550 901 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 902
Chris@335 903 if (m_editingCommand) {
Chris@1266 904 finish(m_editingCommand);
Chris@1408 905 m_editingCommand = nullptr;
Chris@335 906 }
Chris@335 907
Chris@335 908 m_editing = true;
Chris@335 909 }
Chris@335 910
Chris@335 911 void
Chris@918 912 NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 913 {
Chris@335 914 }
Chris@335 915
Chris@335 916 void
Chris@918 917 NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 918 {
Chris@335 919 if (!m_model || !m_editing) return;
Chris@335 920
Chris@335 921 m_editing = false;
Chris@335 922
Chris@1425 923 Event p(0);
Chris@550 924 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1425 925 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1425 926 p.getValue() != m_editingPoint.getValue()) return;
Chris@550 927
Chris@1427 928 m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
Chris@335 929
Chris@1425 930 m_editingCommand->remove(m_editingPoint);
Chris@335 931
Chris@376 932 finish(m_editingCommand);
Chris@1408 933 m_editingCommand = nullptr;
Chris@335 934 m_editing = false;
Chris@335 935 }
Chris@335 936
Chris@335 937 void
Chris@918 938 NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 939 {
Chris@587 940 // SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 941
Chris@30 942 if (!m_model) return;
Chris@30 943
Chris@550 944 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@30 945 m_originalPoint = m_editingPoint;
Chris@30 946
Chris@1425 947 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
Chris@1425 948 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
Chris@551 949
Chris@30 950 if (m_editingCommand) {
Chris@1266 951 finish(m_editingCommand);
Chris@1408 952 m_editingCommand = nullptr;
Chris@30 953 }
Chris@30 954
Chris@30 955 m_editing = true;
Chris@551 956 m_dragStartX = e->x();
Chris@551 957 m_dragStartY = e->y();
Chris@30 958 }
Chris@30 959
Chris@30 960 void
Chris@918 961 NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 962 {
Chris@587 963 // SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 964
Chris@30 965 if (!m_model || !m_editing) return;
Chris@30 966
Chris@551 967 int xdist = e->x() - m_dragStartX;
Chris@551 968 int ydist = e->y() - m_dragStartY;
Chris@551 969 int newx = m_dragPointX + xdist;
Chris@551 970 int newy = m_dragPointY + ydist;
Chris@551 971
Chris@905 972 sv_frame_t frame = v->getFrameForX(newx);
Chris@30 973 if (frame < 0) frame = 0;
Chris@30 974 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 975
Chris@905 976 double value = getValueForY(v, newy);
Chris@30 977
Chris@30 978 if (!m_editingCommand) {
Chris@1427 979 m_editingCommand = new ChangeEventsCommand(m_model,
Chris@1266 980 tr("Drag Point"));
Chris@30 981 }
Chris@30 982
Chris@1425 983 m_editingCommand->remove(m_editingPoint);
Chris@1425 984 m_editingPoint = m_editingPoint
Chris@1425 985 .withFrame(frame)
Chris@1425 986 .withValue(float(value));
Chris@1425 987 m_editingCommand->add(m_editingPoint);
Chris@30 988 }
Chris@30 989
Chris@30 990 void
Chris@918 991 NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@30 992 {
Chris@587 993 // SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 994 if (!m_model || !m_editing) return;
Chris@30 995
Chris@30 996 if (m_editingCommand) {
Chris@30 997
Chris@1266 998 QString newName = m_editingCommand->getName();
Chris@30 999
Chris@1425 1000 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1425 1001 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 1002 newName = tr("Edit Point");
Chris@1266 1003 } else {
Chris@1266 1004 newName = tr("Relocate Point");
Chris@1266 1005 }
Chris@1266 1006 } else {
Chris@1266 1007 newName = tr("Change Point Value");
Chris@1266 1008 }
Chris@30 1009
Chris@1266 1010 m_editingCommand->setName(newName);
Chris@1266 1011 finish(m_editingCommand);
Chris@30 1012 }
Chris@30 1013
Chris@1408 1014 m_editingCommand = nullptr;
Chris@30 1015 m_editing = false;
Chris@30 1016 }
Chris@30 1017
Chris@255 1018 bool
Chris@918 1019 NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 1020 {
Chris@255 1021 if (!m_model) return false;
Chris@70 1022
Chris@1425 1023 Event note(0);
Chris@550 1024 if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
Chris@550 1025
Chris@1425 1026 // Event note = *points.begin();
Chris@70 1027
Chris@70 1028 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 1029 (m_model->getSampleRate(),
Chris@70 1030 ItemEditDialog::ShowTime |
Chris@70 1031 ItemEditDialog::ShowDuration |
Chris@70 1032 ItemEditDialog::ShowValue |
Chris@100 1033 ItemEditDialog::ShowText,
Chris@701 1034 getScaleUnits());
Chris@70 1035
Chris@1425 1036 dialog->setFrameTime(note.getFrame());
Chris@1425 1037 dialog->setValue(note.getValue());
Chris@1425 1038 dialog->setFrameDuration(note.getDuration());
Chris@1425 1039 dialog->setText(note.getLabel());
Chris@70 1040
Chris@1422 1041 m_editingPoint = note;
Chris@1422 1042 m_editIsOpen = true;
Chris@1422 1043
Chris@70 1044 if (dialog->exec() == QDialog::Accepted) {
Chris@70 1045
Chris@1425 1046 Event newNote = note
Chris@1425 1047 .withFrame(dialog->getFrameTime())
Chris@1425 1048 .withValue(dialog->getValue())
Chris@1425 1049 .withDuration(dialog->getFrameDuration())
Chris@1425 1050 .withLabel(dialog->getText());
Chris@70 1051
Chris@1427 1052 ChangeEventsCommand *command = new ChangeEventsCommand
Chris@70 1053 (m_model, tr("Edit Point"));
Chris@1425 1054 command->remove(note);
Chris@1425 1055 command->add(newNote);
Chris@376 1056 finish(command);
Chris@70 1057 }
Chris@70 1058
Chris@1422 1059 m_editingPoint = 0;
Chris@1422 1060 m_editIsOpen = false;
Chris@1422 1061
Chris@70 1062 delete dialog;
Chris@255 1063 return true;
Chris@70 1064 }
Chris@70 1065
Chris@70 1066 void
Chris@905 1067 NoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 1068 {
Chris@99 1069 if (!m_model) return;
Chris@99 1070
Chris@1427 1071 ChangeEventsCommand *command =
Chris@1427 1072 new ChangeEventsCommand(m_model, tr("Drag Selection"));
Chris@43 1073
Chris@1425 1074 EventVector points =
Chris@1425 1075 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1076
Chris@1425 1077 for (Event p: points) {
Chris@1425 1078 command->remove(p);
Chris@1425 1079 Event moved = p.withFrame(p.getFrame() +
Chris@1425 1080 newStartFrame - s.getStartFrame());
Chris@1425 1081 command->add(moved);
Chris@43 1082 }
Chris@43 1083
Chris@376 1084 finish(command);
Chris@43 1085 }
Chris@43 1086
Chris@43 1087 void
Chris@43 1088 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 1089 {
Chris@1425 1090 if (!m_model || !s.getDuration()) return;
Chris@99 1091
Chris@1427 1092 ChangeEventsCommand *command =
Chris@1427 1093 new ChangeEventsCommand(m_model, tr("Resize Selection"));
Chris@43 1094
Chris@1425 1095 EventVector points =
Chris@1425 1096 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1097
Chris@1425 1098 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1425 1099 double oldStart = double(s.getStartFrame());
Chris@1425 1100 double newStart = double(newSize.getStartFrame());
Chris@1425 1101
Chris@1425 1102 for (Event p: points) {
Chris@43 1103
Chris@1425 1104 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@1425 1105 double newDuration = double(p.getDuration()) * ratio;
Chris@43 1106
Chris@1425 1107 Event newPoint = p
Chris@1425 1108 .withFrame(lrint(newFrame))
Chris@1425 1109 .withDuration(lrint(newDuration));
Chris@1425 1110 command->remove(p);
Chris@1425 1111 command->add(newPoint);
Chris@43 1112 }
Chris@43 1113
Chris@376 1114 finish(command);
Chris@43 1115 }
Chris@43 1116
Chris@76 1117 void
Chris@76 1118 NoteLayer::deleteSelection(Selection s)
Chris@76 1119 {
Chris@99 1120 if (!m_model) return;
Chris@99 1121
Chris@1427 1122 ChangeEventsCommand *command =
Chris@1427 1123 new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
Chris@76 1124
Chris@1425 1125 EventVector points =
Chris@1425 1126 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1127
Chris@1425 1128 for (Event p: points) {
Chris@1425 1129 command->remove(p);
Chris@76 1130 }
Chris@76 1131
Chris@376 1132 finish(command);
Chris@76 1133 }
Chris@76 1134
Chris@76 1135 void
Chris@918 1136 NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 1137 {
Chris@99 1138 if (!m_model) return;
Chris@99 1139
Chris@1425 1140 EventVector points =
Chris@1425 1141 m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1142
Chris@1425 1143 for (Event p: points) {
Chris@1425 1144 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 1145 }
Chris@76 1146 }
Chris@76 1147
Chris@125 1148 bool
Chris@1425 1149 NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1425 1150 sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@76 1151 {
Chris@125 1152 if (!m_model) return false;
Chris@99 1153
Chris@1423 1154 const EventVector &points = from.getPoints();
Chris@76 1155
Chris@360 1156 bool realign = false;
Chris@360 1157
Chris@360 1158 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 1159
Chris@360 1160 QMessageBox::StandardButton button =
Chris@918 1161 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 1162 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 1163 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 1164 QMessageBox::Yes);
Chris@360 1165
Chris@360 1166 if (button == QMessageBox::Cancel) {
Chris@360 1167 return false;
Chris@360 1168 }
Chris@360 1169
Chris@360 1170 if (button == QMessageBox::Yes) {
Chris@360 1171 realign = true;
Chris@360 1172 }
Chris@360 1173 }
Chris@360 1174
Chris@1427 1175 ChangeEventsCommand *command =
Chris@1427 1176 new ChangeEventsCommand(m_model, tr("Paste"));
Chris@76 1177
Chris@1423 1178 for (EventVector::const_iterator i = points.begin();
Chris@76 1179 i != points.end(); ++i) {
Chris@1425 1180
Chris@905 1181 sv_frame_t frame = 0;
Chris@360 1182
Chris@360 1183 if (!realign) {
Chris@360 1184
Chris@360 1185 frame = i->getFrame();
Chris@360 1186
Chris@360 1187 } else {
Chris@360 1188
Chris@1423 1189 if (i->hasReferenceFrame()) {
Chris@360 1190 frame = i->getReferenceFrame();
Chris@360 1191 frame = alignFromReference(v, frame);
Chris@360 1192 } else {
Chris@360 1193 frame = i->getFrame();
Chris@360 1194 }
Chris@76 1195 }
Chris@360 1196
Chris@1428 1197 Event p = *i;
Chris@1425 1198 Event newPoint = p;
Chris@1425 1199 if (!p.hasValue()) {
Chris@1425 1200 newPoint = newPoint.withValue((m_model->getValueMinimum() +
Chris@1425 1201 m_model->getValueMaximum()) / 2);
Chris@1425 1202 }
Chris@1425 1203 if (!p.hasDuration()) {
Chris@905 1204 sv_frame_t nextFrame = frame;
Chris@1423 1205 EventVector::const_iterator j = i;
Chris@125 1206 for (; j != points.end(); ++j) {
Chris@125 1207 if (j != i) break;
Chris@125 1208 }
Chris@125 1209 if (j != points.end()) {
Chris@125 1210 nextFrame = j->getFrame();
Chris@125 1211 }
Chris@125 1212 if (nextFrame == frame) {
Chris@1425 1213 newPoint = newPoint.withDuration(m_model->getResolution());
Chris@125 1214 } else {
Chris@1425 1215 newPoint = newPoint.withDuration(nextFrame - frame);
Chris@125 1216 }
Chris@125 1217 }
Chris@76 1218
Chris@1425 1219 command->add(newPoint);
Chris@76 1220 }
Chris@76 1221
Chris@376 1222 finish(command);
Chris@125 1223 return true;
Chris@76 1224 }
Chris@76 1225
Chris@507 1226 void
Chris@905 1227 NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
Chris@507 1228 {
Chris@1425 1229 m_pendingNoteOns.insert(Event(frame, float(pitch), 0,
Chris@1425 1230 float(velocity) / 127.f, QString()));
Chris@507 1231 }
Chris@507 1232
Chris@507 1233 void
Chris@905 1234 NoteLayer::addNoteOff(sv_frame_t frame, int pitch)
Chris@507 1235 {
Chris@507 1236 for (NoteSet::iterator i = m_pendingNoteOns.begin();
Chris@507 1237 i != m_pendingNoteOns.end(); ++i) {
Chris@1425 1238
Chris@1425 1239 Event p = *i;
Chris@1425 1240
Chris@1425 1241 if (lrintf(p.getValue()) == pitch) {
Chris@507 1242 m_pendingNoteOns.erase(i);
Chris@1425 1243 Event note = p.withDuration(frame - p.getFrame());
Chris@507 1244 if (m_model) {
Chris@1427 1245 ChangeEventsCommand *c = new ChangeEventsCommand
Chris@1425 1246 (m_model, tr("Record Note"));
Chris@1425 1247 c->add(note);
Chris@507 1248 // execute and bundle:
Chris@507 1249 CommandHistory::getInstance()->addCommand(c, true, true);
Chris@507 1250 }
Chris@507 1251 break;
Chris@507 1252 }
Chris@507 1253 }
Chris@507 1254 }
Chris@507 1255
Chris@507 1256 void
Chris@507 1257 NoteLayer::abandonNoteOns()
Chris@507 1258 {
Chris@507 1259 m_pendingNoteOns.clear();
Chris@507 1260 }
Chris@507 1261
Chris@287 1262 int
Chris@287 1263 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 1264 {
Chris@287 1265 impose = false;
Chris@287 1266 return ColourDatabase::getInstance()->getColourIndex
Chris@287 1267 (QString(darkbg ? "White" : "Black"));
Chris@287 1268 }
Chris@287 1269
Chris@316 1270 void
Chris@316 1271 NoteLayer::toXml(QTextStream &stream,
Chris@316 1272 QString indent, QString extraAttributes) const
Chris@30 1273 {
Chris@316 1274 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@445 1275 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
Chris@445 1276 .arg(m_verticalScale)
Chris@445 1277 .arg(m_scaleMinimum)
Chris@445 1278 .arg(m_scaleMaximum));
Chris@30 1279 }
Chris@30 1280
Chris@30 1281 void
Chris@30 1282 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 1283 {
Chris@287 1284 SingleColourLayer::setProperties(attributes);
Chris@30 1285
Chris@445 1286 bool ok, alsoOk;
Chris@30 1287 VerticalScale scale = (VerticalScale)
Chris@1266 1288 attributes.value("verticalScale").toInt(&ok);
Chris@30 1289 if (ok) setVerticalScale(scale);
Chris@445 1290
Chris@445 1291 float min = attributes.value("scaleMinimum").toFloat(&ok);
Chris@445 1292 float max = attributes.value("scaleMaximum").toFloat(&alsoOk);
Chris@667 1293 if (ok && alsoOk && min != max) setDisplayExtents(min, max);
Chris@30 1294 }
Chris@30 1295
Chris@30 1296