# HG changeset patch # User Chris Cannam # Date 1574344999 0 # Node ID 045063dcd2bcc09120c385411dc26fda0247899c # Parent e79731086b0f1f2d59ecdaac91bb201f65b435a8# Parent e95cefd4aa81b49dc789f1216ff4058f82ba46ec Merge diff -r e95cefd4aa81 -r 045063dcd2bc layer/NoteLayer.cpp --- a/layer/NoteLayer.cpp Thu Oct 31 15:32:13 2019 +0000 +++ b/layer/NoteLayer.cpp Thu Nov 21 14:03:19 2019 +0000 @@ -48,6 +48,7 @@ NoteLayer::NoteLayer() : SingleColourLayer(), + m_modelUsesHz(true), m_editing(false), m_dragPointX(0), m_dragPointY(0), @@ -86,6 +87,9 @@ if (newModel) { connectSignals(m_model); + + QString unit = newModel->getScaleUnits(); + m_modelUsesHz = (unit.toLower() == "hz"); } m_scaleMinimum = 0; @@ -131,9 +135,7 @@ QString NoteLayer::getScaleUnits() const { - auto model = ModelById::getAs(m_model); - if (model) return model->getScaleUnits(); - else return ""; + return "Hz"; } int @@ -191,8 +193,9 @@ } else if (name == "Scale Units") { auto model = ModelById::getAs(m_model); if (model) { - model->setScaleUnits - (UnitDatabase::getInstance()->getUnitById(value)); + QString unit = UnitDatabase::getInstance()->getUnitById(value); + model->setScaleUnits(unit); + m_modelUsesHz = (unit.toLower() == "hz"); emit modelChanged(m_model); } } else { @@ -215,15 +218,43 @@ return !v->shouldIlluminateLocalFeatures(this, discard); } -bool -NoteLayer::shouldConvertMIDIToHz() const +double +NoteLayer::valueOf(const Event &e) const { - QString unit = getScaleUnits(); - return (unit != "Hz"); -// if (unit == "" || -// unit.startsWith("MIDI") || -// unit.startsWith("midi")) return true; -// return false; + return convertValueFromEventValue(e.getValue()); +} + +Event +NoteLayer::eventWithValue(const Event &e, double value) const +{ + return e.withValue(convertValueToEventValue(value)); +} + +double +NoteLayer::convertValueFromEventValue(float eventValue) const +{ + if (m_modelUsesHz) { + return eventValue; + } else { + double v = eventValue; + if (v < 0) v = 0; + if (v > 127) v = 127; + int p = int(round(v)); + double c = 100.0 * (v - p); + return Pitch::getFrequencyForPitch(p, c); + } +} + +float +NoteLayer::convertValueToEventValue(double value) const +{ + if (m_modelUsesHz) { + return float(value); + } else { + float c = 0; + int p = Pitch::getPitchForFrequency(value, &c); + return float(p) + c / 100.f; + } } bool @@ -232,17 +263,14 @@ { auto model = ModelById::getAs(m_model); if (!model) return false; - min = model->getValueMinimum(); - max = model->getValueMaximum(); - if (shouldConvertMIDIToHz()) { - unit = "Hz"; - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } else unit = getScaleUnits(); + min = convertValueFromEventValue(model->getValueMinimum()); + max = convertValueFromEventValue(model->getValueMaximum()); + min /= 1.06; + max *= 1.06; + unit = "Hz"; - if (m_verticalScale == MIDIRangeScale || - m_verticalScale == LogScale) { + if (m_verticalScale != LinearScale) { logarithmic = true; } @@ -262,20 +290,16 @@ } if (m_scaleMinimum == m_scaleMaximum) { - min = model->getValueMinimum(); - max = model->getValueMaximum(); + QString unit; + bool log = false; + getValueExtents(min, max, log, unit); } else { min = m_scaleMinimum; max = m_scaleMaximum; } - if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } - #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; + SVCERR << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; #endif return true; @@ -298,7 +322,7 @@ m_scaleMaximum = max; #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; + SVCERR << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; #endif emit layerParametersChanged(); @@ -377,7 +401,7 @@ } #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; + SVCERR << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; #endif setDisplayExtents(newmin, newmax); @@ -443,7 +467,7 @@ int nearestDistance = -1; for (const auto &p: onPoints) { - int distance = getYForValue(v, p.getValue()) - y; + int distance = getYForValue(v, valueOf(p)) - y; if (distance < 0) distance = -distance; if (nearestDistance == -1 || distance < nearestDistance) { nearestDistance = distance; @@ -477,12 +501,13 @@ for (i = points.begin(); i != points.end(); ++i) { - int y = getYForValue(v, i->getValue()); + int y = getYForValue(v, valueOf(*i)); int h = 3; if (model->getValueQuantization() != 0.0) { h = y - getYForValue - (v, i->getValue() + model->getValueQuantization()); + (v, convertValueFromEventValue(i->getValue() + + model->getValueQuantization())); if (h < 3) h = 3; } @@ -501,28 +526,27 @@ QString pitchText; - float value = note.getValue(); + if (m_modelUsesHz) { + + float value = note.getValue(); - if (shouldConvertMIDIToHz()) { - - int mnote = int(lrint(value)); - int cents = int(lrint((value - float(mnote)) * 100)); - double 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(value) .arg(Pitch::getPitchLabelForFrequency(value)) .arg(Pitch::getPitchForFrequency(value)); } else { - pitchText = tr("%1 %2") - .arg(value).arg(getScaleUnits()); + + float eventValue = note.getValue(); + double value = convertValueFromEventValue(eventValue); + + int mnote = int(lrint(eventValue)); + int cents = int(lrint((eventValue - float(mnote)) * 100)); + + pitchText = tr("%1 (%2, %3 Hz)") + .arg(Pitch::getPitchLabel(mnote, cents)) + .arg(eventValue) + .arg(value); } QString text; @@ -540,7 +564,8 @@ .arg(note.getLabel()); } - pos = QPoint(v->getXForFrame(note.getFrame()), getYForValue(v, value)); + pos = QPoint(v->getXForFrame(note.getFrame()), + getYForValue(v, valueOf(note))); return text; } @@ -593,24 +618,15 @@ auto model = ModelById::getAs(m_model); if (!model) return; - QString queryUnits; - if (shouldConvertMIDIToHz()) queryUnits = "Hz"; - else queryUnits = getScaleUnits(); - if (shouldAutoAlign()) { - if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) { + if (!v->getVisibleExtentsForUnit("Hz", min, max, log)) { - min = model->getValueMinimum(); - max = model->getValueMaximum(); - - if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } + QString unit; + getValueExtents(min, max, log, unit); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + SVCERR << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; #endif } else if (log) { @@ -618,24 +634,15 @@ LogRange::mapRange(min, max); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + SVCERR << "NoteLayer[" << 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(127); - } else if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } - - if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + if (m_verticalScale != LinearScale) { LogRange::mapRange(min, max); log = true; } @@ -654,27 +661,19 @@ getScaleExtents(v, min, max, logarithmic); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; + SVCERR << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; #endif - if (shouldConvertMIDIToHz()) { - val = Pitch::getFrequencyForPitch(int(lrint(val)), - int(lrint((val - rint(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; + SVCERR << "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; + SVCERR << "y = " << y << endl; #endif return y; } @@ -694,10 +693,6 @@ val = pow(10.0, val); } - if (shouldConvertMIDIToHz()) { - val = Pitch::getPitchForFrequency(val); - } - return val; } @@ -736,20 +731,20 @@ // SVDEBUG << "NoteLayer::paint: resolution is " // << model->getResolution() << " frames" << endl; - double min = model->getValueMinimum(); - double max = model->getValueMaximum(); + double min = convertValueFromEventValue(model->getValueMinimum()); + double max = convertValueFromEventValue(model->getValueMaximum()); if (max == min) max = min + 1.0; QPoint localPos; Event illuminatePoint; bool shouldIlluminate = false; - if (v->shouldIlluminateLocalFeatures(this, localPos)) { + if (m_editing || m_editIsOpen) { + shouldIlluminate = true; + illuminatePoint = m_editingPoint; + } else if (v->shouldIlluminateLocalFeatures(this, localPos)) { shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), illuminatePoint); - } else if (m_editIsOpen) { - shouldIlluminate = true; - illuminatePoint = m_editingPoint; } paint.save(); @@ -761,12 +756,14 @@ const Event &p(*i); int x = v->getXForFrame(p.getFrame()); - int y = getYForValue(v, p.getValue()); + int y = getYForValue(v, valueOf(p)); int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; int h = 3; if (model->getValueQuantization() != 0.0) { - h = y - getYForValue(v, p.getValue() + model->getValueQuantization()); + h = y - getYForValue + (v, convertValueFromEventValue + (p.getValue() + model->getValueQuantization())); if (h < 3) h = 3; } @@ -784,7 +781,17 @@ // which is too new for us #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - QString vlabel = QString("%1%2").arg(p.getValue()).arg(getScaleUnits()); + QString vlabel; + if (m_modelUsesHz) { + vlabel = QString("%1%2") + .arg(p.getValue()) + .arg(model->getScaleUnits()); + } else { + vlabel = QString("%1 %2") + .arg(p.getValue()) + .arg(model->getScaleUnits()); + } + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 @@ -816,7 +823,7 @@ return 0; } - if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + if (m_verticalScale != LinearScale) { return LogNumericalScale().getWidth(v, paint) + 10; // for piano } else { return LinearNumericalScale().getWidth(v, paint); @@ -844,7 +851,7 @@ LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); } - if (logarithmic && (getScaleUnits() == "Hz")) { + if (logarithmic) { PianoScale().paintPianoVertical (v, paint, QRect(w - 10, 0, 10, h), LogRange::unmap(min), @@ -875,8 +882,10 @@ frame = frame / model->getResolution() * model->getResolution(); double value = getValueForY(v, e->y()); + float eventValue = convertValueToEventValue(value); + eventValue = roundf(eventValue); - m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point")); + m_editingPoint = Event(frame, eventValue, 0, 0.8f, tr("New Point")); m_originalPoint = m_editingPoint; if (m_editingCommand) finish(m_editingCommand); @@ -899,6 +908,8 @@ frame = frame / model->getResolution() * model->getResolution(); double newValue = getValueForY(v, e->y()); + float newEventValue = convertValueToEventValue(newValue); + newEventValue = roundf(newEventValue); sv_frame_t newFrame = m_editingPoint.getFrame(); sv_frame_t newDuration = frame - newFrame; @@ -912,8 +923,8 @@ m_editingCommand->remove(m_editingPoint); m_editingPoint = m_editingPoint .withFrame(newFrame) - .withValue(float(newValue)) - .withDuration(newDuration); + .withDuration(newDuration) + .withValue(newEventValue); m_editingCommand->add(m_editingPoint); } @@ -983,7 +994,7 @@ m_originalPoint = m_editingPoint; m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); - m_dragPointY = getYForValue(v, m_editingPoint.getValue()); + m_dragPointY = getYForValue(v, valueOf(m_editingPoint)); if (m_editingCommand) { finish(m_editingCommand); @@ -1012,7 +1023,9 @@ if (frame < 0) frame = 0; frame = frame / model->getResolution() * model->getResolution(); - double value = getValueForY(v, newy); + double newValue = getValueForY(v, newy); + float newEventValue = convertValueToEventValue(newValue); + newEventValue = roundf(newEventValue); if (!m_editingCommand) { m_editingCommand = new ChangeEventsCommand @@ -1022,7 +1035,7 @@ m_editingCommand->remove(m_editingPoint); m_editingPoint = m_editingPoint .withFrame(frame) - .withValue(float(value)); + .withValue(newEventValue); m_editingCommand->add(m_editingPoint); } @@ -1272,7 +1285,9 @@ void NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity) { - m_pendingNoteOns.insert(Event(frame, float(pitch), 0, + double value = Pitch::getFrequencyForPitch(pitch); + float eventValue = convertValueToEventValue(value); + m_pendingNoteOns.insert(Event(frame, eventValue, 0, float(velocity) / 127.f, QString())); } @@ -1285,8 +1300,10 @@ i != m_pendingNoteOns.end(); ++i) { Event p = *i; + double value = valueOf(p); + int eventPitch = Pitch::getPitchForFrequency(value); - if (lrintf(p.getValue()) == pitch) { + if (eventPitch == pitch) { m_pendingNoteOns.erase(i); Event note = p.withDuration(frame - p.getFrame()); if (model) { diff -r e95cefd4aa81 -r 045063dcd2bc layer/NoteLayer.h --- a/layer/NoteLayer.h Thu Oct 31 15:32:13 2019 +0000 +++ b/layer/NoteLayer.h Thu Nov 21 14:03:19 2019 +0000 @@ -27,6 +27,17 @@ class View; class QPainter; +/** + * Layer for displaying and editing notes, i.e. discrete events with + * start time, duration, value that represents pitch, and optionally a + * level that represents velocity. + * + * For the purposes of public API, integration with other classes, and + * display alignment, the y-coordinate (value) of the layer always has + * a unit of Hz. The model itself may have another unit, such as MIDI + * pitch, but the layer always converts to and from Hz behind the + * scenes. + */ class NoteLayer : public SingleColourLayer, public VerticalScaleLayer { @@ -65,8 +76,8 @@ void deleteSelection(Selection s) override; void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override; - bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, - bool interactive) override; + bool paste(LayerGeometryProvider *v, const Clipboard &from, + sv_frame_t frameOffset, bool interactive) override; ModelId getModel() const override { return m_model; } void setModel(ModelId model); // a NoteModel @@ -76,9 +87,9 @@ PropertyType getPropertyType(const PropertyName &) const override; QString getPropertyGroupName(const PropertyName &) const override; int getPropertyRangeAndValue(const PropertyName &, - int *min, int *max, int *deflt) const override; + int *min, int *max, int *deflt) const override; QString getPropertyValueLabel(const PropertyName &, - int value) const override; + int value) const override; void setProperty(const PropertyName &, int value) override; enum VerticalScale { @@ -98,7 +109,7 @@ int getCompletion(LayerGeometryProvider *) const override; bool getValueExtents(double &min, double &max, - bool &log, QString &unit) const override; + bool &log, QString &unit) const override; bool getDisplayExtents(double &min, double &max) const override; bool setDisplayExtents(double min, double max) override; @@ -138,7 +149,6 @@ protected: void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const; - bool shouldConvertMIDIToHz() const; int getDefaultColourHint(bool dark, bool &impose) override; @@ -146,7 +156,14 @@ bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const; + double convertValueFromEventValue(float eventValue) const; + float convertValueToEventValue(double value) const; + + double valueOf(const Event &e) const; + Event eventWithValue(const Event &e, double value) const; + ModelId m_model; + bool m_modelUsesHz; bool m_editing; int m_dragPointX; int m_dragPointY;