Mercurial > hg > svgui
view layer/FlexiNoteLayer.cpp @ 778:6d95d87f1654
Remember the last-used suffix in save dialog (#877).
author | Chris Cannam |
---|---|
date | Fri, 23 May 2014 11:17:52 +0100 |
parents | 6388ddae6ce3 |
children | 8c2dfb4d6c7a |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2006 Chris Cannam. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "FlexiNoteLayer.h" #include "data/model/Model.h" #include "data/model/SparseTimeValueModel.h" #include "base/RealTime.h" #include "base/Profiler.h" #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" #include "ColourDatabase.h" #include "view/View.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "data/model/FlexiNoteModel.h" #include "widgets/ItemEditDialog.h" #include "widgets/TextAbbrev.h" #include <QPainter> #include <QPainterPath> #include <QMouseEvent> #include <QTextStream> #include <QMessageBox> #include <iostream> #include <cmath> #include <utility> #include <limits> // GF: included to compile std::numerical_limits on linux #include <vector> FlexiNoteLayer::FlexiNoteLayer() : SingleColourLayer(), // m_model(0), // m_editing(false), // m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")), // m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")), // m_editingCommand(0), // m_verticalScale(AutoAlignScale), // m_scaleMinimum(0), // m_scaleMaximum(0) m_model(0), m_editing(false), m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")), m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")), m_editingCommand(0), m_verticalScale(AutoAlignScale), m_editMode(DragNote), m_scaleMinimum(34), m_scaleMaximum(77), m_intelligentActions(true) { } void FlexiNoteLayer::setModel(FlexiNoteModel *model) { if (m_model == model) return; m_model = model; connectSignals(m_model); // m_scaleMinimum = 0; // m_scaleMaximum = 0; emit modelReplaced(); } Layer::PropertyList FlexiNoteLayer::getProperties() const { PropertyList list = SingleColourLayer::getProperties(); list.push_back("Vertical Scale"); list.push_back("Scale Units"); return list; } QString FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const { if (name == "Vertical Scale") return tr("Vertical Scale"); if (name == "Scale Units") return tr("Scale Units"); return SingleColourLayer::getPropertyLabel(name); } Layer::PropertyType FlexiNoteLayer::getPropertyType(const PropertyName &name) const { if (name == "Scale Units") return UnitsProperty; if (name == "Vertical Scale") return ValueProperty; return SingleColourLayer::getPropertyType(name); } QString FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const { if (name == "Vertical Scale" || name == "Scale Units") { return tr("Scale"); } return SingleColourLayer::getPropertyGroupName(name); } QString FlexiNoteLayer::getScaleUnits() const { if (m_model) return m_model->getScaleUnits(); else return ""; } int FlexiNoteLayer::getPropertyRangeAndValue(const PropertyName &name, int *min, int *max, int *deflt) const { int val = 0; if (name == "Vertical Scale") { if (min) *min = 0; if (max) *max = 3; if (deflt) *deflt = int(AutoAlignScale); val = int(m_verticalScale); } else if (name == "Scale Units") { if (deflt) *deflt = 0; if (m_model) { val = UnitDatabase::getInstance()->getUnitId (getScaleUnits()); } } else { val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); } return val; } QString FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name, int value) const { if (name == "Vertical Scale") { switch (value) { default: case 0: return tr("Auto-Align"); case 1: return tr("Linear"); case 2: return tr("Log"); case 3: return tr("MIDI Notes"); } } return SingleColourLayer::getPropertyValueLabel(name, value); } void FlexiNoteLayer::setProperty(const PropertyName &name, int value) { if (name == "Vertical Scale") { setVerticalScale(VerticalScale(value)); } else if (name == "Scale Units") { if (m_model) { m_model->setScaleUnits (UnitDatabase::getInstance()->getUnitById(value)); emit modelChanged(); } } else { return SingleColourLayer::setProperty(name, value); } } void FlexiNoteLayer::setVerticalScale(VerticalScale scale) { if (m_verticalScale == scale) return; m_verticalScale = scale; emit layerParametersChanged(); } bool FlexiNoteLayer::isLayerScrollable(const View *v) const { QPoint discard; return !v->shouldIlluminateLocalFeatures(this, discard); } bool FlexiNoteLayer::shouldConvertMIDIToHz() const { QString unit = getScaleUnits(); return (unit != "Hz"); // if (unit == "" || // unit.startsWith("MIDI") || // unit.startsWith("midi")) return true; // return false; } bool FlexiNoteLayer::getValueExtents(float &min, float &max, bool &logarithmic, QString &unit) const { if (!m_model) return false; min = m_model->getValueMinimum(); max = m_model->getValueMaximum(); if (shouldConvertMIDIToHz()) { unit = "Hz"; min = Pitch::getFrequencyForPitch(lrintf(min)); max = Pitch::getFrequencyForPitch(lrintf(max + 1)); } else unit = getScaleUnits(); if (m_verticalScale == MIDIRangeScale || m_verticalScale == LogScale) logarithmic = true; return true; } bool FlexiNoteLayer::getDisplayExtents(float &min, float &max) const { if (!m_model || shouldAutoAlign()) { // std::cerr << "No model or shouldAutoAlign()" << std::endl; return false; } if (m_verticalScale == MIDIRangeScale) { min = Pitch::getFrequencyForPitch(0); max = Pitch::getFrequencyForPitch(127); return true; } if (m_scaleMinimum == m_scaleMaximum) { min = m_model->getValueMinimum(); max = m_model->getValueMaximum(); } else { min = m_scaleMinimum; max = m_scaleMaximum; } if (shouldConvertMIDIToHz()) { min = Pitch::getFrequencyForPitch(lrintf(min)); max = Pitch::getFrequencyForPitch(lrintf(max + 1)); } #ifdef DEBUG_NOTE_LAYER cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; #endif return true; } bool FlexiNoteLayer::setDisplayExtents(float min, float max) { if (!m_model) return false; if (min == max) { if (min == 0.f) { max = 1.f; } else { max = min * 1.0001; } } m_scaleMinimum = min; m_scaleMaximum = max; #ifdef DEBUG_NOTE_LAYER cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; #endif emit layerParametersChanged(); return true; } int FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const { if (shouldAutoAlign()) return 0; if (!m_model) return 0; defaultStep = 0; return 100; } int FlexiNoteLayer::getCurrentVerticalZoomStep() const { if (shouldAutoAlign()) return 0; if (!m_model) return 0; RangeMapper *mapper = getNewVerticalZoomRangeMapper(); if (!mapper) return 0; float dmin, dmax; getDisplayExtents(dmin, dmax); int nr = mapper->getPositionForValue(dmax - dmin); delete mapper; return 100 - nr; } //!!! lots of duplication with TimeValueLayer void FlexiNoteLayer::setVerticalZoomStep(int step) { if (shouldAutoAlign()) return; if (!m_model) return; RangeMapper *mapper = getNewVerticalZoomRangeMapper(); if (!mapper) return; float min, max; bool logarithmic; QString unit; getValueExtents(min, max, logarithmic, unit); float dmin, dmax; getDisplayExtents(dmin, dmax); float newdist = mapper->getValueForPosition(100 - step); float newmin, newmax; if (logarithmic) { // see SpectrogramLayer::setVerticalZoomStep newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2; newmin = newmax - newdist; // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl; } else { float dmid = (dmax + dmin) / 2; newmin = dmid - newdist / 2; newmax = dmid + newdist / 2; } if (newmin < min) { newmax += (min - newmin); newmin = min; } if (newmax > max) { newmax = max; } #ifdef DEBUG_NOTE_LAYER cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; #endif setDisplayExtents(newmin, newmax); } RangeMapper * FlexiNoteLayer::getNewVerticalZoomRangeMapper() const { if (!m_model) return 0; RangeMapper *mapper; float min, max; bool logarithmic; QString unit; getValueExtents(min, max, logarithmic, unit); if (min == max) return 0; if (logarithmic) { mapper = new LogRangeMapper(0, 100, min, max, unit); } else { mapper = new LinearRangeMapper(0, 100, min, max, unit); } return mapper; } FlexiNoteModel::PointList FlexiNoteLayer::getLocalPoints(View *v, int x) const { if (!m_model) return FlexiNoteModel::PointList(); long frame = v->getFrameForX(x); FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); if (!onPoints.empty()) { return onPoints; } FlexiNoteModel::PointList prevPoints = m_model->getPreviousPoints(frame); FlexiNoteModel::PointList nextPoints = m_model->getNextPoints(frame); FlexiNoteModel::PointList usePoints = prevPoints; if (prevPoints.empty()) { usePoints = nextPoints; } else if (long(prevPoints.begin()->frame) < v->getStartFrame() && !(nextPoints.begin()->frame > v->getEndFrame())) { usePoints = nextPoints; } else if (long(nextPoints.begin()->frame) - frame < frame - long(prevPoints.begin()->frame)) { usePoints = nextPoints; } if (!usePoints.empty()) { int fuzz = 2; int px = v->getXForFrame(usePoints.begin()->frame); if ((px > x && px - x > fuzz) || (px < x && x - px > fuzz + 1)) { usePoints.clear(); } } return usePoints; } bool FlexiNoteLayer::getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &p) const { if (!m_model) return false; long frame = v->getFrameForX(x); FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); if (onPoints.empty()) return false; // cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << endl; int nearestDistance = -1; for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin(); i != onPoints.end(); ++i) { int distance = getYForValue(v, (*i).value) - y; if (distance < 0) distance = -distance; if (nearestDistance == -1 || distance < nearestDistance) { nearestDistance = distance; p = *i; } } return true; } bool FlexiNoteLayer::getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &p) const { // GF: find the note that is closest to the cursor if (!m_model) return false; long frame = v->getFrameForX(x); FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); if (onPoints.empty()) return false; // std::cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << std::endl; int nearestDistance = -1; for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin(); i != onPoints.end(); ++i) { int distance = getYForValue(v, (*i).value) - y; if (distance < 0) distance = -distance; if (nearestDistance == -1 || distance < nearestDistance) { nearestDistance = distance; p = *i; } } return true; } QString FlexiNoteLayer::getFeatureDescription(View *v, QPoint &pos) const { int x = pos.x(); if (!m_model || !m_model->getSampleRate()) return ""; FlexiNoteModel::PointList points = getLocalPoints(v, x); if (points.empty()) { if (!m_model->isReady()) { return tr("In progress"); } else { return tr("No local points"); } } FlexiNote note(0); FlexiNoteModel::PointList::iterator i; for (i = points.begin(); i != points.end(); ++i) { int y = getYForValue(v, i->value); int h = NOTE_HEIGHT; // GF: larger notes if (m_model->getValueQuantization() != 0.0) { h = y - getYForValue(v, i->value + m_model->getValueQuantization()); if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; } // GF: this is not quite correct if (pos.y() >= y - 4 && pos.y() <= y + h) { note = *i; break; } } if (i == points.end()) return tr("No local points"); RealTime rt = RealTime::frame2RealTime(note.frame, m_model->getSampleRate()); RealTime rd = RealTime::frame2RealTime(note.duration, m_model->getSampleRate()); QString pitchText; if (shouldConvertMIDIToHz()) { int mnote = lrintf(note.value); int cents = lrintf((note.value - mnote) * 100); float freq = Pitch::getFrequencyForPitch(mnote, cents); pitchText = tr("%1 (%2, %3 Hz)") .arg(Pitch::getPitchLabel(mnote, cents)) .arg(mnote) .arg(freq); } else if (getScaleUnits() == "Hz") { pitchText = tr("%1 Hz (%2, %3)") .arg(note.value) .arg(Pitch::getPitchLabelForFrequency(note.value)) .arg(Pitch::getPitchForFrequency(note.value)); } else { pitchText = tr("%1 %2") .arg(note.value).arg(getScaleUnits()); } QString text; if (note.label == "") { text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label")) .arg(rt.toText(true).c_str()) .arg(pitchText) .arg(rd.toText(true).c_str()); } else { text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4")) .arg(rt.toText(true).c_str()) .arg(pitchText) .arg(rd.toText(true).c_str()) .arg(note.label); } pos = QPoint(v->getXForFrame(note.frame), getYForValue(v, note.value)); return text; } bool FlexiNoteLayer::snapToFeatureFrame(View *v, int &frame, size_t &resolution, SnapType snap) const { if (!m_model) { return Layer::snapToFeatureFrame(v, frame, resolution, snap); } resolution = m_model->getResolution(); FlexiNoteModel::PointList points; if (snap == SnapNeighbouring) { points = getLocalPoints(v, v->getXForFrame(frame)); if (points.empty()) return false; frame = points.begin()->frame; return true; } points = m_model->getPoints(frame, frame); int snapped = frame; bool found = false; for (FlexiNoteModel::PointList::const_iterator i = points.begin(); i != points.end(); ++i) { cerr << "FlexiNoteModel: point at " << i->frame << endl; if (snap == SnapRight) { if (i->frame > frame) { snapped = i->frame; found = true; break; } else if (i->frame + i->duration >= frame) { snapped = i->frame + i->duration; found = true; break; } } else if (snap == SnapLeft) { if (i->frame <= frame) { snapped = i->frame; found = true; // don't break, as the next may be better } else { break; } } else { // nearest FlexiNoteModel::PointList::const_iterator j = i; ++j; if (j == points.end()) { snapped = i->frame; found = true; break; } else if (j->frame >= frame) { if (j->frame - frame < frame - i->frame) { snapped = j->frame; } else { snapped = i->frame; } found = true; break; } } } cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl; frame = snapped; return found; } void FlexiNoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const { min = 0.0; max = 0.0; log = false; QString queryUnits; if (shouldConvertMIDIToHz()) queryUnits = "Hz"; else queryUnits = getScaleUnits(); if (shouldAutoAlign()) { if (!v->getValueExtents(queryUnits, min, max, log)) { min = m_model->getValueMinimum(); max = m_model->getValueMaximum(); if (shouldConvertMIDIToHz()) { min = Pitch::getFrequencyForPitch(lrintf(min)); max = Pitch::getFrequencyForPitch(lrintf(max + 1)); } #ifdef DEBUG_NOTE_LAYER cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; #endif } else if (log) { LogRange::mapRange(min, max); #ifdef DEBUG_NOTE_LAYER cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; #endif } } else { getDisplayExtents(min, max); if (m_verticalScale == MIDIRangeScale) { min = Pitch::getFrequencyForPitch(0); max = Pitch::getFrequencyForPitch(70); } else if (shouldConvertMIDIToHz()) { min = Pitch::getFrequencyForPitch(lrintf(min)); max = Pitch::getFrequencyForPitch(lrintf(max + 1)); } if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { LogRange::mapRange(min, max); log = true; } } if (max == min) max = min + 1.0; } int FlexiNoteLayer::getYForValue(View *v, float val) const { float min = 0.0, max = 0.0; bool logarithmic = false; int h = v->height(); getScaleExtents(v, min, max, logarithmic); #ifdef DEBUG_NOTE_LAYER cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; #endif if (shouldConvertMIDIToHz()) { val = Pitch::getFrequencyForPitch(lrintf(val), lrintf((val - lrintf(val)) * 100)); #ifdef DEBUG_NOTE_LAYER cerr << "shouldConvertMIDIToHz true, val now = " << val << endl; #endif } if (logarithmic) { val = LogRange::map(val); #ifdef DEBUG_NOTE_LAYER cerr << "logarithmic true, val now = " << val << endl; #endif } int y = int(h - ((val - min) * h) / (max - min)) - 1; #ifdef DEBUG_NOTE_LAYER cerr << "y = " << y << endl; #endif return y; } float FlexiNoteLayer::getValueForY(View *v, int y) const { float min = 0.0, max = 0.0; bool logarithmic = false; int h = v->height(); getScaleExtents(v, min, max, logarithmic); float val = min + (float(h - y) * float(max - min)) / h; if (logarithmic) { val = powf(10.f, val); } if (shouldConvertMIDIToHz()) { val = Pitch::getPitchForFrequency(val); } return val; } bool FlexiNoteLayer::shouldAutoAlign() const { if (!m_model) return false; return (m_verticalScale == AutoAlignScale); } void FlexiNoteLayer::paint(View *v, QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) return; int sampleRate = m_model->getSampleRate(); if (!sampleRate) return; // Profiler profiler("FlexiNoteLayer::paint", true); int x0 = rect.left(), x1 = rect.right(); long frame0 = v->getFrameForX(x0); long frame1 = v->getFrameForX(x1); FlexiNoteModel::PointList points(m_model->getPoints(frame0, frame1)); if (points.empty()) return; paint.setPen(getBaseQColor()); QColor brushColour(getBaseQColor()); brushColour.setAlpha(80); // SVDEBUG << "FlexiNoteLayer::paint: resolution is " // << m_model->getResolution() << " frames" << endl; float min = m_model->getValueMinimum(); float max = m_model->getValueMaximum(); if (max == min) max = min + 1.0; QPoint localPos; FlexiNoteModel::Point illuminatePoint(0); bool shouldIlluminate = false; if (v->shouldIlluminateLocalFeatures(this, localPos)) { shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), illuminatePoint); } paint.save(); paint.setRenderHint(QPainter::Antialiasing, false); for (FlexiNoteModel::PointList::const_iterator i = points.begin(); i != points.end(); ++i) { const FlexiNoteModel::Point &p(*i); int x = v->getXForFrame(p.frame); int y = getYForValue(v, p.value); int w = v->getXForFrame(p.frame + p.duration) - x; int h = NOTE_HEIGHT; //GF: larger notes if (m_model->getValueQuantization() != 0.0) { h = y - getYForValue(v, p.value + m_model->getValueQuantization()); if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes } if (w < 1) w = 1; paint.setPen(getBaseQColor()); paint.setBrush(brushColour); // if (shouldIlluminate && // // "illuminatePoint == p" // !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) && // !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) { // // paint.setPen(v->getForeground()); // paint.setBrush(v->getForeground()); // // QString vlabel = QString("%1%2").arg(p.value).arg(m_model->getScaleUnits()); // v->drawVisibleText(paint, // x - paint.fontMetrics().width(vlabel) - 2, // y + paint.fontMetrics().height()/2 // - paint.fontMetrics().descent(), // vlabel, View::OutlinedText); // // QString hlabel = RealTime::frame2RealTime // (p.frame, m_model->getSampleRate()).toText(true).c_str(); // v->drawVisibleText(paint, // x, // y - h/2 - paint.fontMetrics().descent() - 2, // hlabel, View::OutlinedText); // } paint.drawRect(x, y - h/2, w, h); } paint.restore(); } int FlexiNoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const { if (!m_model || shouldAutoAlign()) { return 0; } else { if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { return LogNumericalScale().getWidth(v, paint) + 10; // for piano } else { return LinearNumericalScale().getWidth(v, paint); } } } void FlexiNoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const { if (!m_model || m_model->getPoints().empty()) return; QString unit; float min, max; bool logarithmic; int w = getVerticalScaleWidth(v, false, paint); int h = v->height(); getScaleExtents(v, min, max, logarithmic); if (logarithmic) { LogNumericalScale().paintVertical(v, this, paint, 0, min, max); } else { LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); } if (logarithmic && (getScaleUnits() == "Hz")) { PianoScale().paintPianoVertical (v, paint, QRect(w - 10, 0, 10, h), LogRange::unmap(min), LogRange::unmap(max)); paint.drawLine(w, 0, w, h); } if (getScaleUnits() != "") { int mw = w - 5; paint.drawText(5, 5 + paint.fontMetrics().ascent(), TextAbbrev::abbreviate(getScaleUnits(), paint.fontMetrics(), mw)); } } void FlexiNoteLayer::drawStart(View *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; if (!m_model) return; long frame = v->getFrameForX(e->x()); if (frame < 0) frame = 0; frame = frame / m_model->getResolution() * m_model->getResolution(); float value = getValueForY(v, e->y()); m_editingPoint = FlexiNoteModel::Point(frame, value, 0, 0.8, tr("New Point")); m_originalPoint = m_editingPoint; if (m_editingCommand) finish(m_editingCommand); m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Draw Point")); m_editingCommand->addPoint(m_editingPoint); m_editing = true; } void FlexiNoteLayer::drawDrag(View *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; long frame = v->getFrameForX(e->x()); if (frame < 0) frame = 0; frame = frame / m_model->getResolution() * m_model->getResolution(); float newValue = getValueForY(v, e->y()); long newFrame = m_editingPoint.frame; long newDuration = frame - newFrame; if (newDuration < 0) { newFrame = frame; newDuration = -newDuration; } else if (newDuration == 0) { newDuration = 1; } m_editingCommand->deletePoint(m_editingPoint); m_editingPoint.frame = newFrame; m_editingPoint.value = newValue; m_editingPoint.duration = newDuration; m_editingCommand->addPoint(m_editingPoint); } void FlexiNoteLayer::drawEnd(View *, QMouseEvent *) { // SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; if (!m_model || !m_editing) return; finish(m_editingCommand); m_editingCommand = 0; m_editing = false; } void FlexiNoteLayer::eraseStart(View *v, QMouseEvent *e) { if (!m_model) return; if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; if (m_editingCommand) { finish(m_editingCommand); m_editingCommand = 0; } m_editing = true; } void FlexiNoteLayer::eraseDrag(View *v, QMouseEvent *e) { } void FlexiNoteLayer::eraseEnd(View *v, QMouseEvent *e) { if (!m_model || !m_editing) return; m_editing = false; FlexiNoteModel::Point p(0); if (!getPointToDrag(v, e->x(), e->y(), p)) return; if (p.frame != m_editingPoint.frame || p.value != m_editingPoint.value) return; m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Erase Point")); m_editingCommand->deletePoint(m_editingPoint); finish(m_editingCommand); m_editingCommand = 0; m_editing = false; } void FlexiNoteLayer::editStart(View *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; if (!m_model) return; if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; m_originalPoint = FlexiNote(m_editingPoint); if (m_editMode == RightBoundary) { m_dragPointX = v->getXForFrame(m_editingPoint.frame + m_editingPoint.duration); } else { m_dragPointX = v->getXForFrame(m_editingPoint.frame); } m_dragPointY = getYForValue(v, m_editingPoint.value); if (m_editingCommand) { finish(m_editingCommand); m_editingCommand = 0; } m_editing = true; m_dragStartX = e->x(); m_dragStartY = e->y(); long onset = m_originalPoint.frame; long offset = m_originalPoint.frame + m_originalPoint.duration - 1; m_greatestLeftNeighbourFrame = -1; m_smallestRightNeighbourFrame = std::numeric_limits<long>::max(); for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); i != m_model->getPoints().end(); ++i) { FlexiNote currentNote = *i; // left boundary if (currentNote.frame + currentNote.duration - 1 < onset) { m_greatestLeftNeighbourFrame = currentNote.frame + currentNote.duration - 1; } // right boundary if (currentNote.frame > offset) { m_smallestRightNeighbourFrame = currentNote.frame; break; } } std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl; } void FlexiNoteLayer::editDrag(View *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl; if (!m_model || !m_editing) return; int xdist = e->x() - m_dragStartX; int ydist = e->y() - m_dragStartY; int newx = m_dragPointX + xdist; int newy = m_dragPointY + ydist; long dragFrame = v->getFrameForX(newx); if (dragFrame < 0) dragFrame = 0; dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution(); float value = getValueForY(v, newy); if (!m_editingCommand) { m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Drag Point")); } m_editingCommand->deletePoint(m_editingPoint); std::cerr << "edit mode: " << m_editMode << std::endl; switch (m_editMode) { case LeftBoundary : { // left if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; // right if (m_intelligentActions && dragFrame >= m_originalPoint.frame + m_originalPoint.duration) { dragFrame = m_originalPoint.frame + m_originalPoint.duration - 1; } m_editingPoint.frame = dragFrame; m_editingPoint.duration = m_originalPoint.frame - dragFrame + m_originalPoint.duration; break; } case RightBoundary : { // left if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; if (m_intelligentActions && dragFrame >= m_smallestRightNeighbourFrame) dragFrame = m_smallestRightNeighbourFrame - 1; m_editingPoint.duration = dragFrame - m_originalPoint.frame + 1; break; } case DragNote : { // left if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1; // right if (m_intelligentActions && dragFrame + m_originalPoint.duration >= m_smallestRightNeighbourFrame) { dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration; } m_editingPoint.frame = dragFrame; m_editingPoint.value = value; break; } } updateNoteValue(v, m_editingPoint); m_editingCommand->addPoint(m_editingPoint); std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl; } void FlexiNoteLayer::editEnd(View *v, QMouseEvent *e) { // SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; if (!m_model || !m_editing) return; if (m_editingCommand) { QString newName = m_editingCommand->getName(); if (m_editingPoint.frame != m_originalPoint.frame) { if (m_editingPoint.value != m_originalPoint.value) { newName = tr("Edit Point"); } else { newName = tr("Relocate Point"); } } else { newName = tr("Change Point Value"); } m_editingCommand->setName(newName); finish(m_editingCommand); } m_editingCommand = 0; m_editing = false; } void FlexiNoteLayer::splitStart(View *v, QMouseEvent *e) { // GF: note splitting starts (!! remove printing soon) std::cerr << "splitStart" << std::endl; if (!m_model) return; if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; // m_originalPoint = m_editingPoint; // // m_dragPointX = v->getXForFrame(m_editingPoint.frame); // m_dragPointY = getYForValue(v, m_editingPoint.value); if (m_editingCommand) { finish(m_editingCommand); m_editingCommand = 0; } m_editing = true; m_dragStartX = e->x(); m_dragStartY = e->y(); } void FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e) { // GF: note splitting ends. (!! remove printing soon) std::cerr << "splitEnd" << std::endl; if (!m_model || !m_editing || m_editMode != SplitNote) return; int xdist = e->x() - m_dragStartX; int ydist = e->y() - m_dragStartY; if (xdist != 0 || ydist != 0) { std::cerr << "mouse moved" << std::endl; return; } long frame = v->getFrameForX(e->x()); splitNotesAt(v, frame, e); } void FlexiNoteLayer::splitNotesAt(View *v, int frame) { splitNotesAt(v, frame, 0); } void FlexiNoteLayer::splitNotesAt(View *v, int frame, QMouseEvent *e) { FlexiNoteModel::PointList onPoints = m_model->getPoints(frame); if (onPoints.empty()) return; FlexiNote note(*onPoints.begin()); FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand (m_model, tr("Edit Point")); command->deletePoint(note); if (!e || !(e->modifiers() & Qt::ShiftModifier)) { int gap = 0; // MM: I prefer a gap of 0, but we can decide later FlexiNote newNote1(note.frame, note.value, frame - note.frame - gap, note.level, note.label); FlexiNote newNote2(frame, note.value, note.duration - newNote1.duration, note.level, note.label); if (m_intelligentActions) { if (updateNoteValue(v, newNote1)) { command->addPoint(newNote1); } if (updateNoteValue(v, newNote2)) { command->addPoint(newNote2); } } else { command->addPoint(newNote1); command->addPoint(newNote2); } } finish(command); } void FlexiNoteLayer::addNote(View *v, QMouseEvent *e) { std::cerr << "addNote" << std::endl; if (!m_model) return; long duration = 10000; long frame = v->getFrameForX(e->x()); float value = getValueForY(v, e->y()); if (m_intelligentActions) { long smallestRightNeighbourFrame = 0; for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); i != m_model->getPoints().end(); ++i) { FlexiNote currentNote = *i; if (currentNote.frame > frame) { smallestRightNeighbourFrame = currentNote.frame; break; } } duration = std::min(smallestRightNeighbourFrame - frame + 1, duration); duration = (duration > 0) ? duration : 0; } if (!m_intelligentActions || (m_model->getPoints(frame).empty() && duration > 0)) { FlexiNote newNote(frame, value, duration, 100, "new note"); FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand (m_model, tr("Add Point")); command->addPoint(newNote); finish(command); } } SparseTimeValueModel * FlexiNoteLayer::getAssociatedPitchModel(View *v) const { // Better than we used to do, but still not very satisfactory cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl; for (int i = 0; i < v->getLayerCount(); ++i) { Layer *layer = v->getLayer(i); if (layer && !layer->isLayerDormant(v) && layer->getLayerPresentationName() != "candidate") { cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl; SparseTimeValueModel *model = qobject_cast<SparseTimeValueModel *> (layer->getModel()); cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl; if (model && model->getScaleUnits() == "Hz") { cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl; return model; } } } return 0; } void FlexiNoteLayer::snapSelectedNotesToPitchTrack(View *v, Selection s) { if (!m_model) return; FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand (m_model, tr("Snap Notes")); cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl; for (FlexiNoteModel::PointList::iterator i = points.begin(); i != points.end(); ++i) { FlexiNote note(*i); cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.frame << " to " << note.frame + note.duration << endl; if (!s.contains(note.frame) && !s.contains(note.frame + note.duration - 1)) { continue; } FlexiNote newNote(note); command->deletePoint(note); } finish(command); } void FlexiNoteLayer::mergeNotes(View *v, Selection s, bool inclusive) { FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); FlexiNoteModel::PointList::iterator i = points.begin(); if (inclusive) { while (i != points.end() && i->frame + i->duration < s.getStartFrame()) { ++i; } } else { while (i != points.end() && i->frame < s.getStartFrame()) { ++i; } } if (i == points.end()) return; FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand(m_model, tr("Merge Notes")); FlexiNote newNote(*i); while (i != points.end()) { if (inclusive) { if (i->frame >= s.getEndFrame()) break; } else { if (i->frame + i->duration > s.getEndFrame()) break; } newNote.duration = i->frame + i->duration - newNote.frame; command->deletePoint(*i); ++i; } updateNoteValue(v, newNote); command->addPoint(newNote); finish(command); } bool FlexiNoteLayer::updateNoteValue(View *v, FlexiNoteModel::Point ¬e) const { SparseTimeValueModel *model = getAssociatedPitchModel(v); if (!model) return false; std::cerr << model->getTypeName() << std::endl; SparseModel<TimeValuePoint>::PointList dataPoints = model->getPoints(note.frame, note.frame + note.duration); std::cerr << "frame " << note.frame << ": " << dataPoints.size() << " candidate points" << std::endl; if (dataPoints.empty()) return false; std::vector<float> pitchValues; for (SparseModel<TimeValuePoint>::PointList::const_iterator i = dataPoints.begin(); i != dataPoints.end(); ++i) { if (i->frame >= note.frame && i->frame < note.frame + note.duration) { pitchValues.push_back(i->value); } } if (pitchValues.empty()) return false; sort(pitchValues.begin(), pitchValues.end()); size_t size = pitchValues.size(); double median; if (size % 2 == 0) { median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2; } else { median = pitchValues[size/2]; } note.value = median; return true; } void FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e) { // GF: context sensitive cursors // v->setCursor(Qt::ArrowCursor); FlexiNoteModel::Point note(0); if (!getNoteToEdit(v, e->x(), e->y(), note)) { // v->setCursor(Qt::UpArrowCursor); return; } bool closeToLeft = false, closeToRight = false, closeToTop = false, closeToBottom = false; getRelativeMousePosition(v, note, e->x(), e->y(), closeToLeft, closeToRight, closeToTop, closeToBottom); // if (!closeToLeft) return; // if (closeToTop) v->setCursor(Qt::SizeVerCursor); if (closeToLeft) { v->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; } if (closeToRight) { v->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; } if (closeToTop) { v->setCursor(Qt::CrossCursor); m_editMode = DragNote; return; } if (closeToBottom) { v->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; } v->setCursor(Qt::ArrowCursor); // std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl; // v->setCursor(Qt::SizeHorCursor); } void FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point ¬e, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const { // GF: TODO: consoloidate the tolerance values if (!m_model) return; int ctol = 0; int noteStartX = v->getXForFrame(note.frame); int noteEndX = v->getXForFrame(note.frame + note.duration); int noteValueY = getYForValue(v,note.value); int noteStartY = noteValueY - (NOTE_HEIGHT / 2); int noteEndY = noteValueY + (NOTE_HEIGHT / 2); bool closeToNote = false; if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true; if (!closeToNote) return; int tol = NOTE_HEIGHT / 2; if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true; if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true; if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true; if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true; // cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl; } bool FlexiNoteLayer::editOpen(View *v, QMouseEvent *e) { std::cerr << "Opening note editor dialog" << std::endl; if (!m_model) return false; FlexiNoteModel::Point note(0); if (!getPointToDrag(v, e->x(), e->y(), note)) return false; // FlexiNoteModel::Point note = *points.begin(); ItemEditDialog *dialog = new ItemEditDialog (m_model->getSampleRate(), ItemEditDialog::ShowTime | ItemEditDialog::ShowDuration | ItemEditDialog::ShowValue | ItemEditDialog::ShowText, getScaleUnits()); dialog->setFrameTime(note.frame); dialog->setValue(note.value); dialog->setFrameDuration(note.duration); dialog->setText(note.label); if (dialog->exec() == QDialog::Accepted) { FlexiNoteModel::Point newNote = note; newNote.frame = dialog->getFrameTime(); newNote.value = dialog->getValue(); newNote.duration = dialog->getFrameDuration(); newNote.label = dialog->getText(); FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand (m_model, tr("Edit Point")); command->deletePoint(note); command->addPoint(newNote); finish(command); } delete dialog; return true; } void FlexiNoteLayer::moveSelection(Selection s, size_t newStartFrame) { if (!m_model) return; FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand(m_model, tr("Drag Selection")); FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); for (FlexiNoteModel::PointList::iterator i = points.begin(); i != points.end(); ++i) { if (s.contains(i->frame)) { FlexiNoteModel::Point newPoint(*i); newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); command->deletePoint(*i); command->addPoint(newPoint); } } finish(command); } void FlexiNoteLayer::resizeSelection(Selection s, Selection newSize) { if (!m_model) return; FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand(m_model, tr("Resize Selection")); FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); double ratio = double(newSize.getEndFrame() - newSize.getStartFrame()) / double(s.getEndFrame() - s.getStartFrame()); for (FlexiNoteModel::PointList::iterator i = points.begin(); i != points.end(); ++i) { if (s.contains(i->frame)) { double targetStart = i->frame; targetStart = newSize.getStartFrame() + double(targetStart - s.getStartFrame()) * ratio; double targetEnd = i->frame + i->duration; targetEnd = newSize.getStartFrame() + double(targetEnd - s.getStartFrame()) * ratio; FlexiNoteModel::Point newPoint(*i); newPoint.frame = lrint(targetStart); newPoint.duration = lrint(targetEnd - targetStart); command->deletePoint(*i); command->addPoint(newPoint); } } finish(command); } void FlexiNoteLayer::deleteSelection(Selection s) { if (!m_model) return; FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points")); FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); for (FlexiNoteModel::PointList::iterator i = points.begin(); i != points.end(); ++i) { if (s.contains(i->frame)) { command->deletePoint(*i); } } finish(command); } void FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to) { if (!m_model) return; FlexiNoteModel::PointList points = m_model->getPoints(s.getStartFrame(), s.getEndFrame()); for (FlexiNoteModel::PointList::iterator i = points.begin(); i != points.end(); ++i) { if (s.contains(i->frame)) { Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label); point.setReferenceFrame(alignToReference(v, i->frame)); to.addPoint(point); } } } bool FlexiNoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */) { if (!m_model) return false; const Clipboard::PointList &points = from.getPoints(); bool realign = false; if (clipboardHasDifferentAlignment(v, from)) { QMessageBox::StandardButton button = QMessageBox::question(v, tr("Re-align pasted items?"), 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?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); if (button == QMessageBox::Cancel) { return false; } if (button == QMessageBox::Yes) { realign = true; } } FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand(m_model, tr("Paste")); for (Clipboard::PointList::const_iterator i = points.begin(); i != points.end(); ++i) { if (!i->haveFrame()) continue; size_t frame = 0; if (!realign) { frame = i->getFrame(); } else { if (i->haveReferenceFrame()) { frame = i->getReferenceFrame(); frame = alignFromReference(v, frame); } else { frame = i->getFrame(); } } FlexiNoteModel::Point newPoint(frame); if (i->haveLabel()) newPoint.label = i->getLabel(); if (i->haveValue()) newPoint.value = i->getValue(); else newPoint.value = (m_model->getValueMinimum() + m_model->getValueMaximum()) / 2; if (i->haveLevel()) newPoint.level = i->getLevel(); if (i->haveDuration()) newPoint.duration = i->getDuration(); else { size_t nextFrame = frame; Clipboard::PointList::const_iterator j = i; for (; j != points.end(); ++j) { if (!j->haveFrame()) continue; if (j != i) break; } if (j != points.end()) { nextFrame = j->getFrame(); } if (nextFrame == frame) { newPoint.duration = m_model->getResolution(); } else { newPoint.duration = nextFrame - frame; } } command->addPoint(newPoint); } finish(command); return true; } void FlexiNoteLayer::addNoteOn(long frame, int pitch, int velocity) { m_pendingNoteOns.insert(FlexiNote(frame, pitch, 0, float(velocity) / 127.0, "")); } void FlexiNoteLayer::addNoteOff(long frame, int pitch) { for (FlexiNoteSet::iterator i = m_pendingNoteOns.begin(); i != m_pendingNoteOns.end(); ++i) { if (lrintf((*i).value) == pitch) { FlexiNote note(*i); m_pendingNoteOns.erase(i); note.duration = frame - note.frame; if (m_model) { FlexiNoteModel::AddPointCommand *c = new FlexiNoteModel::AddPointCommand (m_model, note, tr("Record FlexiNote")); // execute and bundle: CommandHistory::getInstance()->addCommand(c, true, true); } break; } } } void FlexiNoteLayer::abandonNoteOns() { m_pendingNoteOns.clear(); } int FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose) { impose = false; return ColourDatabase::getInstance()->getColourIndex (QString(darkbg ? "White" : "Black")); } void FlexiNoteLayer::toXml(QTextStream &stream, QString indent, QString extraAttributes) const { SingleColourLayer::toXml(stream, indent, extraAttributes + QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ") .arg(m_verticalScale) .arg(m_scaleMinimum) .arg(m_scaleMaximum)); } void FlexiNoteLayer::setProperties(const QXmlAttributes &attributes) { SingleColourLayer::setProperties(attributes); bool ok, alsoOk; VerticalScale scale = (VerticalScale) attributes.value("verticalScale").toInt(&ok); if (ok) setVerticalScale(scale); float min = attributes.value("scaleMinimum").toFloat(&ok); float max = attributes.value("scaleMaximum").toFloat(&alsoOk); // if (ok && alsoOk && min != max) setDisplayExtents(min, max); } void FlexiNoteLayer::setVerticalRangeToNoteRange(View *v) { float minf = std::numeric_limits<float>::max(); float maxf = 0; bool hasNotes = 0; for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin(); i != m_model->getPoints().end(); ++i) { hasNotes = 1; FlexiNote note = *i; if (note.value < minf) minf = note.value; if (note.value > maxf) maxf = note.value; } std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl; if (hasNotes) { v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); // MM: this is a hack because we rely on // * this layer being automatically aligned to layer 1 // * layer one is a log frequency layer. } }