annotate layer/FlexiNoteLayer.cpp @ 1548:bd6af89982d7

Permit getScaleProvidingLayerForUnit to return a dormant layer if there is no visible alternative. This is necessary to avoid the scale disappearing in Tony when the spectrogram is toggled off.
author Chris Cannam
date Thu, 17 Oct 2019 14:44:22 +0100
parents e6362cf5ff1d
children 76e4302a3fc2
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
matthiasm@620 16 #include "FlexiNoteLayer.h"
Chris@30 17
Chris@128 18 #include "data/model/Model.h"
gyorgyf@655 19 #include "data/model/SparseTimeValueModel.h"
Chris@30 20 #include "base/RealTime.h"
Chris@30 21 #include "base/Profiler.h"
Chris@30 22 #include "base/Pitch.h"
Chris@197 23 #include "base/LogRange.h"
Chris@439 24 #include "base/RangeMapper.h"
Chris@1078 25
Chris@376 26 #include "ColourDatabase.h"
Chris@1077 27 #include "LayerGeometryProvider.h"
Chris@692 28 #include "PianoScale.h"
Chris@701 29 #include "LinearNumericalScale.h"
Chris@701 30 #include "LogNumericalScale.h"
Chris@1078 31 #include "PaintAssistant.h"
Chris@30 32
Chris@1426 33 #include "data/model/NoteModel.h"
Chris@30 34
Chris@1077 35 #include "view/View.h"
Chris@1077 36
Chris@70 37 #include "widgets/ItemEditDialog.h"
Chris@701 38 #include "widgets/TextAbbrev.h"
Chris@70 39
Chris@30 40 #include <QPainter>
Chris@30 41 #include <QPainterPath>
Chris@30 42 #include <QMouseEvent>
Chris@316 43 #include <QTextStream>
Chris@360 44 #include <QMessageBox>
Chris@30 45
Chris@30 46 #include <iostream>
Chris@30 47 #include <cmath>
Chris@551 48 #include <utility>
gyorgyf@655 49 #include <limits> // GF: included to compile std::numerical_limits on linux
gyorgyf@655 50 #include <vector>
gyorgyf@655 51
Chris@1426 52 #define NOTE_HEIGHT 16
Chris@30 53
matthiasm@620 54 FlexiNoteLayer::FlexiNoteLayer() :
gyorgyf@646 55 SingleColourLayer(),
gyorgyf@628 56 m_editing(false),
Chris@805 57 m_intelligentActions(true),
Chris@844 58 m_dragPointX(0),
Chris@844 59 m_dragPointY(0),
Chris@844 60 m_dragStartX(0),
Chris@844 61 m_dragStartY(0),
gyorgyf@627 62 m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
gyorgyf@627 63 m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
Chris@844 64 m_greatestLeftNeighbourFrame(0),
Chris@844 65 m_smallestRightNeighbourFrame(0),
Chris@1408 66 m_editingCommand(nullptr),
matthiasm@634 67 m_verticalScale(AutoAlignScale),
Chris@688 68 m_editMode(DragNote),
gyorgyf@628 69 m_scaleMinimum(34),
Chris@805 70 m_scaleMaximum(77)
Chris@30 71 {
Chris@30 72 }
Chris@30 73
Chris@30 74 void
Chris@1469 75 FlexiNoteLayer::setModel(ModelId modelId)
Chris@30 76 {
Chris@1471 77 auto newModel = ModelById::getAs<NoteModel>(modelId);
Chris@1471 78
Chris@1471 79 if (!modelId.isNone() && !newModel) {
Chris@1471 80 throw std::logic_error("Not a NoteModel");
Chris@1471 81 }
Chris@1471 82
Chris@1469 83 if (m_model == modelId) return;
Chris@1469 84 m_model = modelId;
Chris@30 85
Chris@1471 86 if (newModel) {
Chris@1471 87 connectSignals(m_model);
Chris@1471 88 }
Chris@30 89
Chris@30 90 emit modelReplaced();
Chris@30 91 }
Chris@30 92
Chris@30 93 Layer::PropertyList
matthiasm@620 94 FlexiNoteLayer::getProperties() const
Chris@30 95 {
Chris@287 96 PropertyList list = SingleColourLayer::getProperties();
Chris@87 97 list.push_back("Vertical Scale");
Chris@100 98 list.push_back("Scale Units");
Chris@30 99 return list;
Chris@30 100 }
Chris@30 101
Chris@87 102 QString
matthiasm@620 103 FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 104 {
Chris@87 105 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@116 106 if (name == "Scale Units") return tr("Scale Units");
Chris@287 107 return SingleColourLayer::getPropertyLabel(name);
Chris@87 108 }
Chris@87 109
Chris@30 110 Layer::PropertyType
matthiasm@620 111 FlexiNoteLayer::getPropertyType(const PropertyName &name) const
Chris@30 112 {
Chris@100 113 if (name == "Scale Units") return UnitsProperty;
Chris@287 114 if (name == "Vertical Scale") return ValueProperty;
Chris@287 115 return SingleColourLayer::getPropertyType(name);
Chris@30 116 }
Chris@30 117
Chris@198 118 QString
matthiasm@620 119 FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const
Chris@198 120 {
Chris@198 121 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@198 122 return tr("Scale");
Chris@198 123 }
Chris@287 124 return SingleColourLayer::getPropertyGroupName(name);
Chris@198 125 }
Chris@198 126
Chris@701 127 QString
Chris@703 128 FlexiNoteLayer::getScaleUnits() const
Chris@701 129 {
Chris@1469 130 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 131 if (model) return model->getScaleUnits();
Chris@701 132 else return "";
Chris@701 133 }
Chris@701 134
Chris@30 135 int
matthiasm@620 136 FlexiNoteLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@714 137 int *min, int *max, int *deflt) const
Chris@30 138 {
Chris@216 139 int val = 0;
Chris@30 140
Chris@287 141 if (name == "Vertical Scale") {
gyorgyf@646 142
Chris@714 143 if (min) *min = 0;
Chris@714 144 if (max) *max = 3;
Chris@216 145 if (deflt) *deflt = int(AutoAlignScale);
gyorgyf@646 146
Chris@714 147 val = int(m_verticalScale);
Chris@30 148
Chris@100 149 } else if (name == "Scale Units") {
Chris@100 150
Chris@216 151 if (deflt) *deflt = 0;
Chris@1469 152 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 153 if (model) {
Chris@216 154 val = UnitDatabase::getInstance()->getUnitId
Chris@701 155 (getScaleUnits());
Chris@100 156 }
Chris@100 157
Chris@30 158 } else {
Chris@216 159
Chris@714 160 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@30 161 }
Chris@30 162
Chris@216 163 return val;
Chris@30 164 }
Chris@30 165
Chris@30 166 QString
matthiasm@620 167 FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name,
Chris@714 168 int value) const
Chris@30 169 {
Chris@287 170 if (name == "Vertical Scale") {
Chris@714 171 switch (value) {
Chris@714 172 default:
Chris@714 173 case 0: return tr("Auto-Align");
Chris@714 174 case 1: return tr("Linear");
Chris@714 175 case 2: return tr("Log");
Chris@714 176 case 3: return tr("MIDI Notes");
Chris@714 177 }
Chris@30 178 }
Chris@287 179 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@30 180 }
Chris@30 181
Chris@30 182 void
matthiasm@620 183 FlexiNoteLayer::setProperty(const PropertyName &name, int value)
Chris@30 184 {
Chris@287 185 if (name == "Vertical Scale") {
Chris@714 186 setVerticalScale(VerticalScale(value));
Chris@100 187 } else if (name == "Scale Units") {
Chris@1469 188 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 189 if (model) {
Chris@1469 190 model->setScaleUnits
Chris@100 191 (UnitDatabase::getInstance()->getUnitById(value));
Chris@1481 192 emit modelChanged(m_model);
Chris@100 193 }
Chris@287 194 } else {
Chris@287 195 return SingleColourLayer::setProperty(name, value);
Chris@30 196 }
Chris@30 197 }
Chris@30 198
Chris@30 199 void
matthiasm@620 200 FlexiNoteLayer::setVerticalScale(VerticalScale scale)
Chris@30 201 {
Chris@30 202 if (m_verticalScale == scale) return;
Chris@30 203 m_verticalScale = scale;
Chris@30 204 emit layerParametersChanged();
Chris@30 205 }
Chris@30 206
Chris@30 207 bool
Chris@916 208 FlexiNoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@30 209 {
Chris@30 210 QPoint discard;
Chris@44 211 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@30 212 }
Chris@30 213
Chris@79 214 bool
matthiasm@620 215 FlexiNoteLayer::shouldConvertMIDIToHz() const
Chris@101 216 {
Chris@701 217 QString unit = getScaleUnits();
Chris@101 218 return (unit != "Hz");
Chris@101 219 // if (unit == "" ||
Chris@101 220 // unit.startsWith("MIDI") ||
Chris@101 221 // unit.startsWith("midi")) return true;
Chris@101 222 // return false;
Chris@101 223 }
Chris@101 224
Chris@1469 225 int
Chris@1469 226 FlexiNoteLayer::getCompletion(LayerGeometryProvider *) const
Chris@1469 227 {
Chris@1469 228 auto model = ModelById::get(m_model);
Chris@1469 229 if (model) return model->getCompletion();
Chris@1469 230 else return 0;
Chris@1469 231 }
Chris@1469 232
Chris@101 233 bool
Chris@904 234 FlexiNoteLayer::getValueExtents(double &min, double &max,
Chris@714 235 bool &logarithmic, QString &unit) const
Chris@79 236 {
Chris@1469 237 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 238 if (!model) return false;
Chris@1469 239 min = model->getValueMinimum();
Chris@1469 240 max = model->getValueMaximum();
Chris@101 241
Chris@105 242 if (shouldConvertMIDIToHz()) {
Chris@105 243 unit = "Hz";
Chris@904 244 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 245 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@701 246 } else unit = getScaleUnits();
Chris@101 247
Chris@101 248 if (m_verticalScale == MIDIRangeScale ||
Chris@101 249 m_verticalScale == LogScale) logarithmic = true;
Chris@101 250
Chris@101 251 return true;
Chris@101 252 }
Chris@101 253
Chris@101 254 bool
Chris@904 255 FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
Chris@101 256 {
Chris@1469 257 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 258 if (!model || shouldAutoAlign()) {
Chris@695 259 // std::cerr << "No model or shouldAutoAlign()" << std::endl;
gyorgyf@646 260 return false;
gyorgyf@646 261 }
Chris@101 262
Chris@101 263 if (m_verticalScale == MIDIRangeScale) {
Chris@101 264 min = Pitch::getFrequencyForPitch(0);
matthiasm@634 265 max = Pitch::getFrequencyForPitch(127);
Chris@101 266 return true;
Chris@101 267 }
Chris@101 268
Chris@439 269 if (m_scaleMinimum == m_scaleMaximum) {
Chris@1469 270 min = model->getValueMinimum();
Chris@1469 271 max = model->getValueMaximum();
Chris@455 272 } else {
Chris@455 273 min = m_scaleMinimum;
Chris@455 274 max = m_scaleMaximum;
Chris@439 275 }
Chris@439 276
Chris@101 277 if (shouldConvertMIDIToHz()) {
Chris@904 278 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 279 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 280 }
Chris@101 281
Chris@667 282 #ifdef DEBUG_NOTE_LAYER
Chris@682 283 cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
Chris@667 284 #endif
Chris@667 285
Chris@79 286 return true;
Chris@79 287 }
Chris@79 288
Chris@439 289 bool
Chris@904 290 FlexiNoteLayer::setDisplayExtents(double min, double max)
Chris@439 291 {
Chris@1469 292 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 293 if (!model) return false;
Chris@439 294
Chris@439 295 if (min == max) {
Chris@439 296 if (min == 0.f) {
Chris@439 297 max = 1.f;
Chris@439 298 } else {
Chris@904 299 max = min * 1.0001f;
Chris@439 300 }
Chris@439 301 }
Chris@439 302
Chris@439 303 m_scaleMinimum = min;
Chris@439 304 m_scaleMaximum = max;
Chris@439 305
Chris@667 306 #ifdef DEBUG_NOTE_LAYER
Chris@684 307 cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
Chris@667 308 #endif
Chris@439 309
Chris@439 310 emit layerParametersChanged();
Chris@439 311 return true;
Chris@439 312 }
Chris@439 313
Chris@439 314 int
matthiasm@620 315 FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@439 316 {
Chris@439 317 if (shouldAutoAlign()) return 0;
Chris@1469 318 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 319 if (!model) return 0;
Chris@439 320
Chris@439 321 defaultStep = 0;
Chris@439 322 return 100;
Chris@439 323 }
Chris@439 324
Chris@439 325 int
matthiasm@620 326 FlexiNoteLayer::getCurrentVerticalZoomStep() const
Chris@439 327 {
Chris@439 328 if (shouldAutoAlign()) return 0;
Chris@1469 329 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 330 if (!model) return 0;
Chris@439 331
Chris@439 332 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@439 333 if (!mapper) return 0;
Chris@439 334
Chris@904 335 double dmin, dmax;
Chris@439 336 getDisplayExtents(dmin, dmax);
Chris@439 337
Chris@439 338 int nr = mapper->getPositionForValue(dmax - dmin);
Chris@439 339
Chris@439 340 delete mapper;
Chris@439 341
Chris@439 342 return 100 - nr;
Chris@439 343 }
Chris@439 344
Chris@439 345 //!!! lots of duplication with TimeValueLayer
Chris@439 346
Chris@439 347 void
matthiasm@620 348 FlexiNoteLayer::setVerticalZoomStep(int step)
Chris@439 349 {
Chris@439 350 if (shouldAutoAlign()) return;
Chris@1469 351 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 352 if (!model) return;
Chris@439 353
Chris@439 354 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@439 355 if (!mapper) return;
Chris@439 356
Chris@904 357 double min, max;
Chris@439 358 bool logarithmic;
Chris@439 359 QString unit;
Chris@439 360 getValueExtents(min, max, logarithmic, unit);
Chris@439 361
Chris@904 362 double dmin, dmax;
Chris@439 363 getDisplayExtents(dmin, dmax);
Chris@439 364
Chris@904 365 double newdist = mapper->getValueForPosition(100 - step);
Chris@439 366
Chris@904 367 double newmin, newmax;
Chris@439 368
Chris@439 369 if (logarithmic) {
Chris@439 370
Chris@439 371 // see SpectrogramLayer::setVerticalZoomStep
Chris@439 372
Chris@904 373 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@439 374 newmin = newmax - newdist;
Chris@439 375
Chris@682 376 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@439 377
Chris@439 378 } else {
Chris@904 379 double dmid = (dmax + dmin) / 2;
Chris@439 380 newmin = dmid - newdist / 2;
Chris@439 381 newmax = dmid + newdist / 2;
Chris@439 382 }
Chris@439 383
Chris@439 384 if (newmin < min) {
Chris@439 385 newmax += (min - newmin);
Chris@439 386 newmin = min;
Chris@439 387 }
Chris@439 388 if (newmax > max) {
Chris@439 389 newmax = max;
Chris@439 390 }
Chris@439 391
Chris@667 392 #ifdef DEBUG_NOTE_LAYER
Chris@684 393 cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@667 394 #endif
Chris@439 395
Chris@439 396 setDisplayExtents(newmin, newmax);
Chris@439 397 }
Chris@439 398
Chris@439 399 RangeMapper *
matthiasm@620 400 FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
Chris@439 401 {
Chris@1469 402 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 403 if (!model) return nullptr;
Chris@439 404
Chris@439 405 RangeMapper *mapper;
Chris@439 406
Chris@904 407 double min, max;
Chris@439 408 bool logarithmic;
Chris@439 409 QString unit;
Chris@439 410 getValueExtents(min, max, logarithmic, unit);
Chris@439 411
Chris@1408 412 if (min == max) return nullptr;
Chris@439 413
Chris@439 414 if (logarithmic) {
Chris@439 415 mapper = new LogRangeMapper(0, 100, min, max, unit);
Chris@439 416 } else {
Chris@439 417 mapper = new LinearRangeMapper(0, 100, min, max, unit);
Chris@439 418 }
Chris@439 419
Chris@439 420 return mapper;
Chris@439 421 }
Chris@439 422
Chris@1426 423 EventVector
Chris@916 424 FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
Chris@30 425 {
Chris@1469 426 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 427 if (!model) return {};
Chris@1426 428
Chris@904 429 sv_frame_t frame = v->getFrameForX(x);
Chris@30 430
Chris@1469 431 EventVector local = model->getEventsCovering(frame);
Chris@1426 432 if (!local.empty()) return local;
Chris@30 433
Chris@1426 434 int fuzz = ViewManager::scalePixelSize(2);
Chris@1426 435 sv_frame_t start = v->getFrameForX(x - fuzz);
Chris@1426 436 sv_frame_t end = v->getFrameForX(x + fuzz);
Chris@30 437
Chris@1469 438 local = model->getEventsStartingWithin(frame, end - frame);
Chris@1426 439 if (!local.empty()) return local;
Chris@30 440
Chris@1469 441 local = model->getEventsSpanning(start, frame - start);
Chris@1426 442 if (!local.empty()) return local;
Chris@30 443
Chris@1426 444 return {};
Chris@30 445 }
Chris@30 446
Chris@550 447 bool
Chris@1426 448 FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
Chris@550 449 {
Chris@1469 450 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 451 if (!model) return false;
Chris@550 452
Chris@904 453 sv_frame_t frame = v->getFrameForX(x);
Chris@550 454
Chris@1469 455 EventVector onPoints = model->getEventsCovering(frame);
Chris@550 456 if (onPoints.empty()) return false;
Chris@550 457
Chris@550 458 int nearestDistance = -1;
Chris@1426 459 for (const auto &p: onPoints) {
Chris@1426 460 int distance = getYForValue(v, p.getValue()) - y;
Chris@550 461 if (distance < 0) distance = -distance;
Chris@550 462 if (nearestDistance == -1 || distance < nearestDistance) {
Chris@550 463 nearestDistance = distance;
Chris@1426 464 point = p;
Chris@550 465 }
Chris@550 466 }
Chris@550 467
Chris@550 468 return true;
Chris@550 469 }
Chris@550 470
gyorgyf@646 471 bool
Chris@1426 472 FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &point) const
gyorgyf@646 473 {
gyorgyf@647 474 // GF: find the note that is closest to the cursor
Chris@1469 475 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 476 if (!model) return false;
gyorgyf@646 477
Chris@904 478 sv_frame_t frame = v->getFrameForX(x);
gyorgyf@646 479
Chris@1469 480 EventVector onPoints = model->getEventsCovering(frame);
gyorgyf@646 481 if (onPoints.empty()) return false;
gyorgyf@646 482
gyorgyf@646 483 int nearestDistance = -1;
Chris@1426 484 for (const auto &p: onPoints) {
Chris@1426 485 int distance = getYForValue(v, p.getValue()) - y;
gyorgyf@646 486 if (distance < 0) distance = -distance;
gyorgyf@646 487 if (nearestDistance == -1 || distance < nearestDistance) {
gyorgyf@646 488 nearestDistance = distance;
Chris@1426 489 point = p;
gyorgyf@646 490 }
gyorgyf@646 491 }
gyorgyf@646 492
gyorgyf@646 493 return true;
gyorgyf@646 494 }
gyorgyf@646 495
Chris@30 496 QString
Chris@916 497 FlexiNoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@30 498 {
Chris@30 499 int x = pos.x();
Chris@30 500
Chris@1469 501 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 502 if (!model || !model->getSampleRate()) return "";
Chris@30 503
Chris@1426 504 EventVector points = getLocalPoints(v, x);
Chris@30 505
Chris@30 506 if (points.empty()) {
Chris@1469 507 if (!model->isReady()) {
Chris@714 508 return tr("In progress");
Chris@714 509 } else {
Chris@714 510 return tr("No local points");
Chris@714 511 }
Chris@30 512 }
Chris@30 513
Chris@1426 514 Event note(0);
Chris@1426 515 EventVector::iterator i;
Chris@30 516
Chris@30 517 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 518
Chris@1426 519 int y = getYForValue(v, i->getValue());
Chris@714 520 int h = NOTE_HEIGHT; // GF: larger notes
Chris@30 521
Chris@1469 522 if (model->getValueQuantization() != 0.0) {
Chris@1426 523 h = y - getYForValue
Chris@1469 524 (v, i->getValue() + model->getValueQuantization());
Chris@714 525 if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
Chris@714 526 }
Chris@30 527
Chris@714 528 // GF: this is not quite correct
Chris@714 529 if (pos.y() >= y - 4 && pos.y() <= y + h) {
Chris@714 530 note = *i;
Chris@714 531 break;
Chris@714 532 }
Chris@30 533 }
Chris@30 534
Chris@30 535 if (i == points.end()) return tr("No local points");
Chris@30 536
Chris@1426 537 RealTime rt = RealTime::frame2RealTime(note.getFrame(),
Chris@1469 538 model->getSampleRate());
Chris@1426 539 RealTime rd = RealTime::frame2RealTime(note.getDuration(),
Chris@1469 540 model->getSampleRate());
Chris@30 541
Chris@101 542 QString pitchText;
Chris@101 543
Chris@101 544 if (shouldConvertMIDIToHz()) {
Chris@101 545
Chris@1426 546 int mnote = int(lrint(note.getValue()));
Chris@1426 547 int cents = int(lrint((note.getValue() - double(mnote)) * 100));
Chris@904 548 double freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@544 549 pitchText = tr("%1 (%2, %3 Hz)")
Chris@544 550 .arg(Pitch::getPitchLabel(mnote, cents))
Chris@544 551 .arg(mnote)
Chris@544 552 .arg(freq);
Chris@101 553
Chris@701 554 } else if (getScaleUnits() == "Hz") {
Chris@101 555
Chris@544 556 pitchText = tr("%1 Hz (%2, %3)")
Chris@1426 557 .arg(note.getValue())
Chris@1426 558 .arg(Pitch::getPitchLabelForFrequency(note.getValue()))
Chris@1426 559 .arg(Pitch::getPitchForFrequency(note.getValue()));
Chris@101 560
Chris@101 561 } else {
Chris@234 562 pitchText = tr("%1 %2")
Chris@1426 563 .arg(note.getValue()).arg(getScaleUnits());
Chris@101 564 }
Chris@101 565
Chris@30 566 QString text;
Chris@30 567
Chris@1426 568 if (note.getLabel() == "") {
Chris@714 569 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@714 570 .arg(rt.toText(true).c_str())
Chris@714 571 .arg(pitchText)
Chris@714 572 .arg(rd.toText(true).c_str());
Chris@30 573 } else {
Chris@714 574 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@714 575 .arg(rt.toText(true).c_str())
Chris@714 576 .arg(pitchText)
Chris@714 577 .arg(rd.toText(true).c_str())
Chris@1426 578 .arg(note.getLabel());
Chris@30 579 }
Chris@30 580
Chris@1426 581 pos = QPoint(v->getXForFrame(note.getFrame()),
Chris@1426 582 getYForValue(v, note.getValue()));
Chris@30 583 return text;
Chris@30 584 }
Chris@30 585
Chris@30 586 bool
Chris@916 587 FlexiNoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 588 int &resolution,
Chris@1547 589 SnapType snap, int ycoord) const
Chris@30 590 {
Chris@1469 591 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 592 if (!model) {
Chris@1547 593 return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
Chris@30 594 }
Chris@30 595
Chris@1469 596 resolution = model->getResolution();
Chris@1426 597 EventVector points;
Chris@30 598
Chris@30 599 if (snap == SnapNeighbouring) {
gyorgyf@646 600
Chris@714 601 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@714 602 if (points.empty()) return false;
Chris@1426 603 frame = points.begin()->getFrame();
Chris@714 604 return true;
Chris@30 605 }
Chris@30 606
Chris@1469 607 points = model->getEventsCovering(frame);
Chris@904 608 sv_frame_t snapped = frame;
Chris@30 609 bool found = false;
Chris@30 610
Chris@1426 611 for (EventVector::const_iterator i = points.begin();
Chris@714 612 i != points.end(); ++i) {
Chris@30 613
Chris@714 614 if (snap == SnapRight) {
Chris@30 615
Chris@1426 616 if (i->getFrame() > frame) {
Chris@1426 617 snapped = i->getFrame();
Chris@714 618 found = true;
Chris@714 619 break;
Chris@1426 620 } else if (i->getFrame() + i->getDuration() >= frame) {
Chris@1426 621 snapped = i->getFrame() + i->getDuration();
Chris@715 622 found = true;
Chris@715 623 break;
Chris@714 624 }
Chris@714 625
Chris@714 626 } else if (snap == SnapLeft) {
Chris@714 627
Chris@1426 628 if (i->getFrame() <= frame) {
Chris@1426 629 snapped = i->getFrame();
Chris@714 630 found = true; // don't break, as the next may be better
Chris@714 631 } else {
Chris@714 632 break;
Chris@714 633 }
Chris@714 634
Chris@714 635 } else { // nearest
Chris@714 636
Chris@1426 637 EventVector::const_iterator j = i;
Chris@714 638 ++j;
Chris@714 639
Chris@714 640 if (j == points.end()) {
Chris@714 641
Chris@1426 642 snapped = i->getFrame();
Chris@714 643 found = true;
Chris@714 644 break;
Chris@714 645
Chris@1426 646 } else if (j->getFrame() >= frame) {
Chris@714 647
Chris@1426 648 if (j->getFrame() - frame < frame - i->getFrame()) {
Chris@1426 649 snapped = j->getFrame();
Chris@714 650 } else {
Chris@1426 651 snapped = i->getFrame();
Chris@714 652 }
Chris@714 653 found = true;
Chris@714 654 break;
Chris@714 655 }
gyorgyf@646 656 }
Chris@30 657 }
Chris@30 658
Chris@715 659 cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl;
Chris@715 660
Chris@30 661 frame = snapped;
Chris@30 662 return found;
Chris@30 663 }
Chris@30 664
Chris@101 665 void
Chris@916 666 FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
Chris@30 667 {
Chris@101 668 min = 0.0;
Chris@101 669 max = 0.0;
Chris@101 670 log = false;
Chris@42 671
Chris@101 672 QString queryUnits;
Chris@101 673 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@701 674 else queryUnits = getScaleUnits();
Chris@30 675
Chris@439 676 if (shouldAutoAlign()) {
Chris@30 677
Chris@1537 678 if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) {
Chris@30 679
Chris@1469 680 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 681 min = model->getValueMinimum();
Chris@1469 682 max = model->getValueMaximum();
Chris@42 683
Chris@101 684 if (shouldConvertMIDIToHz()) {
Chris@904 685 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 686 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 687 }
Chris@42 688
Chris@667 689 #ifdef DEBUG_NOTE_LAYER
Chris@684 690 cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@667 691 #endif
Chris@105 692
Chris@101 693 } else if (log) {
Chris@101 694
Chris@197 695 LogRange::mapRange(min, max);
Chris@105 696
Chris@667 697 #ifdef DEBUG_NOTE_LAYER
Chris@684 698 cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@667 699 #endif
Chris@101 700 }
Chris@101 701
Chris@101 702 } else {
Chris@101 703
Chris@439 704 getDisplayExtents(min, max);
Chris@101 705
Chris@101 706 if (m_verticalScale == MIDIRangeScale) {
Chris@101 707 min = Pitch::getFrequencyForPitch(0);
matthiasm@623 708 max = Pitch::getFrequencyForPitch(70);
Chris@101 709 } else if (shouldConvertMIDIToHz()) {
Chris@904 710 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 711 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 712 }
Chris@101 713
Chris@101 714 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@197 715 LogRange::mapRange(min, max);
Chris@101 716 log = true;
Chris@101 717 }
Chris@30 718 }
Chris@30 719
Chris@101 720 if (max == min) max = min + 1.0;
Chris@101 721 }
Chris@30 722
Chris@101 723 int
Chris@916 724 FlexiNoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
Chris@101 725 {
Chris@904 726 double min = 0.0, max = 0.0;
Chris@101 727 bool logarithmic = false;
Chris@916 728 int h = v->getPaintHeight();
Chris@101 729
Chris@101 730 getScaleExtents(v, min, max, logarithmic);
Chris@101 731
Chris@667 732 #ifdef DEBUG_NOTE_LAYER
Chris@684 733 cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
Chris@667 734 #endif
Chris@101 735
Chris@101 736 if (shouldConvertMIDIToHz()) {
Chris@904 737 val = Pitch::getFrequencyForPitch(int(lrint(val)),
Chris@904 738 int(lrint((val - floor(val)) * 100.0)));
Chris@667 739 #ifdef DEBUG_NOTE_LAYER
Chris@682 740 cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
Chris@667 741 #endif
Chris@101 742 }
Chris@101 743
Chris@101 744 if (logarithmic) {
Chris@197 745 val = LogRange::map(val);
Chris@667 746 #ifdef DEBUG_NOTE_LAYER
Chris@682 747 cerr << "logarithmic true, val now = " << val << endl;
Chris@667 748 #endif
Chris@101 749 }
Chris@101 750
Chris@101 751 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@667 752 #ifdef DEBUG_NOTE_LAYER
Chris@682 753 cerr << "y = " << y << endl;
Chris@667 754 #endif
Chris@101 755 return y;
Chris@30 756 }
Chris@30 757
Chris@904 758 double
Chris@916 759 FlexiNoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
Chris@30 760 {
Chris@904 761 double min = 0.0, max = 0.0;
Chris@101 762 bool logarithmic = false;
Chris@916 763 int h = v->getPaintHeight();
Chris@30 764
Chris@101 765 getScaleExtents(v, min, max, logarithmic);
Chris@101 766
Chris@904 767 double val = min + (double(h - y) * double(max - min)) / h;
Chris@101 768
Chris@101 769 if (logarithmic) {
Chris@904 770 val = pow(10.f, val);
Chris@101 771 }
Chris@101 772
Chris@101 773 if (shouldConvertMIDIToHz()) {
Chris@101 774 val = Pitch::getPitchForFrequency(val);
Chris@101 775 }
Chris@101 776
Chris@101 777 return val;
Chris@30 778 }
Chris@30 779
Chris@439 780 bool
matthiasm@620 781 FlexiNoteLayer::shouldAutoAlign() const
Chris@439 782 {
Chris@439 783 return (m_verticalScale == AutoAlignScale);
Chris@439 784 }
Chris@439 785
Chris@30 786 void
Chris@916 787 FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@30 788 {
Chris@1469 789 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 790 if (!model || !model->isOK()) return;
Chris@30 791
Chris@1469 792 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@30 793 if (!sampleRate) return;
Chris@30 794
matthiasm@620 795 // Profiler profiler("FlexiNoteLayer::paint", true);
Chris@30 796
Chris@1426 797 int x0 = rect.left(), x1 = rect.right();
Chris@1426 798 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@904 799 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@30 800
Chris@1469 801 EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
Chris@30 802 if (points.empty()) return;
Chris@30 803
Chris@287 804 paint.setPen(getBaseQColor());
Chris@30 805
Chris@287 806 QColor brushColour(getBaseQColor());
Chris@30 807 brushColour.setAlpha(80);
Chris@30 808
matthiasm@620 809 // SVDEBUG << "FlexiNoteLayer::paint: resolution is "
Chris@1469 810 // << model->getResolution() << " frames" << endl;
Chris@30 811
Chris@1469 812 double min = model->getValueMinimum();
Chris@1469 813 double max = model->getValueMaximum();
Chris@30 814 if (max == min) max = min + 1.0;
Chris@30 815
Chris@30 816 QPoint localPos;
Chris@1426 817 Event illuminatePoint(0);
Chris@551 818 bool shouldIlluminate = false;
Chris@30 819
Chris@44 820 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@551 821 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@551 822 illuminatePoint);
Chris@30 823 }
Chris@30 824
Chris@30 825 paint.save();
Chris@30 826 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 827
matthiasm@819 828 int noteNumber = 0;
matthiasm@819 829
Chris@1426 830 for (EventVector::const_iterator i = points.begin();
Chris@714 831 i != points.end(); ++i) {
Chris@30 832
matthiasm@819 833 ++noteNumber;
Chris@1426 834 const Event &p(*i);
Chris@30 835
Chris@1426 836 int x = v->getXForFrame(p.getFrame());
Chris@1426 837 int y = getYForValue(v, p.getValue());
Chris@1426 838 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@714 839 int h = NOTE_HEIGHT; //GF: larger notes
gyorgyf@646 840
Chris@1469 841 if (model->getValueQuantization() != 0.0) {
Chris@1469 842 h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
Chris@714 843 if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
Chris@714 844 }
Chris@30 845
Chris@714 846 if (w < 1) w = 1;
Chris@714 847 paint.setPen(getBaseQColor());
Chris@714 848 paint.setBrush(brushColour);
Chris@30 849
Chris@1426 850 if (shouldIlluminate && illuminatePoint == p) {
matthiasm@784 851
Chris@1426 852 paint.drawLine(x, -1, x, v->getPaintHeight() + 1);
Chris@1426 853 paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1);
matthiasm@784 854
Chris@1426 855 paint.setPen(v->getForeground());
matthiasm@784 856
Chris@1426 857 QString vlabel = tr("freq: %1%2")
Chris@1469 858 .arg(p.getValue()).arg(model->getScaleUnits());
Chris@1426 859 PaintAssistant::drawVisibleText
Chris@1426 860 (v, paint,
Chris@1426 861 x,
Chris@1426 862 y - h/2 - 2 - paint.fontMetrics().height()
Chris@1426 863 - paint.fontMetrics().descent(),
Chris@1426 864 vlabel, PaintAssistant::OutlinedText);
matthiasm@793 865
Chris@1426 866 QString hlabel = tr("dur: %1")
Chris@1426 867 .arg(RealTime::frame2RealTime
Chris@1469 868 (p.getDuration(), model->getSampleRate()).toText(true)
Chris@1426 869 .c_str());
Chris@1426 870 PaintAssistant::drawVisibleText
Chris@1426 871 (v, paint,
Chris@1426 872 x,
Chris@1426 873 y - h/2 - paint.fontMetrics().descent() - 2,
Chris@1426 874 hlabel, PaintAssistant::OutlinedText);
matthiasm@793 875
Chris@1426 876 QString llabel = QString("%1").arg(p.getLabel());
Chris@1426 877 PaintAssistant::drawVisibleText
Chris@1426 878 (v, paint,
Chris@1426 879 x,
Chris@1426 880 y + h + 2 + paint.fontMetrics().descent(),
Chris@1426 881 llabel, PaintAssistant::OutlinedText);
Chris@1426 882
Chris@1426 883 QString nlabel = QString("%1").arg(noteNumber);
Chris@1426 884 PaintAssistant::drawVisibleText
Chris@1426 885 (v, paint,
Chris@1426 886 x + paint.fontMetrics().averageCharWidth() / 2,
Chris@1426 887 y + h/2 - paint.fontMetrics().descent(),
Chris@1426 888 nlabel, PaintAssistant::OutlinedText);
matthiasm@784 889 }
gyorgyf@646 890
Chris@714 891 paint.drawRect(x, y - h/2, w, h);
Chris@30 892 }
Chris@30 893
Chris@30 894 paint.restore();
Chris@30 895 }
Chris@30 896
Chris@692 897 int
Chris@916 898 FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@692 899 {
Chris@1469 900 if (shouldAutoAlign()) {
Chris@696 901 return 0;
Chris@701 902 } else {
Chris@701 903 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@701 904 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
Chris@701 905 } else {
Chris@701 906 return LinearNumericalScale().getWidth(v, paint);
Chris@701 907 }
Chris@696 908 }
Chris@692 909 }
Chris@692 910
Chris@692 911 void
Chris@916 912 FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@692 913 {
Chris@1469 914 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 915 if (!model || model->isEmpty()) return;
Chris@701 916
Chris@701 917 QString unit;
Chris@904 918 double min, max;
Chris@701 919 bool logarithmic;
Chris@701 920
Chris@701 921 int w = getVerticalScaleWidth(v, false, paint);
Chris@916 922 int h = v->getPaintHeight();
Chris@701 923
Chris@701 924 getScaleExtents(v, min, max, logarithmic);
Chris@701 925
Chris@701 926 if (logarithmic) {
Chris@701 927 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@696 928 } else {
Chris@701 929 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 930 }
Chris@701 931
Chris@701 932 if (logarithmic && (getScaleUnits() == "Hz")) {
Chris@696 933 PianoScale().paintPianoVertical
Chris@701 934 (v, paint, QRect(w - 10, 0, 10, h),
Chris@701 935 LogRange::unmap(min),
Chris@701 936 LogRange::unmap(max));
Chris@701 937 paint.drawLine(w, 0, w, h);
Chris@701 938 }
Chris@701 939
Chris@701 940 if (getScaleUnits() != "") {
Chris@701 941 int mw = w - 5;
Chris@701 942 paint.drawText(5,
Chris@701 943 5 + paint.fontMetrics().ascent(),
Chris@701 944 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 945 paint.fontMetrics(),
Chris@701 946 mw));
Chris@696 947 }
Chris@692 948 }
Chris@692 949
Chris@30 950 void
Chris@916 951 FlexiNoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 952 {
matthiasm@620 953 // SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 954
Chris@1469 955 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 956 if (!model) return;
Chris@30 957
Chris@904 958 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 959 if (frame < 0) frame = 0;
Chris@1469 960 frame = frame / model->getResolution() * model->getResolution();
Chris@30 961
Chris@904 962 double value = getValueForY(v, e->y());
Chris@30 963
Chris@1426 964 m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point"));
Chris@30 965 m_originalPoint = m_editingPoint;
Chris@30 966
Chris@376 967 if (m_editingCommand) finish(m_editingCommand);
Chris@1470 968 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@1426 969 m_editingCommand->add(m_editingPoint);
Chris@30 970
Chris@30 971 m_editing = true;
Chris@30 972 }
Chris@30 973
Chris@30 974 void
Chris@916 975 FlexiNoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 976 {
matthiasm@620 977 // SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 978
Chris@1469 979 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 980 if (!model || !m_editing) return;
Chris@30 981
Chris@904 982 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 983 if (frame < 0) frame = 0;
Chris@1469 984 frame = frame / model->getResolution() * model->getResolution();
Chris@30 985
Chris@904 986 double newValue = getValueForY(v, e->y());
Chris@101 987
Chris@1426 988 sv_frame_t newFrame = m_editingPoint.getFrame();
Chris@904 989 sv_frame_t newDuration = frame - newFrame;
Chris@101 990 if (newDuration < 0) {
Chris@101 991 newFrame = frame;
Chris@101 992 newDuration = -newDuration;
Chris@101 993 } else if (newDuration == 0) {
Chris@101 994 newDuration = 1;
Chris@101 995 }
Chris@30 996
Chris@1426 997 m_editingCommand->remove(m_editingPoint);
Chris@1426 998 m_editingPoint = m_editingPoint
Chris@1426 999 .withFrame(newFrame)
Chris@1426 1000 .withValue(float(newValue))
Chris@1426 1001 .withDuration(newDuration);
Chris@1426 1002 m_editingCommand->add(m_editingPoint);
Chris@30 1003 }
Chris@30 1004
Chris@30 1005 void
Chris@916 1006 FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@30 1007 {
matthiasm@620 1008 // SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1469 1009 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1010 if (!model || !m_editing) return;
Chris@376 1011 finish(m_editingCommand);
Chris@1408 1012 m_editingCommand = nullptr;
Chris@30 1013 m_editing = false;
Chris@30 1014 }
Chris@30 1015
Chris@30 1016 void
Chris@916 1017 FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1018 {
Chris@1469 1019 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1020 if (!model) return;
Chris@335 1021
Chris@550 1022 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 1023
Chris@335 1024 if (m_editingCommand) {
Chris@714 1025 finish(m_editingCommand);
Chris@1408 1026 m_editingCommand = nullptr;
Chris@335 1027 }
Chris@335 1028
Chris@335 1029 m_editing = true;
Chris@335 1030 }
Chris@335 1031
Chris@335 1032 void
Chris@916 1033 FlexiNoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 1034 {
Chris@335 1035 }
Chris@335 1036
Chris@335 1037 void
Chris@916 1038 FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1039 {
Chris@1469 1040 if (!m_editing) return;
Chris@335 1041 m_editing = false;
Chris@335 1042
Chris@1426 1043 Event p(0);
Chris@550 1044 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1469 1045 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1469 1046 p.getValue() != m_editingPoint.getValue()) return;
Chris@550 1047
Chris@1470 1048 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1426 1049 m_editingCommand->remove(m_editingPoint);
Chris@376 1050 finish(m_editingCommand);
Chris@1408 1051 m_editingCommand = nullptr;
Chris@335 1052 m_editing = false;
Chris@335 1053 }
Chris@335 1054
Chris@335 1055 void
Chris@916 1056 FlexiNoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1057 {
matthiasm@620 1058 // SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
gyorgyf@635 1059 std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 1060
Chris@1469 1061 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1062 if (!model) return;
Chris@30 1063
Chris@550 1064 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@1426 1065 m_originalPoint = m_editingPoint;
gyorgyf@649 1066
matthiasm@651 1067 if (m_editMode == RightBoundary) {
Chris@1426 1068 m_dragPointX = v->getXForFrame
Chris@1426 1069 (m_editingPoint.getFrame() + m_editingPoint.getDuration());
gyorgyf@649 1070 } else {
Chris@1426 1071 m_dragPointX = v->getXForFrame
Chris@1426 1072 (m_editingPoint.getFrame());
gyorgyf@649 1073 }
Chris@1426 1074 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
Chris@551 1075
Chris@30 1076 if (m_editingCommand) {
Chris@714 1077 finish(m_editingCommand);
Chris@1408 1078 m_editingCommand = nullptr;
Chris@30 1079 }
Chris@30 1080
Chris@30 1081 m_editing = true;
Chris@551 1082 m_dragStartX = e->x();
Chris@551 1083 m_dragStartY = e->y();
matthiasm@651 1084
Chris@1426 1085 sv_frame_t onset = m_originalPoint.getFrame();
Chris@1426 1086 sv_frame_t offset =
Chris@1426 1087 m_originalPoint.getFrame() +
Chris@1426 1088 m_originalPoint.getDuration() - 1;
matthiasm@651 1089
matthiasm@651 1090 m_greatestLeftNeighbourFrame = -1;
Chris@806 1091 m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
Chris@1426 1092
Chris@1469 1093 EventVector allEvents = model->getAllEvents();
matthiasm@651 1094
Chris@1426 1095 for (auto currentNote: allEvents) {
matthiasm@651 1096
matthiasm@651 1097 // left boundary
Chris@1426 1098 if (currentNote.getFrame() + currentNote.getDuration() - 1 < onset) {
Chris@1426 1099 m_greatestLeftNeighbourFrame =
Chris@1426 1100 currentNote.getFrame() + currentNote.getDuration() - 1;
matthiasm@651 1101 }
matthiasm@651 1102
matthiasm@651 1103 // right boundary
Chris@1426 1104 if (currentNote.getFrame() > offset) {
Chris@1426 1105 m_smallestRightNeighbourFrame = currentNote.getFrame();
matthiasm@651 1106 break;
matthiasm@651 1107 }
matthiasm@651 1108 }
Chris@1426 1109
Chris@753 1110 std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl;
Chris@30 1111 }
Chris@30 1112
Chris@30 1113 void
Chris@916 1114 FlexiNoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1115 {
matthiasm@620 1116 // SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
gyorgyf@635 1117 std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 1118
Chris@1469 1119 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1120 if (!model || !m_editing) return;
Chris@30 1121
Chris@551 1122 int xdist = e->x() - m_dragStartX;
Chris@551 1123 int ydist = e->y() - m_dragStartY;
Chris@551 1124 int newx = m_dragPointX + xdist;
Chris@551 1125 int newy = m_dragPointY + ydist;
Chris@551 1126
Chris@904 1127 sv_frame_t dragFrame = v->getFrameForX(newx);
matthiasm@657 1128 if (dragFrame < 0) dragFrame = 0;
Chris@1469 1129 dragFrame = dragFrame / model->getResolution() * model->getResolution();
matthiasm@651 1130
Chris@904 1131 double value = getValueForY(v, newy);
Chris@30 1132
Chris@30 1133 if (!m_editingCommand) {
Chris@1469 1134 m_editingCommand =
Chris@1470 1135 new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
Chris@30 1136 }
Chris@1426 1137 m_editingCommand->remove(m_editingPoint);
matthiasm@651 1138
Chris@874 1139 std::cerr << "edit mode: " << m_editMode << " intelligent actions = "
Chris@874 1140 << m_intelligentActions << std::endl;
matthiasm@657 1141
matthiasm@651 1142 switch (m_editMode) {
Chris@1426 1143
Chris@714 1144 case LeftBoundary : {
Chris@714 1145 // left
Chris@1426 1146 if (m_intelligentActions &&
Chris@1426 1147 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1148 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1149 }
Chris@714 1150 // right
Chris@1426 1151 if (m_intelligentActions &&
Chris@1426 1152 dragFrame >= m_originalPoint.getFrame() + m_originalPoint.getDuration()) {
Chris@1426 1153 dragFrame = m_originalPoint.getFrame() + m_originalPoint.getDuration() - 1;
matthiasm@651 1154 }
Chris@1426 1155 m_editingPoint = m_editingPoint
Chris@1426 1156 .withFrame(dragFrame)
Chris@1426 1157 .withDuration(m_originalPoint.getFrame() -
Chris@1426 1158 dragFrame + m_originalPoint.getDuration());
Chris@714 1159 break;
Chris@714 1160 }
Chris@1426 1161
Chris@714 1162 case RightBoundary : {
Chris@714 1163 // left
Chris@1426 1164 if (m_intelligentActions &&
Chris@1426 1165 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1166 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1167 }
Chris@1426 1168 if (m_intelligentActions &&
Chris@1426 1169 dragFrame >= m_smallestRightNeighbourFrame) {
Chris@1426 1170 dragFrame = m_smallestRightNeighbourFrame - 1;
Chris@1426 1171 }
Chris@1426 1172 m_editingPoint = m_editingPoint
Chris@1426 1173 .withDuration(dragFrame - m_originalPoint.getFrame() + 1);
Chris@714 1174 break;
Chris@714 1175 }
Chris@1426 1176
Chris@714 1177 case DragNote : {
Chris@714 1178 // left
Chris@1426 1179 if (m_intelligentActions &&
Chris@1426 1180 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1181 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1182 }
Chris@714 1183 // right
Chris@1426 1184 if (m_intelligentActions &&
Chris@1426 1185 dragFrame + m_originalPoint.getDuration() >= m_smallestRightNeighbourFrame) {
Chris@1426 1186 dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.getDuration();
matthiasm@651 1187 }
Chris@1426 1188
Chris@1426 1189 m_editingPoint = m_editingPoint
Chris@1426 1190 .withFrame(dragFrame)
Chris@1426 1191 .withValue(float(value));
Chris@875 1192
Chris@875 1193 // Re-analyse region within +/- 1 semitone of the dragged value
Chris@875 1194 float cents = 0;
Chris@1426 1195 int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.getValue(), &cents);
Chris@922 1196 double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents);
Chris@922 1197 double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents);
Chris@875 1198
Chris@1426 1199 emit reAnalyseRegion(m_editingPoint.getFrame(),
Chris@1426 1200 m_editingPoint.getFrame() +
Chris@1426 1201 m_editingPoint.getDuration(),
Chris@922 1202 float(lower), float(higher));
Chris@714 1203 break;
Chris@714 1204 }
Chris@1426 1205
Chris@805 1206 case SplitNote: // nothing
Chris@805 1207 break;
gyorgyf@649 1208 }
Chris@875 1209
Chris@1426 1210 m_editingCommand->add(m_editingPoint);
Chris@875 1211
Chris@1426 1212 std::cerr << "added new point(" << m_editingPoint.getFrame() << "," << m_editingPoint.getDuration() << ")" << std::endl;
Chris@30 1213 }
Chris@30 1214
Chris@30 1215 void
Chris@948 1216 FlexiNoteLayer::editEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1217 {
Chris@1426 1218 std::cerr << "FlexiNoteLayer::editEnd("
Chris@1426 1219 << e->x() << "," << e->y() << ")" << std::endl;
matthiasm@656 1220
Chris@1469 1221 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1222 if (!model || !m_editing) return;
Chris@30 1223
Chris@30 1224 if (m_editingCommand) {
Chris@30 1225
Chris@714 1226 QString newName = m_editingCommand->getName();
Chris@30 1227
Chris@876 1228 if (m_editMode == DragNote) {
Chris@876 1229 //!!! command nesting is wrong?
Chris@876 1230 emit materialiseReAnalysis();
Chris@876 1231 }
Chris@876 1232
Chris@1426 1233 m_editingCommand->remove(m_editingPoint);
Chris@875 1234 updateNoteValueFromPitchCurve(v, m_editingPoint);
Chris@1426 1235 m_editingCommand->add(m_editingPoint);
Chris@875 1236
Chris@1426 1237 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1426 1238 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@714 1239 newName = tr("Edit Point");
Chris@714 1240 } else {
Chris@714 1241 newName = tr("Relocate Point");
Chris@714 1242 }
gyorgyf@646 1243 } else {
Chris@714 1244 newName = tr("Change Point Value");
gyorgyf@646 1245 }
Chris@30 1246
Chris@714 1247 m_editingCommand->setName(newName);
Chris@714 1248 finish(m_editingCommand);
Chris@30 1249 }
Chris@30 1250
Chris@1408 1251 m_editingCommand = nullptr;
Chris@30 1252 m_editing = false;
Chris@30 1253 }
Chris@30 1254
gyorgyf@635 1255 void
Chris@916 1256 FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@635 1257 {
Chris@1469 1258 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1259 if (!model) return;
Chris@1469 1260
gyorgyf@646 1261 // GF: note splitting starts (!! remove printing soon)
Chris@874 1262 std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
gyorgyf@635 1263
gyorgyf@635 1264 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
gyorgyf@635 1265 // m_originalPoint = m_editingPoint;
gyorgyf@635 1266 //
Chris@1426 1267 // m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
Chris@1426 1268 // m_dragPointY = getYForValue(v, m_editingPoint.getValue());
gyorgyf@635 1269
gyorgyf@635 1270 if (m_editingCommand) {
Chris@714 1271 finish(m_editingCommand);
Chris@1408 1272 m_editingCommand = nullptr;
gyorgyf@635 1273 }
gyorgyf@635 1274
gyorgyf@635 1275 m_editing = true;
gyorgyf@635 1276 m_dragStartX = e->x();
gyorgyf@635 1277 m_dragStartY = e->y();
gyorgyf@635 1278 }
gyorgyf@635 1279
gyorgyf@635 1280 void
Chris@916 1281 FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@635 1282 {
Chris@1469 1283 auto model = ModelById::getAs<NoteModel>(m_model);
gyorgyf@646 1284 // GF: note splitting ends. (!! remove printing soon)
gyorgyf@646 1285 std::cerr << "splitEnd" << std::endl;
Chris@1469 1286 if (!model || !m_editing || m_editMode != SplitNote) return;
gyorgyf@635 1287
gyorgyf@635 1288 int xdist = e->x() - m_dragStartX;
gyorgyf@635 1289 int ydist = e->y() - m_dragStartY;
gyorgyf@635 1290 if (xdist != 0 || ydist != 0) {
gyorgyf@646 1291 std::cerr << "mouse moved" << std::endl;
gyorgyf@635 1292 return;
gyorgyf@635 1293 }
gyorgyf@635 1294
Chris@904 1295 sv_frame_t frame = v->getFrameForX(e->x());
gyorgyf@635 1296
Chris@753 1297 splitNotesAt(v, frame, e);
Chris@746 1298 }
Chris@746 1299
Chris@746 1300 void
Chris@916 1301 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame)
Chris@746 1302 {
Chris@1408 1303 splitNotesAt(v, frame, nullptr);
Chris@753 1304 }
Chris@753 1305
Chris@753 1306 void
Chris@916 1307 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
Chris@753 1308 {
Chris@1469 1309 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1310 if (!model) return;
Chris@1469 1311
Chris@1469 1312 EventVector onPoints = model->getEventsCovering(frame);
Chris@746 1313 if (onPoints.empty()) return;
Chris@746 1314
Chris@1426 1315 Event note(*onPoints.begin());
gyorgyf@635 1316
Chris@1470 1317 auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1426 1318 command->remove(note);
Chris@753 1319
Chris@753 1320 if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
Chris@753 1321
Chris@753 1322 int gap = 0; // MM: I prefer a gap of 0, but we can decide later
Chris@753 1323
Chris@1426 1324 Event newNote1(note.getFrame(), note.getValue(),
Chris@1426 1325 frame - note.getFrame() - gap,
Chris@1426 1326 note.getLevel(), note.getLabel());
Chris@753 1327
Chris@1426 1328 Event newNote2(frame, note.getValue(),
Chris@1426 1329 note.getDuration() - newNote1.getDuration(),
Chris@1426 1330 note.getLevel(), note.getLabel());
Chris@747 1331
Chris@753 1332 if (m_intelligentActions) {
Chris@875 1333 if (updateNoteValueFromPitchCurve(v, newNote1)) {
Chris@1426 1334 command->add(newNote1);
Chris@753 1335 }
Chris@875 1336 if (updateNoteValueFromPitchCurve(v, newNote2)) {
Chris@1426 1337 command->add(newNote2);
Chris@753 1338 }
Chris@753 1339 } else {
Chris@1426 1340 command->add(newNote1);
Chris@1426 1341 command->add(newNote2);
Chris@747 1342 }
Chris@747 1343 }
Chris@746 1344
gyorgyf@635 1345 finish(command);
gyorgyf@646 1346 }
gyorgyf@646 1347
gyorgyf@655 1348 void
Chris@916 1349 FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
matthiasm@660 1350 {
Chris@1469 1351 auto model = ModelById::getAs<NoteModel>(m_model);
matthiasm@660 1352 std::cerr << "addNote" << std::endl;
Chris@1469 1353 if (!model) return;
matthiasm@660 1354
Chris@904 1355 sv_frame_t duration = 10000;
matthiasm@660 1356
Chris@904 1357 sv_frame_t frame = v->getFrameForX(e->x());
Chris@904 1358 double value = getValueForY(v, e->y());
matthiasm@660 1359
Chris@1469 1360 EventVector noteList = model->getAllEvents();
matthiasm@792 1361
matthiasm@660 1362 if (m_intelligentActions) {
Chris@904 1363 sv_frame_t smallestRightNeighbourFrame = 0;
Chris@1426 1364 for (EventVector::const_iterator i = noteList.begin();
matthiasm@792 1365 i != noteList.end(); ++i) {
Chris@1426 1366 Event currentNote = *i;
Chris@1426 1367 if (currentNote.getFrame() > frame) {
Chris@1426 1368 smallestRightNeighbourFrame = currentNote.getFrame();
matthiasm@660 1369 break;
matthiasm@660 1370 }
matthiasm@660 1371 }
matthiasm@792 1372 if (smallestRightNeighbourFrame > 0) {
matthiasm@792 1373 duration = std::min(smallestRightNeighbourFrame - frame + 1, duration);
matthiasm@792 1374 duration = (duration > 0) ? duration : 0;
matthiasm@792 1375 }
matthiasm@660 1376 }
matthiasm@660 1377
matthiasm@660 1378 if (!m_intelligentActions ||
Chris@1469 1379 (model->getEventsCovering(frame).empty() && duration > 0)) {
Chris@1426 1380 Event newNote(frame, float(value), duration, 100.f, tr("new note"));
Chris@1470 1381 auto command = new ChangeEventsCommand(m_model.untyped, tr("Add Point"));
Chris@1426 1382 command->add(newNote);
matthiasm@660 1383 finish(command);
matthiasm@660 1384 }
matthiasm@660 1385 }
matthiasm@660 1386
Chris@1469 1387 ModelId
Chris@916 1388 FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
Chris@745 1389 {
Chris@745 1390 // Better than we used to do, but still not very satisfactory
Chris@745 1391
Chris@874 1392 // cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
Chris@746 1393
Chris@918 1394 for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
Chris@918 1395 Layer *layer = v->getView()->getLayer(i);
Chris@795 1396 if (layer &&
Chris@748 1397 layer->getLayerPresentationName() != "candidate") {
Chris@874 1398 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
Chris@1469 1399 auto modelId = layer->getModel();
Chris@1469 1400 auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
Chris@745 1401 if (model && model->getScaleUnits() == "Hz") {
Chris@1469 1402 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
Chris@1469 1403 return modelId;
Chris@745 1404 }
Chris@745 1405 }
Chris@745 1406 }
Chris@1469 1407 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
Chris@1469 1408 return {};
Chris@745 1409 }
matthiasm@660 1410
matthiasm@660 1411 void
Chris@916 1412 FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
Chris@746 1413 {
Chris@1469 1414 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1415 if (!model) return;
Chris@746 1416
Chris@1426 1417 EventVector points =
Chris@1469 1418 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@746 1419
Chris@1470 1420 auto command = new ChangeEventsCommand(m_model.untyped, tr("Snap Notes"));
Chris@746 1421
Chris@746 1422 cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
Chris@746 1423
Chris@1426 1424 for (EventVector::iterator i = points.begin();
Chris@746 1425 i != points.end(); ++i) {
Chris@746 1426
Chris@1426 1427 Event note(*i);
Chris@746 1428
Chris@1426 1429 cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.getFrame() << " to " << note.getFrame() + note.getDuration() << endl;
Chris@746 1430
Chris@1426 1431 if (!s.contains(note.getFrame()) &&
Chris@1426 1432 !s.contains(note.getFrame() + note.getDuration() - 1)) {
Chris@746 1433 continue;
Chris@746 1434 }
Chris@746 1435
matthiasm@773 1436 cerr << "snapSelectedNotesToPitchTrack: making new note" << endl;
Chris@1426 1437 Event newNote(note);
Chris@746 1438
Chris@1426 1439 command->remove(note);
Chris@746 1440
Chris@875 1441 if (updateNoteValueFromPitchCurve(v, newNote)) {
Chris@1426 1442 command->add(newNote);
matthiasm@775 1443 }
Chris@746 1444 }
Chris@747 1445
Chris@746 1446 finish(command);
Chris@746 1447 }
Chris@746 1448
Chris@746 1449 void
Chris@916 1450 FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
Chris@747 1451 {
Chris@1469 1452 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1453 if (!model) return;
Chris@1469 1454
Chris@1426 1455 EventVector points;
Chris@749 1456 if (inclusive) {
Chris@1469 1457 points = model->getEventsSpanning(s.getStartFrame(), s.getDuration());
Chris@749 1458 } else {
Chris@1469 1459 points = model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@747 1460 }
Chris@749 1461
Chris@1426 1462 EventVector::iterator i = points.begin();
Chris@747 1463 if (i == points.end()) return;
Chris@747 1464
Chris@1470 1465 auto command = new ChangeEventsCommand(m_model.untyped, tr("Merge Notes"));
Chris@747 1466
Chris@1426 1467 Event newNote(*i);
Chris@747 1468
Chris@747 1469 while (i != points.end()) {
Chris@747 1470
Chris@749 1471 if (inclusive) {
Chris@1426 1472 if (i->getFrame() >= s.getEndFrame()) break;
Chris@749 1473 } else {
Chris@1426 1474 if (i->getFrame() + i->getDuration() > s.getEndFrame()) break;
Chris@749 1475 }
Chris@747 1476
Chris@1426 1477 newNote = newNote.withDuration
Chris@1426 1478 (i->getFrame() + i->getDuration() - newNote.getFrame());
Chris@1426 1479 command->remove(*i);
Chris@747 1480
Chris@747 1481 ++i;
Chris@747 1482 }
Chris@747 1483
Chris@875 1484 updateNoteValueFromPitchCurve(v, newNote);
Chris@1426 1485 command->add(newNote);
Chris@747 1486 finish(command);
Chris@747 1487 }
Chris@747 1488
Chris@747 1489 bool
Chris@1426 1490 FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const
gyorgyf@655 1491 {
Chris@1469 1492 ModelId modelId = getAssociatedPitchModel(v);
Chris@1469 1493 auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
Chris@747 1494 if (!model) return false;
gyorgyf@655 1495
gyorgyf@655 1496 std::cerr << model->getTypeName() << std::endl;
gyorgyf@655 1497
Chris@1429 1498 EventVector dataPoints =
Chris@1429 1499 model->getEventsWithin(note.getFrame(), note.getDuration());
Chris@746 1500
Chris@1426 1501 std::cerr << "frame " << note.getFrame() << ": " << dataPoints.size() << " candidate points" << std::endl;
Chris@746 1502
Chris@747 1503 if (dataPoints.empty()) return false;
Chris@746 1504
Chris@904 1505 std::vector<double> pitchValues;
gyorgyf@655 1506
Chris@1429 1507 for (EventVector::const_iterator i =
Chris@747 1508 dataPoints.begin(); i != dataPoints.end(); ++i) {
Chris@1429 1509 pitchValues.push_back(i->getValue());
gyorgyf@655 1510 }
Chris@747 1511
Chris@747 1512 if (pitchValues.empty()) return false;
Chris@747 1513
gyorgyf@655 1514 sort(pitchValues.begin(), pitchValues.end());
Chris@904 1515 int size = int(pitchValues.size());
gyorgyf@655 1516 double median;
gyorgyf@655 1517
gyorgyf@655 1518 if (size % 2 == 0) {
gyorgyf@655 1519 median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2;
gyorgyf@655 1520 } else {
gyorgyf@655 1521 median = pitchValues[size/2];
gyorgyf@655 1522 }
Chris@875 1523
Chris@1426 1524 std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.getValue() << " to median " << median << std::endl;
gyorgyf@655 1525
Chris@1426 1526 note = note.withValue(float(median));
Chris@747 1527
Chris@747 1528 return true;
gyorgyf@655 1529 }
gyorgyf@655 1530
gyorgyf@646 1531 void
Chris@916 1532 FlexiNoteLayer::mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@646 1533 {
gyorgyf@646 1534 // GF: context sensitive cursors
Chris@918 1535 // v->getView()->setCursor(Qt::ArrowCursor);
Chris@1426 1536 Event note(0);
gyorgyf@646 1537 if (!getNoteToEdit(v, e->x(), e->y(), note)) {
Chris@918 1538 // v->getView()->setCursor(Qt::UpArrowCursor);
gyorgyf@646 1539 return;
gyorgyf@646 1540 }
gyorgyf@646 1541
Chris@874 1542 bool closeToLeft = false, closeToRight = false,
Chris@874 1543 closeToTop = false, closeToBottom = false;
Chris@874 1544 getRelativeMousePosition(v, note, e->x(), e->y(),
Chris@874 1545 closeToLeft, closeToRight,
Chris@874 1546 closeToTop, closeToBottom);
gyorgyf@649 1547
Chris@874 1548 if (closeToLeft) {
Chris@945 1549 v->getView()->setCursor(Qt::SizeHorCursor);
Chris@874 1550 m_editMode = LeftBoundary;
Chris@874 1551 cerr << "edit mode -> LeftBoundary" << endl;
Chris@874 1552 } else if (closeToRight) {
Chris@945 1553 v->getView()->setCursor(Qt::SizeHorCursor);
Chris@874 1554 m_editMode = RightBoundary;
Chris@874 1555 cerr << "edit mode -> RightBoundary" << endl;
Chris@874 1556 } else if (closeToTop) {
Chris@945 1557 v->getView()->setCursor(Qt::CrossCursor);
Chris@874 1558 m_editMode = DragNote;
Chris@874 1559 cerr << "edit mode -> DragNote" << endl;
Chris@874 1560 } else if (closeToBottom) {
Chris@945 1561 v->getView()->setCursor(Qt::UpArrowCursor);
Chris@874 1562 m_editMode = SplitNote;
Chris@874 1563 cerr << "edit mode -> SplitNote" << endl;
Chris@874 1564 } else {
Chris@945 1565 v->getView()->setCursor(Qt::ArrowCursor);
Chris@874 1566 }
gyorgyf@646 1567 }
gyorgyf@646 1568
gyorgyf@646 1569 void
Chris@1426 1570 FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
gyorgyf@646 1571 {
Chris@1469 1572 // GF: TODO: consolidate the tolerance values
gyorgyf@646 1573
matthiasm@651 1574 int ctol = 0;
Chris@1426 1575 int noteStartX = v->getXForFrame(note.getFrame());
Chris@1426 1576 int noteEndX = v->getXForFrame(note.getFrame() + note.getDuration());
Chris@1426 1577 int noteValueY = getYForValue(v,note.getValue());
gyorgyf@646 1578 int noteStartY = noteValueY - (NOTE_HEIGHT / 2);
gyorgyf@646 1579 int noteEndY = noteValueY + (NOTE_HEIGHT / 2);
gyorgyf@646 1580
gyorgyf@646 1581 bool closeToNote = false;
gyorgyf@646 1582
gyorgyf@646 1583 if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true;
gyorgyf@646 1584 if (!closeToNote) return;
gyorgyf@646 1585
matthiasm@651 1586 int tol = NOTE_HEIGHT / 2;
gyorgyf@646 1587
gyorgyf@646 1588 if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true;
gyorgyf@646 1589 if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true;
gyorgyf@646 1590 if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true;
gyorgyf@646 1591 if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true;
Chris@688 1592
Chris@688 1593 // cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl;
gyorgyf@635 1594 }
gyorgyf@635 1595
gyorgyf@635 1596
Chris@255 1597 bool
Chris@916 1598 FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 1599 {
gyorgyf@633 1600 std::cerr << "Opening note editor dialog" << std::endl;
Chris@1469 1601 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1602 if (!model) return false;
Chris@70 1603
Chris@1426 1604 Event note(0);
Chris@550 1605 if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
Chris@550 1606
Chris@70 1607 ItemEditDialog *dialog = new ItemEditDialog
Chris@1469 1608 (model->getSampleRate(),
Chris@70 1609 ItemEditDialog::ShowTime |
Chris@70 1610 ItemEditDialog::ShowDuration |
Chris@70 1611 ItemEditDialog::ShowValue |
Chris@1515 1612 ItemEditDialog::ShowLevel |
Chris@100 1613 ItemEditDialog::ShowText,
Chris@701 1614 getScaleUnits());
Chris@70 1615
Chris@1426 1616 dialog->setFrameTime(note.getFrame());
Chris@1426 1617 dialog->setValue(note.getValue());
Chris@1426 1618 dialog->setFrameDuration(note.getDuration());
Chris@1426 1619 dialog->setText(note.getLabel());
Chris@70 1620
Chris@70 1621 if (dialog->exec() == QDialog::Accepted) {
Chris@70 1622
Chris@1426 1623 Event newNote = note
Chris@1426 1624 .withFrame(dialog->getFrameTime())
Chris@1426 1625 .withValue(dialog->getValue())
Chris@1426 1626 .withDuration(dialog->getFrameDuration())
Chris@1426 1627 .withLabel(dialog->getText());
Chris@70 1628
Chris@1470 1629 auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1426 1630 command->remove(note);
Chris@1426 1631 command->add(newNote);
Chris@376 1632 finish(command);
Chris@70 1633 }
Chris@70 1634
Chris@70 1635 delete dialog;
Chris@255 1636 return true;
Chris@70 1637 }
Chris@70 1638
Chris@70 1639 void
Chris@905 1640 FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 1641 {
Chris@1469 1642 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1643 if (!model) return;
Chris@1469 1644
Chris@1470 1645 auto command = new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 1646
Chris@1426 1647 EventVector points =
Chris@1469 1648 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1649
Chris@1426 1650 for (Event p: points) {
Chris@1426 1651 command->remove(p);
Chris@1426 1652 Event moved = p.withFrame(p.getFrame() +
Chris@1426 1653 newStartFrame - s.getStartFrame());
Chris@1426 1654 command->add(moved);
Chris@43 1655 }
Chris@43 1656
Chris@376 1657 finish(command);
Chris@43 1658 }
Chris@43 1659
Chris@43 1660 void
matthiasm@620 1661 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 1662 {
Chris@1469 1663 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1664 if (!model || !s.getDuration()) return;
Chris@99 1665
Chris@1470 1666 auto command = new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 1667
Chris@1426 1668 EventVector points =
Chris@1469 1669 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1670
Chris@1426 1671 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1426 1672 double oldStart = double(s.getStartFrame());
Chris@1426 1673 double newStart = double(newSize.getStartFrame());
Chris@1426 1674
Chris@1426 1675 for (Event p: points) {
Chris@43 1676
Chris@1426 1677 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@1426 1678 double newDuration = double(p.getDuration()) * ratio;
Chris@43 1679
Chris@1426 1680 Event newPoint = p
Chris@1426 1681 .withFrame(lrint(newFrame))
Chris@1426 1682 .withDuration(lrint(newDuration));
Chris@1426 1683 command->remove(p);
Chris@1426 1684 command->add(newPoint);
Chris@43 1685 }
Chris@43 1686
Chris@376 1687 finish(command);
Chris@43 1688 }
Chris@43 1689
Chris@76 1690 void
matthiasm@620 1691 FlexiNoteLayer::deleteSelection(Selection s)
Chris@76 1692 {
Chris@1469 1693 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1694 if (!model) return;
Chris@99 1695
Chris@1469 1696 auto command =
Chris@1470 1697 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
Chris@76 1698
Chris@1426 1699 EventVector points =
Chris@1469 1700 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1701
Chris@1426 1702 for (Event p: points) {
Chris@1426 1703 command->remove(p);
Chris@76 1704 }
Chris@76 1705
Chris@376 1706 finish(command);
Chris@76 1707 }
Chris@76 1708
Chris@76 1709 void
matthiasm@784 1710 FlexiNoteLayer::deleteSelectionInclusive(Selection s)
matthiasm@784 1711 {
Chris@1469 1712 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1713 if (!model) return;
matthiasm@784 1714
Chris@1469 1715 auto command =
Chris@1470 1716 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
matthiasm@784 1717
Chris@1426 1718 EventVector points =
Chris@1469 1719 model->getEventsSpanning(s.getStartFrame(), s.getDuration());
matthiasm@784 1720
Chris@1426 1721 for (Event p: points) {
Chris@1426 1722 command->remove(p);
matthiasm@784 1723 }
matthiasm@784 1724
matthiasm@784 1725 finish(command);
matthiasm@784 1726 }
matthiasm@784 1727
matthiasm@784 1728 void
Chris@916 1729 FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 1730 {
Chris@1469 1731 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1732 if (!model) return;
Chris@99 1733
Chris@1426 1734 EventVector points =
Chris@1469 1735 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1736
Chris@1426 1737 for (Event p: points) {
Chris@1426 1738 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 1739 }
Chris@76 1740 }
Chris@76 1741
Chris@125 1742 bool
Chris@1533 1743 FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 1744 sv_frame_t /*frameOffset */, bool /* interactive */)
Chris@76 1745 {
Chris@1469 1746 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1747 if (!model) return false;
Chris@99 1748
Chris@1423 1749 const EventVector &points = from.getPoints();
Chris@76 1750
Chris@360 1751 bool realign = false;
Chris@360 1752
Chris@360 1753 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 1754
Chris@360 1755 QMessageBox::StandardButton button =
Chris@918 1756 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 1757 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 1758 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 1759 QMessageBox::Yes);
Chris@360 1760
Chris@360 1761 if (button == QMessageBox::Cancel) {
Chris@360 1762 return false;
Chris@360 1763 }
Chris@360 1764
Chris@360 1765 if (button == QMessageBox::Yes) {
Chris@360 1766 realign = true;
Chris@360 1767 }
Chris@360 1768 }
Chris@360 1769
Chris@1470 1770 auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 1771
Chris@1423 1772 for (EventVector::const_iterator i = points.begin();
Chris@76 1773 i != points.end(); ++i) {
Chris@76 1774
Chris@904 1775 sv_frame_t frame = 0;
Chris@360 1776
Chris@360 1777 if (!realign) {
Chris@360 1778
Chris@360 1779 frame = i->getFrame();
Chris@360 1780
Chris@360 1781 } else {
Chris@360 1782
Chris@1423 1783 if (i->hasReferenceFrame()) {
Chris@360 1784 frame = i->getReferenceFrame();
Chris@360 1785 frame = alignFromReference(v, frame);
Chris@360 1786 } else {
Chris@360 1787 frame = i->getFrame();
Chris@360 1788 }
Chris@76 1789 }
Chris@360 1790
Chris@1533 1791 Event p = i->withFrame(frame);
Chris@1533 1792
Chris@1426 1793 Event newPoint = p;
Chris@1426 1794 if (!p.hasValue()) {
Chris@1469 1795 newPoint = newPoint.withValue((model->getValueMinimum() +
Chris@1469 1796 model->getValueMaximum()) / 2);
Chris@1426 1797 }
Chris@1426 1798 if (!p.hasDuration()) {
Chris@904 1799 sv_frame_t nextFrame = frame;
Chris@1423 1800 EventVector::const_iterator j = i;
Chris@125 1801 for (; j != points.end(); ++j) {
Chris@125 1802 if (j != i) break;
Chris@125 1803 }
Chris@125 1804 if (j != points.end()) {
Chris@125 1805 nextFrame = j->getFrame();
Chris@125 1806 }
Chris@125 1807 if (nextFrame == frame) {
Chris@1469 1808 newPoint = newPoint.withDuration(model->getResolution());
Chris@125 1809 } else {
Chris@1426 1810 newPoint = newPoint.withDuration(nextFrame - frame);
Chris@125 1811 }
Chris@125 1812 }
Chris@76 1813
Chris@1426 1814 command->add(newPoint);
Chris@76 1815 }
Chris@76 1816
Chris@376 1817 finish(command);
Chris@125 1818 return true;
Chris@76 1819 }
Chris@76 1820
Chris@507 1821 void
Chris@904 1822 FlexiNoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
Chris@507 1823 {
Chris@1426 1824 m_pendingNoteOns.insert(Event(frame, float(pitch), 0,
Chris@1426 1825 float(velocity / 127.0), ""));
Chris@507 1826 }
Chris@507 1827
Chris@507 1828 void
Chris@904 1829 FlexiNoteLayer::addNoteOff(sv_frame_t frame, int pitch)
Chris@507 1830 {
Chris@1426 1831 for (NoteSet::iterator i = m_pendingNoteOns.begin();
Chris@507 1832 i != m_pendingNoteOns.end(); ++i) {
Chris@1426 1833
Chris@1426 1834 Event p = *i;
Chris@1426 1835
Chris@1426 1836 if (lrintf(p.getValue()) == pitch) {
Chris@507 1837 m_pendingNoteOns.erase(i);
Chris@1426 1838 Event note = p.withDuration(frame - p.getFrame());
Chris@1470 1839 auto c = new ChangeEventsCommand
Chris@1470 1840 (m_model.untyped, tr("Record Note"));
Chris@1469 1841 c->add(note);
Chris@1469 1842 // execute and bundle:
Chris@1469 1843 CommandHistory::getInstance()->addCommand(c, true, true);
Chris@507 1844 break;
Chris@507 1845 }
Chris@507 1846 }
Chris@507 1847 }
Chris@507 1848
Chris@507 1849 void
matthiasm@620 1850 FlexiNoteLayer::abandonNoteOns()
Chris@507 1851 {
Chris@507 1852 m_pendingNoteOns.clear();
Chris@507 1853 }
Chris@507 1854
Chris@287 1855 int
matthiasm@620 1856 FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 1857 {
Chris@287 1858 impose = false;
Chris@287 1859 return ColourDatabase::getInstance()->getColourIndex
Chris@287 1860 (QString(darkbg ? "White" : "Black"));
Chris@287 1861 }
Chris@287 1862
Chris@316 1863 void
matthiasm@620 1864 FlexiNoteLayer::toXml(QTextStream &stream,
Chris@714 1865 QString indent, QString extraAttributes) const
Chris@30 1866 {
Chris@316 1867 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@445 1868 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
Chris@445 1869 .arg(m_verticalScale)
Chris@445 1870 .arg(m_scaleMinimum)
Chris@445 1871 .arg(m_scaleMaximum));
Chris@30 1872 }
Chris@30 1873
Chris@30 1874 void
matthiasm@620 1875 FlexiNoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 1876 {
Chris@287 1877 SingleColourLayer::setProperties(attributes);
Chris@30 1878
Chris@805 1879 bool ok;
Chris@30 1880 VerticalScale scale = (VerticalScale)
Chris@714 1881 attributes.value("verticalScale").toInt(&ok);
Chris@30 1882 if (ok) setVerticalScale(scale);
Chris@30 1883 }
Chris@30 1884
matthiasm@651 1885 void
Chris@916 1886 FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
matthiasm@651 1887 {
Chris@1469 1888 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1889 if (!model) return;
Chris@1469 1890
Chris@904 1891 double minf = std::numeric_limits<double>::max();
Chris@904 1892 double maxf = 0;
matthiasm@656 1893 bool hasNotes = 0;
Chris@1469 1894 EventVector allPoints = model->getAllEvents();
Chris@1426 1895 for (EventVector::const_iterator i = allPoints.begin();
Chris@1426 1896 i != allPoints.end(); ++i) {
Chris@714 1897 hasNotes = 1;
Chris@1426 1898 Event note = *i;
Chris@1426 1899 if (note.getValue() < minf) minf = note.getValue();
Chris@1426 1900 if (note.getValue() > maxf) maxf = note.getValue();
matthiasm@651 1901 }
matthiasm@651 1902
matthiasm@656 1903 std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
matthiasm@656 1904
matthiasm@656 1905 if (hasNotes) {
Chris@918 1906 v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5);
matthiasm@656 1907 // MM: this is a hack because we rely on
matthiasm@656 1908 // * this layer being automatically aligned to layer 1
matthiasm@656 1909 // * layer one is a log frequency layer.
matthiasm@651 1910 }
matthiasm@651 1911 }
Chris@30 1912
matthiasm@651 1913