# HG changeset patch # User Chris Cannam # Date 1470405902 -3600 # Node ID 3b84f9bd0048179ea09cec4e30f1fc5c76a6eb82 # Parent 69c84a66727b69b57e27053c9fa28ec205bc0ec1# Parent 6f7a440b6218ddfac9ffdfa3b32b6e706e4c527f Merge work on unified spectrogram and colour 3d plot caching renderer diff -r 69c84a66727b -r 3b84f9bd0048 base/ColumnOp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ColumnOp.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,216 @@ +/* -*- 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-2016 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 COLUMN_OP_H +#define COLUMN_OP_H + +#include "BaseTypes.h" + +#include + +/** + * Display normalization types for columns in e.g. grid plots. + * + * Max1 means to normalize to max value = 1.0. + * Sum1 means to normalize to sum of values = 1.0. + * + * Hybrid means normalize to max = 1.0 and then multiply by + * log10 of the max value, to retain some difference between + * levels of neighbouring columns. + * + * Area normalization is handled separately. + */ +enum class ColumnNormalization { + None, + Max1, + Sum1, + Hybrid +}; + +/** + * Class containing static functions for simple operations on data + * columns, for use by display layers. + */ +class ColumnOp +{ +public: + /** + * Column type. + */ + typedef std::vector Column; + + /** + * Scale the given column using the given gain multiplier. + */ + static Column applyGain(const Column &in, double gain) { + + if (gain == 1.0) { + return in; + } + Column out; + out.reserve(in.size()); + for (auto v: in) { + out.push_back(float(v * gain)); + } + return out; + } + + /** + * Scale an FFT output by half the FFT size. + */ + static Column fftScale(const Column &in, int fftSize) { + return applyGain(in, 2.0 / fftSize); + } + + /** + * Determine whether an index points to a local peak. + */ + static bool isPeak(const Column &in, int ix) { + + if (!in_range_for(in, ix-1)) return false; + if (!in_range_for(in, ix+1)) return false; + if (in[ix] < in[ix+1]) return false; + if (in[ix] < in[ix-1]) return false; + + return true; + } + + /** + * Return a column containing only the local peak values (all + * others zero). + */ + static Column peakPick(const Column &in) { + + std::vector out(in.size(), 0.f); + for (int i = 0; in_range_for(in, i); ++i) { + if (isPeak(in, i)) { + out[i] = in[i]; + } + } + + return out; + } + + /** + * Return a column normalized from the input column according to + * the given normalization scheme. + */ + static Column normalize(const Column &in, ColumnNormalization n) { + + if (n == ColumnNormalization::None) { + return in; + } + + float scale = 1.f; + + if (n == ColumnNormalization::Sum1) { + + float sum = 0.f; + + for (auto v: in) { + sum += v; + } + + if (sum != 0.f) { + scale = 1.f / sum; + } + } else { + + float max = *max_element(in.begin(), in.end()); + + if (n == ColumnNormalization::Max1) { + if (max != 0.f) { + scale = 1.f / max; + } + } else if (n == ColumnNormalization::Hybrid) { + if (max > 0.f) { + scale = log10f(max + 1.f) / max; + } + } + } + + return applyGain(in, scale); + } + + /** + * Distribute the given column into a target vector of a different + * size, optionally using linear interpolation. The binfory vector + * contains a mapping from y coordinate (i.e. index into the + * target vector) to bin (i.e. index into the source column). + */ + static Column distribute(const Column &in, + int h, + const std::vector &binfory, + int minbin, + bool interpolate) { + + std::vector out(h, 0.f); + int bins = int(in.size()); + + for (int y = 0; y < h; ++y) { + + double sy0 = binfory[y] - minbin; + double sy1 = sy0 + 1; + if (y+1 < h) { + sy1 = binfory[y+1] - minbin; + } + + if (interpolate && fabs(sy1 - sy0) < 1.0) { + + double centre = (sy0 + sy1) / 2; + double dist = (centre - 0.5) - rint(centre - 0.5); + int bin = int(centre); + + int other = (dist < 0 ? (bin-1) : (bin+1)); + + if (bin < 0) bin = 0; + if (bin >= bins) bin = bins-1; + + if (other < 0 || other >= bins) { + other = bin; + } + + double prop = 1.0 - fabs(dist); + + double v0 = in[bin]; + double v1 = in[other]; + + out[y] = float(prop * v0 + (1.0 - prop) * v1); + + } else { // not interpolating this one + + int by0 = int(sy0 + 0.0001); + int by1 = int(sy1 + 0.0001); + if (by1 < by0 + 1) by1 = by0 + 1; + if (by1 >= bins) by1 = by1 - 1; + + for (int bin = by0; bin < by1; ++bin) { + + float value = in[bin]; + + if (bin == by0 || value > out[y]) { + out[y] = value; + } + } + } + } + + return out; + } + +}; + +#endif + diff -r 69c84a66727b -r 3b84f9bd0048 base/MagnitudeRange.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/MagnitudeRange.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,83 @@ +/* -*- 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 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 MAGNITUDE_RANGE_H +#define MAGNITUDE_RANGE_H + +#include + +/** + * Maintain a min and max value, and update them when supplied a new + * data point. + */ +class MagnitudeRange +{ +public: + MagnitudeRange() : m_min(0), m_max(0) { } + MagnitudeRange(float min, float max) : m_min(min), m_max(max) { } + + bool operator==(const MagnitudeRange &r) { + return r.m_min == m_min && r.m_max == m_max; + } + bool operator!=(const MagnitudeRange &r) { + return !(*this == r); + } + + bool isSet() const { return (m_min != 0.f || m_max != 0.f); } + void set(float min, float max) { + m_min = min; + m_max = max; + if (m_max < m_min) m_max = m_min; + } + bool sample(float f) { + bool changed = false; + if (isSet()) { + if (f < m_min) { m_min = f; changed = true; } + if (f > m_max) { m_max = f; changed = true; } + } else { + m_max = m_min = f; + changed = true; + } + return changed; + } + bool sample(const std::vector &ff) { + bool changed = false; + for (auto f: ff) { + if (sample(f)) { + changed = true; + } + } + return changed; + } + bool sample(const MagnitudeRange &r) { + bool changed = false; + if (isSet()) { + if (r.m_min < m_min) { m_min = r.m_min; changed = true; } + if (r.m_max > m_max) { m_max = r.m_max; changed = true; } + } else { + m_min = r.m_min; + m_max = r.m_max; + changed = true; + } + return changed; + } + float getMin() const { return m_min; } + float getMax() const { return m_max; } +private: + float m_min; + float m_max; +}; + +#endif diff -r 69c84a66727b -r 3b84f9bd0048 base/RangeMapper.cpp --- a/base/RangeMapper.cpp Wed Apr 27 11:04:32 2016 +0100 +++ b/base/RangeMapper.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -70,6 +70,7 @@ double value = m_minval + ((double(position - m_minpos) / double(m_maxpos - m_minpos)) * (m_maxval - m_minval)); +// cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl; return value; } diff -r 69c84a66727b -r 3b84f9bd0048 data/model/Dense3DModelPeakCache.cpp --- a/data/model/Dense3DModelPeakCache.cpp Wed Apr 27 11:04:32 2016 +0100 +++ b/data/model/Dense3DModelPeakCache.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -17,10 +17,10 @@ #include "base/Profiler.h" -Dense3DModelPeakCache::Dense3DModelPeakCache(DenseThreeDimensionalModel *source, +Dense3DModelPeakCache::Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, int columnsPerPeak) : m_source(source), - m_resolution(columnsPerPeak) + m_columnsPerPeak(columnsPerPeak) { m_cache = new EditableDenseThreeDimensionalModel (source->getSampleRate(), @@ -88,15 +88,29 @@ Profiler profiler("Dense3DModelPeakCache::fillColumn"); if (!in_range_for(m_coverage, column)) { - // see note in sourceModelChanged - if (m_coverage.size() > 0) m_coverage[m_coverage.size()-1] = false; + if (m_coverage.size() > 0) { + // The last peak may have come from an incomplete read, which + // may since have been filled, so reset it + m_coverage[m_coverage.size()-1] = false; + } m_coverage.resize(column + 1, false); } + int sourceWidth = m_source->getWidth(); + Column peak; int n = 0; - for (int i = 0; i < m_resolution; ++i) { - Column here = m_source->getColumn(column * m_resolution + i); + for (int i = 0; i < m_columnsPerPeak; ++i) { + + int sourceColumn = column * m_columnsPerPeak + i; + if (sourceColumn >= sourceWidth) break; + + Column here = m_source->getColumn(sourceColumn); + +// cerr << "Dense3DModelPeakCache::fillColumn(" << column << "): source col " +// << sourceColumn << " of " << sourceWidth +// << " returned " << here.size() << " elts" << endl; + if (i == 0) { peak = here; n = int(peak.size()); diff -r 69c84a66727b -r 3b84f9bd0048 data/model/Dense3DModelPeakCache.h --- a/data/model/Dense3DModelPeakCache.h Wed Apr 27 11:04:32 2016 +0100 +++ b/data/model/Dense3DModelPeakCache.h Fri Aug 05 15:05:02 2016 +0100 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _DENSE_3D_MODEL_PEAK_CACHE_H_ -#define _DENSE_3D_MODEL_PEAK_CACHE_H_ +#ifndef DENSE_3D_MODEL_PEAK_CACHE_H +#define DENSE_3D_MODEL_PEAK_CACHE_H #include "DenseThreeDimensionalModel.h" #include "EditableDenseThreeDimensionalModel.h" @@ -24,7 +24,7 @@ Q_OBJECT public: - Dense3DModelPeakCache(DenseThreeDimensionalModel *source, + Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, int columnsPerPeak); ~Dense3DModelPeakCache(); @@ -45,11 +45,20 @@ } virtual int getResolution() const { - return m_source->getResolution() * m_resolution; + return m_source->getResolution() * m_columnsPerPeak; } + virtual int getColumnsPerPeak() const { + return m_columnsPerPeak; + } + virtual int getWidth() const { - return m_source->getWidth() / m_resolution + 1; + int sourceWidth = m_source->getWidth(); + if ((sourceWidth % m_columnsPerPeak) == 0) { + return sourceWidth / m_columnsPerPeak; + } else { + return sourceWidth / m_columnsPerPeak + 1; + } } virtual int getHeight() const { @@ -87,11 +96,11 @@ void sourceModelAboutToBeDeleted(); private: - DenseThreeDimensionalModel *m_source; + const DenseThreeDimensionalModel *m_source; mutable EditableDenseThreeDimensionalModel *m_cache; mutable std::vector m_coverage; // must be bool, for space efficiency // (vector of bool uses 1-bit elements) - int m_resolution; + int m_columnsPerPeak; bool haveColumn(int column) const; void fillColumn(int column) const; diff -r 69c84a66727b -r 3b84f9bd0048 data/model/DenseThreeDimensionalModel.h --- a/data/model/DenseThreeDimensionalModel.h Wed Apr 27 11:04:32 2016 +0100 +++ b/data/model/DenseThreeDimensionalModel.h Fri Aug 05 15:05:02 2016 +0100 @@ -18,6 +18,7 @@ #include "Model.h" #include "TabularModel.h" +#include "base/ColumnOp.h" #include "base/ZoomConstraint.h" #include "base/RealTime.h" @@ -55,7 +56,7 @@ */ virtual float getMaximumLevel() const = 0; - typedef std::vector Column; + typedef ColumnOp::Column Column; /** * Get data from the given column of bin values. diff -r 69c84a66727b -r 3b84f9bd0048 data/model/FFTModel.cpp --- a/data/model/FFTModel.cpp Wed Apr 27 11:04:32 2016 +0100 +++ b/data/model/FFTModel.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -103,6 +103,18 @@ return move(col); } +FFTModel::Column +FFTModel::getPhases(int x) const +{ + auto cplx = getFFTColumn(x); + Column col; + col.reserve(cplx.size()); + for (auto c: cplx) { + col.push_back(arg(c)); + } + return move(col); +} + float FFTModel::getMagnitudeAt(int x, int y) const { @@ -149,23 +161,6 @@ return true; } -float -FFTModel::getNormalizedMagnitudesAt(int x, float *values, int minbin, int count) const -{ - if (!getMagnitudesAt(x, values, minbin, count)) return false; - if (count == 0) count = getHeight(); - float max = 0.f; - for (int i = 0; i < count; ++i) { - if (values[i] > max) max = values[i]; - } - if (max > 0.f) { - for (int i = 0; i < count; ++i) { - values[i] /= max; - } - } - return max; -} - bool FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const { @@ -348,7 +343,7 @@ } FFTModel::PeakLocationSet -FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) +FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const { Profiler profiler("FFTModel::getPeaks"); @@ -496,7 +491,7 @@ FFTModel::PeakSet FFTModel::getPeakFrequencies(PeakPickType type, int x, - int ymin, int ymax) + int ymin, int ymax) const { Profiler profiler("FFTModel::getPeakFrequencies"); diff -r 69c84a66727b -r 3b84f9bd0048 data/model/FFTModel.h --- a/data/model/FFTModel.h Wed Apr 27 11:04:32 2016 +0100 +++ b/data/model/FFTModel.h Fri Aug 05 15:05:02 2016 +0100 @@ -76,6 +76,7 @@ virtual float getMinimumLevel() const { return 0.f; } // Can't provide virtual float getMaximumLevel() const { return 1.f; } // Can't provide virtual Column getColumn(int x) const; // magnitudes + virtual Column getPhases(int x) const; virtual QString getBinName(int n) const; virtual bool shouldUseLogValueScale() const { return true; } virtual int getCompletion() const { @@ -95,13 +96,14 @@ int getWindowSize() const { return m_windowSize; } int getWindowIncrement() const { return m_windowIncrement; } int getFFTSize() const { return m_fftSize; } + +//!!! review which of these are ever actually called float getMagnitudeAt(int x, int y) const; float getMaximumMagnitudeAt(int x) const; float getPhaseAt(int x, int y) const; void getValuesAt(int x, int y, float &real, float &imaginary) const; bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; - float getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; // returns maximum of unnormalized magnitudes bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) const; bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0) const; @@ -127,13 +129,13 @@ * ymax is zero, getHeight()-1 will be used. */ virtual PeakLocationSet getPeaks(PeakPickType type, int x, - int ymin = 0, int ymax = 0); + int ymin = 0, int ymax = 0) const; /** * Return locations and estimated stable frequencies of peak bins. */ virtual PeakSet getPeakFrequencies(PeakPickType type, int x, - int ymin = 0, int ymax = 0); + int ymin = 0, int ymax = 0) const; QString getTypeName() const { return tr("FFT"); } diff -r 69c84a66727b -r 3b84f9bd0048 plugin/plugins/SamplePlayer.cpp --- a/plugin/plugins/SamplePlayer.cpp Wed Apr 27 11:04:32 2016 +0100 +++ b/plugin/plugins/SamplePlayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -571,7 +571,7 @@ if (m_concertA) { ratio *= *m_concertA / 440.f; } - if (m_basePitch && n != *m_basePitch) { + if (m_basePitch && float(n) != *m_basePitch) { ratio *= powf(1.059463094f, float(n) - *m_basePitch); } } diff -r 69c84a66727b -r 3b84f9bd0048 svcore.pro --- a/svcore.pro Wed Apr 27 11:04:32 2016 +0100 +++ b/svcore.pro Fri Aug 05 15:05:02 2016 +0100 @@ -56,10 +56,12 @@ base/AudioPlaySource.h \ base/BaseTypes.h \ base/Clipboard.h \ + base/ColumnOp.h \ base/Command.h \ base/Debug.h \ base/Exceptions.h \ base/LogRange.h \ + base/MagnitudeRange.h \ base/Pitch.h \ base/Playable.h \ base/PlayParameterRepository.h \