Mercurial > hg > svgui
view 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 |
line wrap: on
line source
/* -*- 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 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. */ #include "Colour3DPlotExporter.h" #include "data/model/EditableDenseThreeDimensionalModel.h" #include "data/model/FFTModel.h" #include "VerticalBinLayer.h" Colour3DPlotExporter::Colour3DPlotExporter(Sources sources, Parameters params) : m_sources(sources), m_params(params) { SVCERR << "Colour3DPlotExporter::Colour3DPlotExporter: constructed at " << this << endl; } Colour3DPlotExporter::~Colour3DPlotExporter() { SVCERR << "Colour3DPlotExporter[" << this << "]::~Colour3DPlotExporter" << endl; } void Colour3DPlotExporter::discardSources() { SVCERR << "Colour3DPlotExporter[" << this << "]::discardSources" << endl; QMutexLocker locker(&m_mutex); m_sources.verticalBinLayer = nullptr; m_sources.source = {}; m_sources.fft = {}; m_sources.provider = nullptr; } QString Colour3DPlotExporter::getDelimitedDataHeaderLine(QString delimiter, DataExportOptions) const { auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source); auto layer = m_sources.verticalBinLayer; auto provider = m_sources.provider; if (!model || !layer) { SVCERR << "ERROR: Colour3DPlotExporter::getDelimitedDataHeaderLine: Source model and layer required" << endl; return {}; } int minbin = 0; int sh = model->getHeight(); int nbins = sh; if (provider) { minbin = layer->getIBinForY(provider, provider->getPaintHeight()); if (minbin >= sh) minbin = sh - 1; if (minbin < 0) minbin = 0; nbins = layer->getIBinForY(provider, 0) - minbin + 1; if (minbin + nbins > sh) nbins = sh - minbin; } QStringList list; switch (m_params.timestampFormat) { case TimestampFormat::None: break; case TimestampFormat::Frames: list << "FRAME"; break; case TimestampFormat::Seconds: list << "TIME"; break; } if (m_params.binDisplay == BinDisplay::PeakFrequencies) { for (int i = 0; i < nbins/4; ++i) { list << QString("FREQ %1").arg(i+1) << QString("MAG %1").arg(i+1); } } else { bool hasValues = model->hasBinValues(); QString unit = (hasValues ? model->getBinValueUnit() : ""); for (int i = minbin; i < minbin + nbins; ++i) { QString name = model->getBinName(i); if (name == "") { if (hasValues) { if (unit != "") { name = QString("BIN %1: %2 %3") .arg(i+1) .arg(model->getBinValue(i)) .arg(unit); } else { name = QString("BIN %1: %2") .arg(i+1) .arg(model->getBinValue(i)); } } else { name = QString("BIN %1") .arg(i+1); } } list << name; } } return list.join(delimiter); } QString Colour3DPlotExporter::toDelimitedDataString(QString delimiter, DataExportOptions, sv_frame_t startFrame, sv_frame_t duration) const { QMutexLocker locker(&m_mutex); BinDisplay binDisplay = m_params.binDisplay; auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source); auto fftModel = ModelById::getAs<FFTModel>(m_sources.fft); auto layer = m_sources.verticalBinLayer; auto provider = m_sources.provider; if (!model || !layer) { SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: Source model and layer required" << endl; return {}; } if ((binDisplay == BinDisplay::PeakFrequencies) && !fftModel) { SVCERR << "ERROR: Colour3DPlotExporter::toDelimitedDataString: FFT model required in peak frequencies mode" << endl; return {}; } int minbin = 0; int sh = model->getHeight(); int nbins = sh; if (provider) { minbin = layer->getIBinForY(provider, provider->getPaintHeight()); if (minbin >= sh) minbin = sh - 1; if (minbin < 0) minbin = 0; nbins = layer->getIBinForY(provider, 0) - minbin + 1; if (minbin + nbins > sh) nbins = sh - minbin; } int w = model->getWidth(); QString s; for (int i = 0; i < w; ++i) { sv_frame_t fr = model->getStartFrame() + i * model->getResolution(); if (fr < startFrame || fr >= startFrame + duration) { continue; } //!!! (+ phase layer type) auto column = model->getColumn(i); column = ColumnOp::Column(column.data() + minbin, column.data() + minbin + nbins); // The scale factor is always applied column = ColumnOp::applyGain(column, m_params.scaleFactor); QStringList list; switch (m_params.timestampFormat) { case TimestampFormat::None: break; case TimestampFormat::Frames: list << QString("%1").arg(fr); break; case TimestampFormat::Seconds: list << RealTime::frame2RealTime(fr, model->getSampleRate()) .toString().c_str(); break; } if (binDisplay == BinDisplay::PeakFrequencies) { FFTModel::PeakSet peaks = fftModel->getPeakFrequencies (FFTModel::AllPeaks, i, minbin, minbin + nbins - 1); // We don't apply normalisation or gain to the output, but // we *do* perform thresholding when exporting the // peak-frequency spectrogram, to give the user an // opportunity to cut irrelevant peaks. And to make that // match the display, we have to apply both normalisation // and gain locally for thresholding auto toTest = ColumnOp::normalize(column, m_params.normalization); toTest = ColumnOp::applyGain(toTest, m_params.gain); for (const auto &p: peaks) { int bin = p.first; if (toTest[bin - minbin] < m_params.threshold) { continue; } double freq = p.second; double value = column[bin - minbin]; list << QString("%1").arg(freq) << QString("%1").arg(value); } } else { if (binDisplay == BinDisplay::PeakBins) { column = ColumnOp::peakPick(column); } for (auto value: column) { list << QString("%1").arg(value); } } s += list.join(delimiter) + "\n"; } return s; }