Chris@1554: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1554: Chris@1554: /* Chris@1554: Sonic Visualiser Chris@1554: An audio file viewer and annotation editor. Chris@1554: Centre for Digital Music, Queen Mary, University of London. Chris@1554: Chris@1554: This program is free software; you can redistribute it and/or Chris@1554: modify it under the terms of the GNU General Public License as Chris@1554: published by the Free Software Foundation; either version 2 of the Chris@1554: License, or (at your option) any later version. See the file Chris@1554: COPYING included with this distribution for more information. Chris@1554: */ Chris@1554: Chris@1554: #include "Colour3DPlotExporter.h" Chris@1554: Chris@1554: #include "data/model/EditableDenseThreeDimensionalModel.h" Chris@1554: #include "data/model/FFTModel.h" Chris@1554: Chris@1554: #include "VerticalBinLayer.h" Chris@1554: Chris@1556: Colour3DPlotExporter::Colour3DPlotExporter(Sources sources, Parameters params) : Chris@1556: m_sources(sources), Chris@1556: m_params(params) Chris@1556: { Chris@1556: SVCERR << "Colour3DPlotExporter::Colour3DPlotExporter: constructed at " Chris@1556: << this << endl; Chris@1556: } Chris@1556: Chris@1556: Colour3DPlotExporter::~Colour3DPlotExporter() Chris@1556: { Chris@1556: SVCERR << "Colour3DPlotExporter[" << this << "]::~Colour3DPlotExporter" Chris@1556: << endl; Chris@1556: } Chris@1556: Chris@1556: void Chris@1556: Colour3DPlotExporter::discardSources() Chris@1556: { Chris@1556: SVCERR << "Colour3DPlotExporter[" << this << "]::discardSources" Chris@1556: << endl; Chris@1556: QMutexLocker locker(&m_mutex); Chris@1556: m_sources.verticalBinLayer = nullptr; Chris@1556: m_sources.source = {}; Chris@1556: m_sources.fft = {}; Chris@1556: m_sources.provider = nullptr; Chris@1556: } Chris@1556: Chris@1554: QString Chris@1565: Colour3DPlotExporter::getDelimitedDataHeaderLine(QString delimiter, Chris@1568: DataExportOptions opts) const Chris@1565: { Chris@1565: auto model = Chris@1565: ModelById::getAs(m_sources.source); Chris@1565: Chris@1565: auto layer = m_sources.verticalBinLayer; Chris@1565: auto provider = m_sources.provider; Chris@1565: Chris@1565: if (!model || !layer) { Chris@1565: SVCERR << "ERROR: Colour3DPlotExporter::getDelimitedDataHeaderLine: Source model and layer required" << endl; Chris@1565: return {}; Chris@1565: } Chris@1565: Chris@1565: int minbin = 0; Chris@1565: int sh = model->getHeight(); Chris@1565: int nbins = sh; Chris@1565: Chris@1565: if (provider) { Chris@1565: Chris@1565: minbin = layer->getIBinForY(provider, provider->getPaintHeight()); Chris@1565: if (minbin >= sh) minbin = sh - 1; Chris@1565: if (minbin < 0) minbin = 0; Chris@1565: Chris@1565: nbins = layer->getIBinForY(provider, 0) - minbin + 1; Chris@1565: if (minbin + nbins > sh) nbins = sh - minbin; Chris@1565: } Chris@1565: Chris@1565: QStringList list; Chris@1565: Chris@1568: if (opts & DataExportAlwaysIncludeTimestamp) { Chris@1568: if (opts & DataExportWriteTimeInFrames) { Chris@1568: list << "FRAME"; Chris@1568: } else { Chris@1568: list << "TIME"; Chris@1568: } Chris@1565: } Chris@1565: Chris@1565: if (m_params.binDisplay == BinDisplay::PeakFrequencies) { Chris@1565: for (int i = 0; i < nbins/4; ++i) { Chris@1565: list << QString("FREQ %1").arg(i+1) Chris@1565: << QString("MAG %1").arg(i+1); Chris@1565: } Chris@1565: } else { Chris@1565: bool hasValues = model->hasBinValues(); Chris@1565: QString unit = (hasValues ? model->getBinValueUnit() : ""); Chris@1565: for (int i = minbin; i < minbin + nbins; ++i) { Chris@1565: QString name = model->getBinName(i); Chris@1565: if (name == "") { Chris@1565: if (hasValues) { Chris@1565: if (unit != "") { Chris@1565: name = QString("BIN %1: %2 %3") Chris@1565: .arg(i+1) Chris@1565: .arg(model->getBinValue(i)) Chris@1565: .arg(unit); Chris@1565: } else { Chris@1565: name = QString("BIN %1: %2") Chris@1565: .arg(i+1) Chris@1565: .arg(model->getBinValue(i)); Chris@1565: } Chris@1565: } else { Chris@1565: name = QString("BIN %1") Chris@1565: .arg(i+1); Chris@1565: } Chris@1565: } Chris@1565: list << name; Chris@1565: } Chris@1565: } Chris@1565: Chris@1565: return list.join(delimiter); Chris@1565: } Chris@1565: Chris@1565: QString Chris@1554: Colour3DPlotExporter::toDelimitedDataString(QString delimiter, Chris@1568: DataExportOptions opts, Chris@1554: sv_frame_t startFrame, Chris@1554: sv_frame_t duration) const Chris@1554: { Chris@1554: QMutexLocker locker(&m_mutex); Chris@1554: Chris@1554: BinDisplay binDisplay = m_params.binDisplay; Chris@1554: Chris@1554: auto model = Chris@1554: ModelById::getAs(m_sources.source); Chris@1554: auto fftModel = Chris@1554: ModelById::getAs(m_sources.fft); Chris@1554: Chris@1554: auto layer = m_sources.verticalBinLayer; Chris@1554: auto provider = m_sources.provider; Chris@1554: Chris@1554: if (!model || !layer) { Chris@1554: SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: Source model and layer required" << endl; Chris@1554: return {}; Chris@1554: } Chris@1556: if ((binDisplay == BinDisplay::PeakFrequencies) && !fftModel) { Chris@1556: SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: FFT model required in peak frequencies mode" << endl; Chris@1556: return {}; Chris@1556: } Chris@1554: Chris@1554: int minbin = 0; Chris@1554: int sh = model->getHeight(); Chris@1554: int nbins = sh; Chris@1554: Chris@1554: if (provider) { Chris@1554: Chris@1554: minbin = layer->getIBinForY(provider, provider->getPaintHeight()); Chris@1554: if (minbin >= sh) minbin = sh - 1; Chris@1554: if (minbin < 0) minbin = 0; Chris@1554: Chris@1554: nbins = layer->getIBinForY(provider, 0) - minbin + 1; Chris@1554: if (minbin + nbins > sh) nbins = sh - minbin; Chris@1554: } Chris@1554: Chris@1554: int w = model->getWidth(); Chris@1554: Chris@1554: QString s; Chris@1554: Chris@1554: for (int i = 0; i < w; ++i) { Chris@1556: Chris@1554: sv_frame_t fr = model->getStartFrame() + i * model->getResolution(); Chris@1554: if (fr < startFrame || fr >= startFrame + duration) { Chris@1554: continue; Chris@1554: } Chris@1556: Chris@1561: //!!! (+ phase layer type) Chris@1556: Chris@1556: auto column = model->getColumn(i); Chris@1556: column = ColumnOp::Column(column.data() + minbin, Chris@1556: column.data() + minbin + nbins); Chris@1561: Chris@1561: // The scale factor is always applied Chris@1561: column = ColumnOp::applyGain(column, m_params.scaleFactor); Chris@1556: Chris@1554: QStringList list; Chris@1554: Chris@1568: if (opts & DataExportAlwaysIncludeTimestamp) { Chris@1568: if (opts & DataExportWriteTimeInFrames) { Chris@1568: list << QString("%1").arg(fr); Chris@1568: } else { Chris@1568: list << RealTime::frame2RealTime(fr, model->getSampleRate()) Chris@1568: .toString().c_str(); Chris@1568: } Chris@1564: } Chris@1564: Chris@1556: if (binDisplay == BinDisplay::PeakFrequencies) { Chris@1556: Chris@1556: FFTModel::PeakSet peaks = fftModel->getPeakFrequencies Chris@1558: (FFTModel::AllPeaks, i, minbin, minbin + nbins - 1); Chris@1554: Chris@1561: // We don't apply normalisation or gain to the output, but Chris@1561: // we *do* perform thresholding when exporting the Chris@1561: // peak-frequency spectrogram, to give the user an Chris@1561: // opportunity to cut irrelevant peaks. And to make that Chris@1561: // match the display, we have to apply both normalisation Chris@1561: // and gain locally for thresholding Chris@1561: Chris@1561: auto toTest = ColumnOp::normalize(column, m_params.normalization); Chris@1561: toTest = ColumnOp::applyGain(toTest, m_params.gain); Chris@1561: Chris@1556: for (const auto &p: peaks) { Chris@1556: Chris@1556: int bin = p.first; Chris@1561: Chris@1561: if (toTest[bin - minbin] < m_params.threshold) { Chris@1561: continue; Chris@1561: } Chris@1561: Chris@1556: double freq = p.second; Chris@1561: double value = column[bin - minbin]; Chris@1561: Chris@1561: list << QString("%1").arg(freq) << QString("%1").arg(value); Chris@1556: } Chris@1556: Chris@1556: } else { Chris@1556: Chris@1556: if (binDisplay == BinDisplay::PeakBins) { Chris@1556: column = ColumnOp::peakPick(column); Chris@1556: } Chris@1556: Chris@1556: for (auto value: column) { Chris@1556: list << QString("%1").arg(value); Chris@1556: } Chris@1556: } Chris@1567: Chris@1567: if (!list.empty()) { Chris@1567: s += list.join(delimiter) + "\n"; Chris@1567: } Chris@1554: } Chris@1554: Chris@1554: return s; Chris@1554: } Chris@1554: