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@1593
|
48 QVector<QString>
|
Chris@1593
|
49 Colour3DPlotExporter::getStringExportHeaders(DataExportOptions opts) const
|
Chris@1565
|
50 {
|
Chris@1565
|
51 auto model =
|
Chris@1565
|
52 ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
|
Chris@1565
|
53
|
Chris@1565
|
54 auto layer = m_sources.verticalBinLayer;
|
Chris@1565
|
55 auto provider = m_sources.provider;
|
Chris@1565
|
56
|
Chris@1565
|
57 if (!model || !layer) {
|
Chris@1565
|
58 SVCERR << "ERROR: Colour3DPlotExporter::getDelimitedDataHeaderLine: Source model and layer required" << endl;
|
Chris@1565
|
59 return {};
|
Chris@1565
|
60 }
|
Chris@1565
|
61
|
Chris@1565
|
62 int minbin = 0;
|
Chris@1565
|
63 int sh = model->getHeight();
|
Chris@1565
|
64 int nbins = sh;
|
Chris@1565
|
65
|
Chris@1565
|
66 if (provider) {
|
Chris@1565
|
67
|
Chris@1565
|
68 minbin = layer->getIBinForY(provider, provider->getPaintHeight());
|
Chris@1565
|
69 if (minbin >= sh) minbin = sh - 1;
|
Chris@1565
|
70 if (minbin < 0) minbin = 0;
|
Chris@1565
|
71
|
Chris@1565
|
72 nbins = layer->getIBinForY(provider, 0) - minbin + 1;
|
Chris@1565
|
73 if (minbin + nbins > sh) nbins = sh - minbin;
|
Chris@1565
|
74 }
|
Chris@1565
|
75
|
Chris@1593
|
76 QVector<QString> headers;
|
Chris@1565
|
77
|
Chris@1568
|
78 if (opts & DataExportAlwaysIncludeTimestamp) {
|
Chris@1568
|
79 if (opts & DataExportWriteTimeInFrames) {
|
Chris@1593
|
80 headers << "FRAME";
|
Chris@1568
|
81 } else {
|
Chris@1593
|
82 headers << "TIME";
|
Chris@1568
|
83 }
|
Chris@1565
|
84 }
|
Chris@1565
|
85
|
Chris@1565
|
86 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
|
Chris@1565
|
87 for (int i = 0; i < nbins/4; ++i) {
|
Chris@1593
|
88 headers << QString("FREQ %1").arg(i+1)
|
Chris@1565
|
89 << QString("MAG %1").arg(i+1);
|
Chris@1565
|
90 }
|
Chris@1565
|
91 } else {
|
Chris@1565
|
92 bool hasValues = model->hasBinValues();
|
Chris@1565
|
93 QString unit = (hasValues ? model->getBinValueUnit() : "");
|
Chris@1565
|
94 for (int i = minbin; i < minbin + nbins; ++i) {
|
Chris@1565
|
95 QString name = model->getBinName(i);
|
Chris@1565
|
96 if (name == "") {
|
Chris@1565
|
97 if (hasValues) {
|
Chris@1565
|
98 if (unit != "") {
|
Chris@1565
|
99 name = QString("BIN %1: %2 %3")
|
Chris@1565
|
100 .arg(i+1)
|
Chris@1565
|
101 .arg(model->getBinValue(i))
|
Chris@1565
|
102 .arg(unit);
|
Chris@1565
|
103 } else {
|
Chris@1565
|
104 name = QString("BIN %1: %2")
|
Chris@1565
|
105 .arg(i+1)
|
Chris@1565
|
106 .arg(model->getBinValue(i));
|
Chris@1565
|
107 }
|
Chris@1565
|
108 } else {
|
Chris@1565
|
109 name = QString("BIN %1")
|
Chris@1565
|
110 .arg(i+1);
|
Chris@1565
|
111 }
|
Chris@1565
|
112 }
|
Chris@1593
|
113 headers << name;
|
Chris@1565
|
114 }
|
Chris@1565
|
115 }
|
Chris@1565
|
116
|
Chris@1593
|
117 return headers;
|
Chris@1565
|
118 }
|
Chris@1565
|
119
|
Chris@1593
|
120 QVector<QVector<QString>>
|
Chris@1593
|
121 Colour3DPlotExporter::toStringExportRows(DataExportOptions opts,
|
Chris@1593
|
122 sv_frame_t startFrame,
|
Chris@1593
|
123 sv_frame_t duration) const
|
Chris@1554
|
124 {
|
Chris@1554
|
125 QMutexLocker locker(&m_mutex);
|
Chris@1554
|
126
|
Chris@1554
|
127 BinDisplay binDisplay = m_params.binDisplay;
|
Chris@1554
|
128
|
Chris@1554
|
129 auto model =
|
Chris@1554
|
130 ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
|
Chris@1554
|
131 auto fftModel =
|
Chris@1554
|
132 ModelById::getAs<FFTModel>(m_sources.fft);
|
Chris@1554
|
133
|
Chris@1554
|
134 auto layer = m_sources.verticalBinLayer;
|
Chris@1554
|
135 auto provider = m_sources.provider;
|
Chris@1554
|
136
|
Chris@1554
|
137 if (!model || !layer) {
|
Chris@1554
|
138 SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: Source model and layer required" << endl;
|
Chris@1554
|
139 return {};
|
Chris@1554
|
140 }
|
Chris@1556
|
141 if ((binDisplay == BinDisplay::PeakFrequencies) && !fftModel) {
|
Chris@1556
|
142 SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: FFT model required in peak frequencies mode" << endl;
|
Chris@1556
|
143 return {};
|
Chris@1556
|
144 }
|
Chris@1554
|
145
|
Chris@1554
|
146 int minbin = 0;
|
Chris@1554
|
147 int sh = model->getHeight();
|
Chris@1554
|
148 int nbins = sh;
|
Chris@1554
|
149
|
Chris@1554
|
150 if (provider) {
|
Chris@1554
|
151
|
Chris@1554
|
152 minbin = layer->getIBinForY(provider, provider->getPaintHeight());
|
Chris@1554
|
153 if (minbin >= sh) minbin = sh - 1;
|
Chris@1554
|
154 if (minbin < 0) minbin = 0;
|
Chris@1554
|
155
|
Chris@1554
|
156 nbins = layer->getIBinForY(provider, 0) - minbin + 1;
|
Chris@1554
|
157 if (minbin + nbins > sh) nbins = sh - minbin;
|
Chris@1554
|
158 }
|
Chris@1554
|
159
|
Chris@1554
|
160 int w = model->getWidth();
|
Chris@1554
|
161
|
Chris@1593
|
162 QVector<QVector<QString>> rows;
|
Chris@1554
|
163
|
Chris@1554
|
164 for (int i = 0; i < w; ++i) {
|
Chris@1556
|
165
|
Chris@1554
|
166 sv_frame_t fr = model->getStartFrame() + i * model->getResolution();
|
Chris@1554
|
167 if (fr < startFrame || fr >= startFrame + duration) {
|
Chris@1554
|
168 continue;
|
Chris@1554
|
169 }
|
Chris@1556
|
170
|
Chris@1561
|
171 //!!! (+ phase layer type)
|
Chris@1556
|
172
|
Chris@1556
|
173 auto column = model->getColumn(i);
|
Chris@1556
|
174 column = ColumnOp::Column(column.data() + minbin,
|
Chris@1556
|
175 column.data() + minbin + nbins);
|
Chris@1561
|
176
|
Chris@1561
|
177 // The scale factor is always applied
|
Chris@1561
|
178 column = ColumnOp::applyGain(column, m_params.scaleFactor);
|
Chris@1556
|
179
|
Chris@1593
|
180 QVector<QString> row;
|
Chris@1593
|
181
|
Chris@1568
|
182 if (opts & DataExportAlwaysIncludeTimestamp) {
|
Chris@1568
|
183 if (opts & DataExportWriteTimeInFrames) {
|
Chris@1593
|
184 row << QString("%1").arg(fr);
|
Chris@1568
|
185 } else {
|
Chris@1593
|
186 row << RealTime::frame2RealTime(fr, model->getSampleRate())
|
Chris@1568
|
187 .toString().c_str();
|
Chris@1568
|
188 }
|
Chris@1564
|
189 }
|
Chris@1564
|
190
|
Chris@1556
|
191 if (binDisplay == BinDisplay::PeakFrequencies) {
|
Chris@1556
|
192
|
Chris@1556
|
193 FFTModel::PeakSet peaks = fftModel->getPeakFrequencies
|
Chris@1558
|
194 (FFTModel::AllPeaks, i, minbin, minbin + nbins - 1);
|
Chris@1554
|
195
|
Chris@1561
|
196 // We don't apply normalisation or gain to the output, but
|
Chris@1561
|
197 // we *do* perform thresholding when exporting the
|
Chris@1561
|
198 // peak-frequency spectrogram, to give the user an
|
Chris@1561
|
199 // opportunity to cut irrelevant peaks. And to make that
|
Chris@1561
|
200 // match the display, we have to apply both normalisation
|
Chris@1561
|
201 // and gain locally for thresholding
|
Chris@1561
|
202
|
Chris@1561
|
203 auto toTest = ColumnOp::normalize(column, m_params.normalization);
|
Chris@1561
|
204 toTest = ColumnOp::applyGain(toTest, m_params.gain);
|
Chris@1561
|
205
|
Chris@1556
|
206 for (const auto &p: peaks) {
|
Chris@1556
|
207
|
Chris@1556
|
208 int bin = p.first;
|
Chris@1561
|
209
|
Chris@1561
|
210 if (toTest[bin - minbin] < m_params.threshold) {
|
Chris@1561
|
211 continue;
|
Chris@1561
|
212 }
|
Chris@1561
|
213
|
Chris@1556
|
214 double freq = p.second;
|
Chris@1561
|
215 double value = column[bin - minbin];
|
Chris@1561
|
216
|
Chris@1593
|
217 row << QString("%1").arg(freq) << QString("%1").arg(value);
|
Chris@1556
|
218 }
|
Chris@1556
|
219
|
Chris@1556
|
220 } else {
|
Chris@1556
|
221
|
Chris@1556
|
222 if (binDisplay == BinDisplay::PeakBins) {
|
Chris@1556
|
223 column = ColumnOp::peakPick(column);
|
Chris@1556
|
224 }
|
Chris@1556
|
225
|
Chris@1556
|
226 for (auto value: column) {
|
Chris@1593
|
227 row << QString("%1").arg(value);
|
Chris@1556
|
228 }
|
Chris@1556
|
229 }
|
Chris@1567
|
230
|
Chris@1593
|
231 if (!row.empty()) {
|
Chris@1593
|
232 rows.push_back(row);
|
Chris@1567
|
233 }
|
Chris@1554
|
234 }
|
Chris@1554
|
235
|
Chris@1593
|
236 return rows;
|
Chris@1554
|
237 }
|
Chris@1554
|
238
|