# HG changeset patch # User Chris Cannam # Date 1525356915 -3600 # Node ID fc9d9f1103fa79930c66455cd620ada16043b164 # Parent 34394e8c2942fefa86478390ca94fcf1d9abc1ef Provide linear horizontal scale in spectrum as well as log; fix bin positioning and colour scale property box updating; ensure proper background colour and visibility of peak lines diff -r 34394e8c2942 -r fc9d9f1103fa files.pri --- a/files.pri Wed May 02 14:27:17 2018 +0100 +++ b/files.pri Thu May 03 15:15:15 2018 +0100 @@ -7,6 +7,8 @@ layer/ColourScale.h \ layer/ColourScaleLayer.h \ layer/FlexiNoteLayer.h \ + layer/HorizontalFrequencyScale.h \ + layer/HorizontalScaleProvider.h \ layer/ImageLayer.h \ layer/ImageRegionFinder.h \ layer/Layer.h \ @@ -94,6 +96,7 @@ layer/ColourMapper.cpp \ layer/ColourScale.cpp \ layer/FlexiNoteLayer.cpp \ + layer/HorizontalFrequencyScale.cpp \ layer/ImageLayer.cpp \ layer/ImageRegionFinder.cpp \ layer/Layer.cpp \ diff -r 34394e8c2942 -r fc9d9f1103fa layer/HorizontalFrequencyScale.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/HorizontalFrequencyScale.cpp Thu May 03 15:15:15 2018 +0100 @@ -0,0 +1,77 @@ +/* -*- 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-2018 Chris Cannam and QMUL. + + 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 "HorizontalFrequencyScale.h" +#include "HorizontalScaleProvider.h" +#include "LayerGeometryProvider.h" + +#include "base/ScaleTickIntervals.h" + +#include + +#include + +int +HorizontalFrequencyScale::getHeight(LayerGeometryProvider *, + QPainter &paint) +{ + return paint.fontMetrics().height() + 10; +} + +void +HorizontalFrequencyScale::paintScale(LayerGeometryProvider *v, + const HorizontalScaleProvider *p, + QPainter &paint, + QRect r, + bool logarithmic) +{ + int x0 = r.x(), y0 = r.y(), x1 = r.x() + r.width(), y1 = r.y() + r.height(); + + paint.drawLine(x0, y0, x1, y0); + + double f0 = p->getFrequencyForX(v, x0 ? x0 : 1); + double f1 = p->getFrequencyForX(v, x1); + + int n = 20; + + auto ticks = + logarithmic ? + ScaleTickIntervals::logarithmic({ f0, f1, n }) : + ScaleTickIntervals::linear({ f0, f1, n }); + + n = int(ticks.size()); + + int marginx = -1; + + for (int i = 0; i < n; ++i) { + + double val = ticks[i].value; + QString label = QString::fromStdString(ticks[i].label); + int tw = paint.fontMetrics().width(label); + + int x = int(round(p->getXForFrequency(v, val))); + + if (x < marginx) continue; + + //!!! todo: pixel scaling (here & elsewhere in these classes) + + paint.drawLine(x, y0, x, y1); + + paint.drawText(x + 5, y0 + paint.fontMetrics().ascent() + 5, label); + + marginx = x + tw + 10; + } +} + diff -r 34394e8c2942 -r fc9d9f1103fa layer/HorizontalFrequencyScale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/HorizontalFrequencyScale.h Thu May 03 15:15:15 2018 +0100 @@ -0,0 +1,35 @@ +/* -*- 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-2018 Chris Cannam and QMUL. + + 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. +*/ + +#ifndef SV_HORIZONTAL_FREQUENCY_SCALE_H +#define SV_HORIZONTAL_FREQUENCY_SCALE_H + +#include + +class QPainter; +class LayerGeometryProvider; +class HorizontalScaleProvider; + +class HorizontalFrequencyScale +{ +public: + int getHeight(LayerGeometryProvider *v, QPainter &paint); + + void paintScale + (LayerGeometryProvider *v, const HorizontalScaleProvider *provider, + QPainter &paint, QRect r, bool logarithmic); +}; + +#endif diff -r 34394e8c2942 -r fc9d9f1103fa layer/LinearNumericalScale.cpp --- a/layer/LinearNumericalScale.cpp Wed May 02 14:27:17 2018 +0100 +++ b/layer/LinearNumericalScale.cpp Thu May 03 15:15:15 2018 +0100 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2013 Chris Cannam and QMUL. + This file copyright 2006-2018 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -15,25 +15,19 @@ #include "LinearNumericalScale.h" #include "VerticalScaleLayer.h" +#include "LayerGeometryProvider.h" #include #include -#include "LayerGeometryProvider.h" - #include "base/ScaleTickIntervals.h" int LinearNumericalScale::getWidth(LayerGeometryProvider *, - QPainter &paint, - bool horizontal) + QPainter &paint) { - if (horizontal) { - return paint.fontMetrics().height() + 10; - } else { - return paint.fontMetrics().width("-000.00") + 10; - } + return paint.fontMetrics().width("-000.00") + 10; } void diff -r 34394e8c2942 -r fc9d9f1103fa layer/LinearNumericalScale.h --- a/layer/LinearNumericalScale.h Wed May 02 14:27:17 2018 +0100 +++ b/layer/LinearNumericalScale.h Thu May 03 15:15:15 2018 +0100 @@ -21,21 +21,15 @@ class QPainter; class LayerGeometryProvider; class VerticalScaleLayer; -class HorizontalScaleProvider; class LinearNumericalScale { public: - int getWidth(LayerGeometryProvider *v, QPainter &paint, - bool horizontal = false); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical (LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minf, double maxf); - - void paintHorizontal - (LayerGeometryProvider *v, const HorizontalScaleProvider *provider, - QPainter &paint, QRect r); }; #endif diff -r 34394e8c2942 -r fc9d9f1103fa layer/LogNumericalScale.cpp --- a/layer/LogNumericalScale.cpp Wed May 02 14:27:17 2018 +0100 +++ b/layer/LogNumericalScale.cpp Thu May 03 15:15:15 2018 +0100 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2013 Chris Cannam and QMUL. + This file copyright 2006-2018 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -15,7 +15,6 @@ #include "LogNumericalScale.h" #include "VerticalScaleLayer.h" -#include "HorizontalScaleProvider.h" #include "LayerGeometryProvider.h" #include "base/LogRange.h" @@ -27,14 +26,9 @@ int LogNumericalScale::getWidth(LayerGeometryProvider *, - QPainter &paint, - bool horizontal) + QPainter &paint) { - if (horizontal) { - return paint.fontMetrics().height() + 10; - } else { - return paint.fontMetrics().width("-000.00") + 10; - } + return paint.fontMetrics().width("-000.00") + 10; } void @@ -85,49 +79,3 @@ } } -void -LogNumericalScale::paintHorizontal(LayerGeometryProvider *v, - const HorizontalScaleProvider *p, - QPainter &paint, - QRect r) -{ - int x0 = r.x(), y0 = r.y(), x1 = r.x() + r.width(), y1 = r.y() + r.height(); - - paint.drawLine(x0, y0, x1, y0); - - double f0 = p->getFrequencyForX(v, x0 ? x0 : 1); - double f1 = p->getFrequencyForX(v, x1); - - cerr << "f0 = " << f0 << " at x " << (x0 ? x0 : 1) << endl; - cerr << "f1 = " << f1 << " at x " << x1 << endl; - - int n = 20; - auto ticks = ScaleTickIntervals::logarithmic({ f0, f1, n }); - n = int(ticks.size()); - - int marginx = -1; - - for (int i = 0; i < n; ++i) { - - double val = ticks[i].value; - QString label = QString::fromStdString(ticks[i].label); - int tw = paint.fontMetrics().width(label); - - cerr << "i = " << i << ", value = " << val << ", tw = " << tw << endl; - - int x = int(round(p->getXForFrequency(v, val))); - - cerr << "x = " << x << endl; - - if (x < marginx) continue; - - //!!! todo: pixel scaling (here & elsewhere in these classes) - - paint.drawLine(x, y0, x, y1); - - paint.drawText(x + 5, y0 + paint.fontMetrics().ascent() + 5, label); - - marginx = x + tw + 10; - } -} - diff -r 34394e8c2942 -r fc9d9f1103fa layer/LogNumericalScale.h --- a/layer/LogNumericalScale.h Wed May 02 14:27:17 2018 +0100 +++ b/layer/LogNumericalScale.h Thu May 03 15:15:15 2018 +0100 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2013 Chris Cannam and QMUL. + This file copyright 2006-2018 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -21,21 +21,15 @@ class QPainter; class LayerGeometryProvider; class VerticalScaleLayer; -class HorizontalScaleProvider; class LogNumericalScale { public: - int getWidth(LayerGeometryProvider *v, QPainter &paint, - bool horizontal = false); + int getWidth(LayerGeometryProvider *v, QPainter &paint); void paintVertical (LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minlog, double maxlog); - - void paintHorizontal - (LayerGeometryProvider *v, const HorizontalScaleProvider *provider, - QPainter &paint, QRect r); }; #endif diff -r 34394e8c2942 -r fc9d9f1103fa layer/SliceLayer.cpp --- a/layer/SliceLayer.cpp Wed May 02 14:27:17 2018 +0100 +++ b/layer/SliceLayer.cpp Thu May 03 15:15:15 2018 +0100 @@ -31,7 +31,7 @@ SliceLayer::SliceLayer() : m_sliceableModel(0), - m_colourMap(0), + m_colourMap(int(ColourMapper::Ice)), m_energyScale(dBScale), m_samplingMode(SampleMean), m_plotStyle(PlotLines), @@ -210,7 +210,19 @@ break; case LogBins: - x = (w * log10(bin + 1)) / log10(count + 1); + // The 0.8 here is an awkward compromise. Our x-coord is + // proportional to log of bin number, with the x-coord "of a + // bin" being that of the left edge of the bin range. We can't + // start counting bins from 0, as that would give us x = -Inf + // and hide the first bin entirely. But if we start from 1, we + // are giving a lot of space to the first bin, which in most + // display modes won't be used because the "point" location + // for that bin is in the middle of it. Yet in some modes + // we'll still want it. A compromise is to count our first bin + // as "a bit less than 1", so that most of it is visible but a + // bit is tactfully cropped at the left edge so it doesn't + // take up so much space. + x = (w * log10(bin + 0.8)) / log10(count + 0.8); break; case InvertedLogBins: @@ -247,7 +259,8 @@ break; case LogBins: - bin = pow(10.0, (x * log10(count + 1)) / w) - 1.0 + eps; + // See comment in getXForBin + bin = pow(10.0, (x * log10(count + 0.8)) / w) - 0.8 + eps; break; case InvertedLogBins: @@ -551,6 +564,17 @@ } } +bool +SliceLayer::hasLightBackground() const +{ + if (usesSolidColour()) { + ColourMapper mapper(m_colourMap, 0, 1); + return mapper.hasLightBackground(); + } else { + return SingleColourLayer::hasLightBackground(); + } +} + Layer::PropertyList SliceLayer::getProperties() const { @@ -595,7 +619,7 @@ if (name == "Scale") return ValueProperty; if (name == "Sampling Mode") return ValueProperty; if (name == "Bin Scale") return ValueProperty; - if (name == "Colour" && m_plotStyle == PlotFilledBlocks) return ValueProperty; + if (name == "Colour" && usesSolidColour()) return ColourMapProperty; return SingleColourLayer::getPropertyType(name); } @@ -653,11 +677,11 @@ val = (m_normalize ? 1 : 0); *deflt = 0; - } else if (name == "Colour" && m_plotStyle == PlotFilledBlocks) { + } else if (name == "Colour" && usesSolidColour()) { *min = 0; *max = ColourMapper::getColourMapCount() - 1; - *deflt = 0; + *deflt = int(ColourMapper::Ice); val = m_colourMap; @@ -703,9 +727,9 @@ QString SliceLayer::getPropertyValueLabel(const PropertyName &name, - int value) const + int value) const { - if (name == "Colour" && m_plotStyle == PlotFilledBlocks) { + if (name == "Colour" && usesSolidColour()) { return ColourMapper::getColourMapName(value); } if (name == "Scale") { @@ -765,7 +789,7 @@ } else if (name == "Threshold") { if (value == -80) setThreshold(0.0f); else setThreshold(float(AudioLevel::dB_to_multiplier(value))); - } else if (name == "Colour" && m_plotStyle == PlotFilledBlocks) { + } else if (name == "Colour" && usesSolidColour()) { setFillColourMap(value); } else if (name == "Scale") { switch (value) { diff -r 34394e8c2942 -r fc9d9f1103fa layer/SliceLayer.h --- a/layer/SliceLayer.h Wed May 02 14:27:17 2018 +0100 +++ b/layer/SliceLayer.h Thu May 03 15:15:15 2018 +0100 @@ -32,8 +32,6 @@ SliceLayer(); ~SliceLayer(); -// virtual void setModel(const Model *model); -// virtual const Model *getModel() const { return m_model; } virtual const Model *getModel() const { return 0; } void setSliceableModel(const Model *model); @@ -49,6 +47,8 @@ return ColourAndBackgroundSignificant; } + virtual bool hasLightBackground() const; + virtual PropertyList getProperties() const; virtual QString getPropertyLabel(const PropertyName &) const; virtual QString getPropertyIconName(const PropertyName &) const; @@ -85,6 +85,8 @@ enum BinScale { LinearBins, LogBins, InvertedLogBins }; + bool usesSolidColour() const { return m_plotStyle == PlotFilledBlocks; } + void setFillColourMap(int); int getFillColourMap() const { return m_colourMap; } diff -r 34394e8c2942 -r fc9d9f1103fa layer/SpectrumLayer.cpp --- a/layer/SpectrumLayer.cpp Wed May 02 14:27:17 2018 +0100 +++ b/layer/SpectrumLayer.cpp Thu May 03 15:15:15 2018 +0100 @@ -26,7 +26,7 @@ #include "ColourMapper.h" #include "PaintAssistant.h" #include "PianoScale.h" -#include "LogNumericalScale.h" +#include "HorizontalFrequencyScale.h" #include #include @@ -304,6 +304,9 @@ { if (!m_sliceableModel) return 0; double bin = getBinForX(v, x); + // we assume the frequency of a bin corresponds to the centre of + // its visual range + bin -= 0.5; return (m_sliceableModel->getSampleRate() * bin) / (m_sliceableModel->getHeight() * 2); } @@ -314,6 +317,8 @@ if (!m_sliceableModel) return 0; double bin = (freq * m_sliceableModel->getHeight() * 2) / m_sliceableModel->getSampleRate(); + // we want the centre of the bin range + bin += 0.5; return getXForBin(v, bin); } @@ -600,27 +605,22 @@ double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj int xorigin = getVerticalScaleWidth(v, false, paint) + 1; - int w = v->getPaintWidth() - xorigin - 1; - - int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5); - if (pkh < 10) pkh = 10; - - int scaleh = LogNumericalScale().getWidth(v, paint, true); - - paint.save(); + int scaleHeight = getHorizontalScaleHeight(v, paint); if (fft && m_showPeaks) { // draw peak lines -// SVDEBUG << "Showing peaks..." << endl; - int col = int(v->getCentreFrame() / fft->getResolution()); paint.save(); paint.setRenderHint(QPainter::Antialiasing, false); - paint.setPen(QColor(160, 160, 160)); //!!! + ColourMapper mapper = + hasLightBackground() ? + ColourMapper(ColourMapper::BlackOnWhite, 0, 1) : + ColourMapper(ColourMapper::WhiteOnBlack, 0, 1); + int peakminbin = 0; int peakmaxbin = fft->getHeight() - 1; double peakmaxfreq = Pitch::getFrequencyForPitch(128); @@ -629,8 +629,6 @@ FFTModel::PeakSet peaks = fft->getPeakFrequencies (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin); - ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1); - BiasCurve curve; getBiasCurve(curve); int cs = int(curve.size()); @@ -660,14 +658,38 @@ (void)getYForValue(v, values[bin], norm); // don't need return value, need norm paint.setPen(mapper.map(norm)); - paint.drawLine(x, 0, x, v->getPaintHeight() - scaleh - pkh - 1); + paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1); } paint.restore(); } + paint.save(); + SliceLayer::paint(v, paint, rect); + + paintHorizontalScale(v, paint, xorigin); + paint.restore(); +} + +int +SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v, + QPainter &paint) const +{ + int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5); + if (pkh < 10) pkh = 10; + + int scaleh = HorizontalFrequencyScale().getHeight(v, paint); + + return pkh + scaleh; +} + +void +SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v, + QPainter &paint, + int xorigin) const +{ //!!! All of this stuff relating to depicting frequencies // (keyboard, crosshairs etc) should be applicable to any slice // layer whose model has a vertical scale unit of Hz. However, @@ -677,17 +699,39 @@ // that could be relevant to Colour3DPlotLayer with unit Hz, but // that's a bigger proposition. - int h = v->getPaintHeight(); + if (!v->getViewManager()->shouldShowHorizontalValueScale()) { + return; + } + + int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano + int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint); + int paintHeight = v->getPaintHeight(); + int paintWidth = v->getPaintWidth(); PianoScale().paintPianoHorizontal (v, this, paint, - QRect(xorigin, h - scaleh - pkh - 1, w + xorigin, pkh)); + QRect(xorigin, paintHeight - totalScaleHeight - 1, + paintWidth - 1, totalScaleHeight - freqScaleHeight)); - LogNumericalScale().paintHorizontal + int scaleLeft = int(getXForBin(v, 1)); + + paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight, + scaleLeft, paintHeight - freqScaleHeight); + + QString hz = tr("Hz"); + int hzw = paint.fontMetrics().width(hz); + if (scaleLeft > hzw + 5) { + paint.drawText + (scaleLeft - hzw - 5, + paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5, + hz); + } + + HorizontalFrequencyScale().paintScale (v, this, paint, - QRect(int(getXForBin(v, 1)), h - scaleh, w + xorigin, scaleh)); - - paint.restore(); + QRect(scaleLeft, paintHeight - freqScaleHeight, + paintWidth, totalScaleHeight), + m_binScale == LogBins); } void diff -r 34394e8c2942 -r fc9d9f1103fa layer/SpectrumLayer.h --- a/layer/SpectrumLayer.h Wed May 02 14:27:17 2018 +0100 +++ b/layer/SpectrumLayer.h Thu May 03 15:15:15 2018 +0100 @@ -46,6 +46,9 @@ std::vector &extents) const override; virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const override; + virtual int getHorizontalScaleHeight(LayerGeometryProvider *, QPainter &) const; + virtual void paintHorizontalScale(LayerGeometryProvider *, QPainter &, int xorigin) const; + virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override; virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override; @@ -96,10 +99,10 @@ virtual void toXml(QTextStream &stream, QString indent = "", QString extraAttributes = "") const override; - virtual double getFrequencyForX(const LayerGeometryProvider *, - double x) const override; - virtual double getXForFrequency(const LayerGeometryProvider *, - double freq) const override; + virtual double getFrequencyForX(const LayerGeometryProvider *, double x) + const override; + virtual double getXForFrequency(const LayerGeometryProvider *, double freq) + const override; protected slots: void preferenceChanged(PropertyContainer::PropertyName name); diff -r 34394e8c2942 -r fc9d9f1103fa view/ViewManager.h --- a/view/ViewManager.h Wed May 02 14:27:17 2018 +0100 +++ b/view/ViewManager.h Thu May 03 15:15:15 2018 +0100 @@ -219,6 +219,9 @@ bool shouldShowVerticalColourScale() const { return m_overlayMode == AllOverlays; } + bool shouldShowHorizontalValueScale() const { // for layers where x != time + return m_overlayMode != NoOverlays; + } bool shouldShowSelectionExtents() const { return m_overlayMode != NoOverlays && m_overlayMode != GlobalOverlays; } diff -r 34394e8c2942 -r fc9d9f1103fa widgets/PropertyBox.cpp --- a/widgets/PropertyBox.cpp Wed May 02 14:27:17 2018 +0100 +++ b/widgets/PropertyBox.cpp Thu May 03 15:15:15 2018 +0100 @@ -251,9 +251,10 @@ cerr << "PropertyBox[" << this << "(\"" << m_container->getPropertyContainerName() << "\")]"; - cerr << "::updatePropertyEditor(\"" << name << "\"):"; - cerr << " value " << value << ", have " << have << ", group \"" - << groupName << "\"" << endl; + cerr << "::updatePropertyEditor(\"" << name << "\", " + << rangeChanged << "):"; + cerr << " type " << type << ", value " << value + << ", have " << have << ", group \"" << groupName << "\"" << endl; #endif QString groupLabel = groupName;