comparison layer/Colour3DPlotExporter.cpp @ 1566:1f80a514ce29

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