annotate layer/Colour3DPlotExporter.cpp @ 1565:a6a31908bd13 spectrogram-export

Add support for a header line on delimited data output
author Chris Cannam
date Fri, 10 Jan 2020 14:30:26 +0000
parents 62b7699e5bfe
children 77ffd5421627 3943553b95b0
rev   line source
Chris@1554 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1554 2
Chris@1554 3 /*
Chris@1554 4 Sonic Visualiser
Chris@1554 5 An audio file viewer and annotation editor.
Chris@1554 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1554 7
Chris@1554 8 This program is free software; you can redistribute it and/or
Chris@1554 9 modify it under the terms of the GNU General Public License as
Chris@1554 10 published by the Free Software Foundation; either version 2 of the
Chris@1554 11 License, or (at your option) any later version. See the file
Chris@1554 12 COPYING included with this distribution for more information.
Chris@1554 13 */
Chris@1554 14
Chris@1554 15 #include "Colour3DPlotExporter.h"
Chris@1554 16
Chris@1554 17 #include "data/model/EditableDenseThreeDimensionalModel.h"
Chris@1554 18 #include "data/model/FFTModel.h"
Chris@1554 19
Chris@1554 20 #include "VerticalBinLayer.h"
Chris@1554 21
Chris@1556 22 Colour3DPlotExporter::Colour3DPlotExporter(Sources sources, Parameters params) :
Chris@1556 23 m_sources(sources),
Chris@1556 24 m_params(params)
Chris@1556 25 {
Chris@1556 26 SVCERR << "Colour3DPlotExporter::Colour3DPlotExporter: constructed at "
Chris@1556 27 << this << endl;
Chris@1556 28 }
Chris@1556 29
Chris@1556 30 Colour3DPlotExporter::~Colour3DPlotExporter()
Chris@1556 31 {
Chris@1556 32 SVCERR << "Colour3DPlotExporter[" << this << "]::~Colour3DPlotExporter"
Chris@1556 33 << endl;
Chris@1556 34 }
Chris@1556 35
Chris@1556 36 void
Chris@1556 37 Colour3DPlotExporter::discardSources()
Chris@1556 38 {
Chris@1556 39 SVCERR << "Colour3DPlotExporter[" << this << "]::discardSources"
Chris@1556 40 << endl;
Chris@1556 41 QMutexLocker locker(&m_mutex);
Chris@1556 42 m_sources.verticalBinLayer = nullptr;
Chris@1556 43 m_sources.source = {};
Chris@1556 44 m_sources.fft = {};
Chris@1556 45 m_sources.provider = nullptr;
Chris@1556 46 }
Chris@1556 47
Chris@1554 48 QString
Chris@1565 49 Colour3DPlotExporter::getDelimitedDataHeaderLine(QString delimiter,
Chris@1565 50 DataExportOptions) const
Chris@1565 51 {
Chris@1565 52 auto model =
Chris@1565 53 ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1565 54
Chris@1565 55 auto layer = m_sources.verticalBinLayer;
Chris@1565 56 auto provider = m_sources.provider;
Chris@1565 57
Chris@1565 58 if (!model || !layer) {
Chris@1565 59 SVCERR << "ERROR: Colour3DPlotExporter::getDelimitedDataHeaderLine: Source model and layer required" << endl;
Chris@1565 60 return {};
Chris@1565 61 }
Chris@1565 62
Chris@1565 63 int minbin = 0;
Chris@1565 64 int sh = model->getHeight();
Chris@1565 65 int nbins = sh;
Chris@1565 66
Chris@1565 67 if (provider) {
Chris@1565 68
Chris@1565 69 minbin = layer->getIBinForY(provider, provider->getPaintHeight());
Chris@1565 70 if (minbin >= sh) minbin = sh - 1;
Chris@1565 71 if (minbin < 0) minbin = 0;
Chris@1565 72
Chris@1565 73 nbins = layer->getIBinForY(provider, 0) - minbin + 1;
Chris@1565 74 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1565 75 }
Chris@1565 76
Chris@1565 77 QStringList list;
Chris@1565 78
Chris@1565 79 switch (m_params.timestampFormat) {
Chris@1565 80 case TimestampFormat::None:
Chris@1565 81 break;
Chris@1565 82 case TimestampFormat::Frames:
Chris@1565 83 list << "FRAME";
Chris@1565 84 break;
Chris@1565 85 case TimestampFormat::Seconds:
Chris@1565 86 list << "TIME";
Chris@1565 87 break;
Chris@1565 88 }
Chris@1565 89
Chris@1565 90 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1565 91 for (int i = 0; i < nbins/4; ++i) {
Chris@1565 92 list << QString("FREQ %1").arg(i+1)
Chris@1565 93 << QString("MAG %1").arg(i+1);
Chris@1565 94 }
Chris@1565 95 } else {
Chris@1565 96 bool hasValues = model->hasBinValues();
Chris@1565 97 QString unit = (hasValues ? model->getBinValueUnit() : "");
Chris@1565 98 for (int i = minbin; i < minbin + nbins; ++i) {
Chris@1565 99 QString name = model->getBinName(i);
Chris@1565 100 if (name == "") {
Chris@1565 101 if (hasValues) {
Chris@1565 102 if (unit != "") {
Chris@1565 103 name = QString("BIN %1: %2 %3")
Chris@1565 104 .arg(i+1)
Chris@1565 105 .arg(model->getBinValue(i))
Chris@1565 106 .arg(unit);
Chris@1565 107 } else {
Chris@1565 108 name = QString("BIN %1: %2")
Chris@1565 109 .arg(i+1)
Chris@1565 110 .arg(model->getBinValue(i));
Chris@1565 111 }
Chris@1565 112 } else {
Chris@1565 113 name = QString("BIN %1")
Chris@1565 114 .arg(i+1);
Chris@1565 115 }
Chris@1565 116 }
Chris@1565 117 list << name;
Chris@1565 118 }
Chris@1565 119 }
Chris@1565 120
Chris@1565 121 return list.join(delimiter);
Chris@1565 122 }
Chris@1565 123
Chris@1565 124 QString
Chris@1554 125 Colour3DPlotExporter::toDelimitedDataString(QString delimiter,
Chris@1565 126 DataExportOptions,
Chris@1554 127 sv_frame_t startFrame,
Chris@1554 128 sv_frame_t duration) const
Chris@1554 129 {
Chris@1554 130 QMutexLocker locker(&m_mutex);
Chris@1554 131
Chris@1554 132 BinDisplay binDisplay = m_params.binDisplay;
Chris@1554 133
Chris@1554 134 auto model =
Chris@1554 135 ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1554 136 auto fftModel =
Chris@1554 137 ModelById::getAs<FFTModel>(m_sources.fft);
Chris@1554 138
Chris@1554 139 auto layer = m_sources.verticalBinLayer;
Chris@1554 140 auto provider = m_sources.provider;
Chris@1554 141
Chris@1554 142 if (!model || !layer) {
Chris@1554 143 SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: Source model and layer required" << endl;
Chris@1554 144 return {};
Chris@1554 145 }
Chris@1556 146 if ((binDisplay == BinDisplay::PeakFrequencies) && !fftModel) {
Chris@1556 147 SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: FFT model required in peak frequencies mode" << endl;
Chris@1556 148 return {};
Chris@1556 149 }
Chris@1554 150
Chris@1554 151 int minbin = 0;
Chris@1554 152 int sh = model->getHeight();
Chris@1554 153 int nbins = sh;
Chris@1554 154
Chris@1554 155 if (provider) {
Chris@1554 156
Chris@1554 157 minbin = layer->getIBinForY(provider, provider->getPaintHeight());
Chris@1554 158 if (minbin >= sh) minbin = sh - 1;
Chris@1554 159 if (minbin < 0) minbin = 0;
Chris@1554 160
Chris@1554 161 nbins = layer->getIBinForY(provider, 0) - minbin + 1;
Chris@1554 162 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1554 163 }
Chris@1554 164
Chris@1554 165 int w = model->getWidth();
Chris@1554 166
Chris@1554 167 QString s;
Chris@1554 168
Chris@1554 169 for (int i = 0; i < w; ++i) {
Chris@1556 170
Chris@1554 171 sv_frame_t fr = model->getStartFrame() + i * model->getResolution();
Chris@1554 172 if (fr < startFrame || fr >= startFrame + duration) {
Chris@1554 173 continue;
Chris@1554 174 }
Chris@1556 175
Chris@1561 176 //!!! (+ phase layer type)
Chris@1556 177
Chris@1556 178 auto column = model->getColumn(i);
Chris@1556 179 column = ColumnOp::Column(column.data() + minbin,
Chris@1556 180 column.data() + minbin + nbins);
Chris@1561 181
Chris@1561 182 // The scale factor is always applied
Chris@1561 183 column = ColumnOp::applyGain(column, m_params.scaleFactor);
Chris@1556 184
Chris@1554 185 QStringList list;
Chris@1554 186
Chris@1564 187 switch (m_params.timestampFormat) {
Chris@1564 188 case TimestampFormat::None:
Chris@1564 189 break;
Chris@1564 190 case TimestampFormat::Frames:
Chris@1564 191 list << QString("%1").arg(fr);
Chris@1564 192 break;
Chris@1564 193 case TimestampFormat::Seconds:
Chris@1565 194 list << RealTime::frame2RealTime(fr, model->getSampleRate())
Chris@1565 195 .toString().c_str();
Chris@1564 196 break;
Chris@1564 197 }
Chris@1564 198
Chris@1556 199 if (binDisplay == BinDisplay::PeakFrequencies) {
Chris@1556 200
Chris@1556 201 FFTModel::PeakSet peaks = fftModel->getPeakFrequencies
Chris@1558 202 (FFTModel::AllPeaks, i, minbin, minbin + nbins - 1);
Chris@1554 203
Chris@1561 204 // We don't apply normalisation or gain to the output, but
Chris@1561 205 // we *do* perform thresholding when exporting the
Chris@1561 206 // peak-frequency spectrogram, to give the user an
Chris@1561 207 // opportunity to cut irrelevant peaks. And to make that
Chris@1561 208 // match the display, we have to apply both normalisation
Chris@1561 209 // and gain locally for thresholding
Chris@1561 210
Chris@1561 211 auto toTest = ColumnOp::normalize(column, m_params.normalization);
Chris@1561 212 toTest = ColumnOp::applyGain(toTest, m_params.gain);
Chris@1561 213
Chris@1556 214 for (const auto &p: peaks) {
Chris@1556 215
Chris@1556 216 int bin = p.first;
Chris@1561 217
Chris@1561 218 if (toTest[bin - minbin] < m_params.threshold) {
Chris@1561 219 continue;
Chris@1561 220 }
Chris@1561 221
Chris@1556 222 double freq = p.second;
Chris@1561 223 double value = column[bin - minbin];
Chris@1561 224
Chris@1561 225 list << QString("%1").arg(freq) << QString("%1").arg(value);
Chris@1556 226 }
Chris@1556 227
Chris@1556 228 } else {
Chris@1556 229
Chris@1556 230 if (binDisplay == BinDisplay::PeakBins) {
Chris@1556 231 column = ColumnOp::peakPick(column);
Chris@1556 232 }
Chris@1556 233
Chris@1556 234 for (auto value: column) {
Chris@1556 235 list << QString("%1").arg(value);
Chris@1556 236 }
Chris@1556 237 }
Chris@1556 238
Chris@1554 239 s += list.join(delimiter) + "\n";
Chris@1554 240 }
Chris@1554 241
Chris@1554 242 return s;
Chris@1554 243 }
Chris@1554 244