annotate layer/NoteLayer.cpp @ 959:2633a1d73e39

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