annotate layer/NoteLayer.cpp @ 1330:c1f719094c25 zoom

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