annotate layer/NoteLayer.cpp @ 701:084f65094203

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