view layer/Colour3DPlotExporter.cpp @ 1615:911330a28a7c

Where the "below" view represents only a subset of the "above" view, cut off the feature mappings at the outer edges of the "below" view - don't map everything outside this (it would all just map onto the same single points at beginning and end, which is excessive, confusing and not useful)
author Chris Cannam
date Thu, 02 Jul 2020 15:37:43 +0100
parents 32171776fcc9
children
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;
}

QVector<QString>
Colour3DPlotExporter::getStringExportHeaders(DataExportOptions opts) 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;
    }

    QVector<QString> headers;

    if (opts & DataExportAlwaysIncludeTimestamp) {
        if (opts & DataExportWriteTimeInFrames) {
            headers << "FRAME";
        } else {
            headers << "TIME";
        }
    }
    
    if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
        for (int i = 0; i < nbins/4; ++i) {
            headers << 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);
                }
            }
            headers << name;
        }
    }

    return headers;
}

QVector<QVector<QString>>
Colour3DPlotExporter::toStringExportRows(DataExportOptions opts,
                                         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();

    QVector<QVector<QString>> rows;
    
    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);
        
        QVector<QString> row;
 
        if (opts & DataExportAlwaysIncludeTimestamp) {
            if (opts & DataExportWriteTimeInFrames) {
                row << QString("%1").arg(fr);
            } else {
                row << RealTime::frame2RealTime(fr, model->getSampleRate())
                    .toString().c_str();
            }
        }
        
        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];
                
                row << QString("%1").arg(freq) << QString("%1").arg(value);
            }

        } else {
        
            if (binDisplay == BinDisplay::PeakBins) {
                column = ColumnOp::peakPick(column);
            }
        
            for (auto value: column) {
                row << QString("%1").arg(value);
            }
        }

        if (!row.empty()) {
            rows.push_back(row);
        }
    }

    return rows;
}