annotate layer/NoteLayer.cpp @ 789:9fd1bdf214dd tonioni

Play pointer: when user drags pane during playback such that the pointer is no longer visible, accept that and stop trying to track it until pointer naturally comes back within visible area
author Chris Cannam
date Thu, 12 Jun 2014 12:48:11 +0100
parents 137d3ff48f73
children 1d526ba11a24
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 {
gyorgyf@625 59 SVDEBUG << "constructed NoteLayer" << endl;
Chris@30 60 }
Chris@30 61
Chris@30 62 void
Chris@30 63 NoteLayer::setModel(NoteModel *model)
gyorgyf@626 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@717 845 if (!m_model || m_model->getPoints().empty()) 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