407 lines
14 KiB
C++
407 lines
14 KiB
C++
|
// PartialsSpecgramGraphicsScene class implementation
|
||
|
//
|
||
|
// The QGraphicsScene-derived partials spectrogram view
|
||
|
//
|
||
|
// QATSH Copyright 2009 Jean-Philippe MEURET <jpmeuret@free.fr>
|
||
|
|
||
|
#include <limits>
|
||
|
#include <iostream>
|
||
|
|
||
|
#include <QtCore/QObject>
|
||
|
#include <QtCore/QList>
|
||
|
#include <QtCore/QRectF>
|
||
|
#include <QtCore/QModelIndex>
|
||
|
|
||
|
#include <QtGui/QPen>
|
||
|
#include <QtGui/QGraphicsView>
|
||
|
#include <QtGui/QGraphicsItemGroup>
|
||
|
#include <QtGui/QItemSelection>
|
||
|
|
||
|
#include "ATSMath.h"
|
||
|
|
||
|
#include "PartialsSpecgramGraphicsScene.h"
|
||
|
#include "PartialsSpecgramGraphicsFrameItem.h"
|
||
|
|
||
|
#include "ATSModelManager.h"
|
||
|
#include "ATSModel.h"
|
||
|
#include "ATSModelItems.h"
|
||
|
#include "ATSPropertiesProxyModel.h"
|
||
|
#include "ATSPartialsProxyModel.h"
|
||
|
|
||
|
// Ids for graphics items custom data.
|
||
|
enum EPartialGraphItemDataType { eMeanAmplitude };
|
||
|
|
||
|
// Constructors / Destructor ===========================================================
|
||
|
PartialsSpecgramGraphicsScene::PartialsSpecgramGraphicsScene(QObject *parent)
|
||
|
: QGraphicsScene(parent), _pModelMgr(0), _pqvgiPartials(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
PartialsSpecgramGraphicsScene::~PartialsSpecgramGraphicsScene()
|
||
|
{
|
||
|
clear();
|
||
|
}
|
||
|
|
||
|
void PartialsSpecgramGraphicsScene::clear()
|
||
|
{
|
||
|
// Free the locally stored partial data.
|
||
|
if (_pqvgiPartials)
|
||
|
{
|
||
|
for (int nPartInd = 0; nPartInd < _pqvgiPartials->size(); nPartInd++)
|
||
|
if ((*_pqvgiPartials)[nPartInd])
|
||
|
delete (*_pqvgiPartials)[nPartInd];
|
||
|
delete _pqvgiPartials;
|
||
|
_pqvgiPartials = 0;
|
||
|
}
|
||
|
|
||
|
// Normal QGraphicsScene clearing.
|
||
|
QGraphicsScene::clear();
|
||
|
}
|
||
|
|
||
|
// Constructors / Destructor ===========================================================
|
||
|
void PartialsSpecgramGraphicsScene::setModelManager(ATSModelManager* pModelMgr)
|
||
|
{
|
||
|
// Save pointer to model for later.
|
||
|
_pModelMgr = pModelMgr;
|
||
|
|
||
|
// Connect to any model change.
|
||
|
if (_pModelMgr)
|
||
|
{
|
||
|
connect(_pModelMgr->partialsModel(), SIGNAL(modelReset()), this, SLOT(onModelReset()));
|
||
|
connect(_pModelMgr->partialsModel(),
|
||
|
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
||
|
this, SLOT(onDataChanged(const QModelIndex&, const QModelIndex&)));
|
||
|
}
|
||
|
|
||
|
// Connect to selection model change.
|
||
|
connect(_pModelMgr->partialsSelectionModel(),
|
||
|
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||
|
this, SLOT(onSelectionChanged(const QItemSelection&, const QItemSelection&)));
|
||
|
|
||
|
// Load scene from model : create graphic items from partials data
|
||
|
loadFromModel();
|
||
|
}
|
||
|
|
||
|
// Model change slots ==================================================================
|
||
|
void PartialsSpecgramGraphicsScene::onModelReset()
|
||
|
{
|
||
|
//std::cout << "PartialsSpecgramGraphicsScene::onModelReset()" << std::endl;
|
||
|
|
||
|
_pModelMgr->partialsSelectionModel()->clear();
|
||
|
|
||
|
clear();
|
||
|
|
||
|
loadFromModel();
|
||
|
}
|
||
|
|
||
|
void PartialsSpecgramGraphicsScene::onDataChanged(const QModelIndex& qmiTopLeft,
|
||
|
const QModelIndex& qmiBotRight)
|
||
|
{
|
||
|
std::cout << "PartialsSpecgramGraphicsScene::onDataChanged : Not yet implemented" << std::endl;
|
||
|
|
||
|
// TODO : Check really changed rect if useful here ...
|
||
|
|
||
|
// TODO : Update item properties (and add/remove items ?)
|
||
|
}
|
||
|
|
||
|
// Selection management ================================================================
|
||
|
void PartialsSpecgramGraphicsScene::selectPartialFrame(int nPartialIndex, int nFramIndex,
|
||
|
QItemSelectionModel::SelectionFlags selFlags)
|
||
|
{
|
||
|
// std::cout << "PartialsSpecgramGraphicsScene::onSelectionChanged : "
|
||
|
// << "selFlags=" << std::hex << selFlags << std::dec << std::endl;
|
||
|
// Select the entier partial (for the moment).
|
||
|
QModelIndex qmiPartFrame = _pModelMgr->partialsModel()->index(0, nPartialIndex, QModelIndex());
|
||
|
_pModelMgr->partialsSelectionModel()->select(qmiPartFrame, selFlags);
|
||
|
}
|
||
|
|
||
|
void PartialsSpecgramGraphicsScene::onSelectionChanged(const QItemSelection& qisSelected,
|
||
|
const QItemSelection& qisDeselected)
|
||
|
{
|
||
|
std::cout << "PartialsSpecgramGraphicsScene::onSelectionChanged : " << std::endl;
|
||
|
|
||
|
// Note: Selected partials get all white, and the Z-value of associated item groups
|
||
|
// is increased above all the unselected ones in order we always can see them.
|
||
|
// Once deselected, they get back to their initial color (frame amplitude)
|
||
|
// and initial Z-value (mean amplitude of the partial).
|
||
|
|
||
|
// Selected partials.
|
||
|
if (qisSelected.count())
|
||
|
{
|
||
|
foreach (QModelIndex qmiSelected, qisSelected.indexes())
|
||
|
{
|
||
|
const ATSModelItem *pmiSelected =
|
||
|
static_cast<const ATSModelItem*>(qmiSelected.internalPointer());
|
||
|
if (pmiSelected)
|
||
|
{
|
||
|
// std::cout << " Selecting "
|
||
|
// << qmiSelected.row() << ',' << qmiSelected.column()
|
||
|
// << ':' << pmiSelected->type() << std::endl;
|
||
|
switch (pmiSelected->type())
|
||
|
{
|
||
|
case ATSModelItem::ePartial:
|
||
|
{
|
||
|
const QVector<PartialsSpecgramGraphicsFrameItem*>* pqvgiFrames =
|
||
|
(*_pqvgiPartials)[qmiSelected.column()];
|
||
|
// if ((*pqvgiFrames)[0])
|
||
|
// (*pqvgiFrames)[0]->parentItem()->setZValue(_dMaxPartialMeanAmpl*1.1);
|
||
|
for (int nFrameInd = 0; nFrameInd < pqvgiFrames->size(); nFrameInd++)
|
||
|
if ((*pqvgiFrames)[nFrameInd])
|
||
|
(*pqvgiFrames)[nFrameInd]->select(true);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Deselected partials.
|
||
|
if (qisDeselected.count())
|
||
|
{
|
||
|
foreach (QModelIndex qmiDeselected, qisDeselected.indexes())
|
||
|
{
|
||
|
const ATSModelItem *pmiDeselected =
|
||
|
static_cast<const ATSModelItem*>(qmiDeselected.internalPointer());
|
||
|
if (pmiDeselected)
|
||
|
{
|
||
|
// std::cout << " Deselecting "
|
||
|
// << qmiDeselected.row() << ',' << qmiDeselected.column()
|
||
|
// << ':' << pmiDeselected->type() << std::endl;
|
||
|
switch (pmiDeselected->type())
|
||
|
{
|
||
|
case ATSModelItem::ePartial:
|
||
|
{
|
||
|
const QVector<PartialsSpecgramGraphicsFrameItem*>* pqvgiFrames =
|
||
|
(*_pqvgiPartials)[qmiDeselected.column()];
|
||
|
// if ((*pqvgiFrames)[0])
|
||
|
// {
|
||
|
// const double dPartZValue =
|
||
|
// (*pqvgiFrames)[0]->parentItem()->data(eMeanAmplitude).toDouble();
|
||
|
// (*pqvgiFrames)[0]->parentItem()->setZValue(dPartZValue);
|
||
|
// }
|
||
|
for (int nFrameInd = 0; nFrameInd < pqvgiFrames->size(); nFrameInd++)
|
||
|
if ((*pqvgiFrames)[nFrameInd])
|
||
|
(*pqvgiFrames)[nFrameInd]->select(false);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Change contrast / brightness ========================================================
|
||
|
// Set amplitude color contrast in [0, 1]
|
||
|
void PartialsSpecgramGraphicsScene::setAmplitudeContrast(double dContrast)
|
||
|
{
|
||
|
//std::cout << "PartialsSpecgramGraphicsScene::setAmplitudeContrast("
|
||
|
// << dContrast << ")" << std::endl;
|
||
|
|
||
|
clear();
|
||
|
|
||
|
_cmAmplColorMap.setContrast(dContrast);
|
||
|
|
||
|
loadPartials();
|
||
|
|
||
|
emit colorMapChanged(&_cmAmplColorMap);
|
||
|
}
|
||
|
|
||
|
// Set amplitude color brightness in [0, 1]
|
||
|
void PartialsSpecgramGraphicsScene::setAmplitudeBrightness(double dBrightness)
|
||
|
{
|
||
|
//std::cout << "PartialsSpecgramGraphicsScene::setAmplitudeBrightness("
|
||
|
// << dBrightness << ")" << std::endl;
|
||
|
|
||
|
clear();
|
||
|
|
||
|
_cmAmplColorMap.setBrightness(dBrightness);
|
||
|
|
||
|
loadPartials();
|
||
|
|
||
|
emit colorMapChanged(&_cmAmplColorMap);
|
||
|
}
|
||
|
|
||
|
// Load scene from model ===============================================================
|
||
|
void PartialsSpecgramGraphicsScene::loadFromModel()
|
||
|
{
|
||
|
ATSPropertiesProxyModel* pPropsModel = _pModelMgr->propertiesModel();
|
||
|
|
||
|
// Initialize scene rect from model
|
||
|
// (x = time from 0 to sound.duration, y = frequency from sound.minFreq to sound.maxFreq).
|
||
|
const QModelIndex miDuration =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropDuration, QModelIndex());
|
||
|
const double dMaxTime = pPropsModel->data(miDuration, Qt::DisplayRole).toDouble();
|
||
|
const double dMinTime = 0.0;
|
||
|
|
||
|
const QModelIndex miMaxFreq =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropMaxPartialFrequency, QModelIndex());
|
||
|
const double dMaxFreq = pPropsModel->data(miMaxFreq, Qt::DisplayRole).toDouble();
|
||
|
const double dMinFreq = 0.0;
|
||
|
|
||
|
setSceneRect(dMinTime, dMinFreq, dMaxTime - dMinTime, dMaxFreq - dMinFreq);
|
||
|
|
||
|
std::cout << "PartialsSpecgramGraphicsScene::loadFromModel : "
|
||
|
<< "duration = " << dMaxTime
|
||
|
<< ", max. freq. = " << dMaxFreq << std::endl;
|
||
|
|
||
|
// Reset amplitude color map.
|
||
|
const QModelIndex miMaxAmpl =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropMaxPartialAmplitude, QModelIndex());
|
||
|
const double dMinAmpl = 0.0;
|
||
|
const double dMaxAmpl = pPropsModel->data(miMaxAmpl, Qt::DisplayRole).toDouble();
|
||
|
_cmAmplColorMap.reset(ColorMap::eStyleStandard, 256, dMinAmpl, dMaxAmpl);
|
||
|
|
||
|
emit colorMapChanged(&_cmAmplColorMap);
|
||
|
|
||
|
// Reset time ruler spec.
|
||
|
const double dTimeRulerBaseStep = pow(10.0, floor(log10(dMaxTime - dMinTime)));
|
||
|
_rsTimeRulerSpec.reset(RulerSpec::eHorizontal, RulerSpec::ePositive, RulerSpec::ePositiveSide,
|
||
|
dMinTime, dMaxTime, 0.0, dTimeRulerBaseStep, 10.0,
|
||
|
QList<double>() << 2 << 5, QList<double>() << 0.6 << 0.3);
|
||
|
|
||
|
emit timeRangeChanged(&_rsTimeRulerSpec);
|
||
|
|
||
|
// Reset frequency ruler spec.
|
||
|
const double dFreqRulerBaseStep = pow(10.0, floor(log10(dMaxFreq - dMinFreq)));
|
||
|
_rsFreqRulerSpec.reset(RulerSpec::eVertical, RulerSpec::ePositive, RulerSpec::ePositiveSide,
|
||
|
dMinFreq, dMaxFreq, 0.0, dFreqRulerBaseStep, 10.0,
|
||
|
QList<double>() << 2 << 5, QList<double>() << 0.4 << 0.2);
|
||
|
|
||
|
emit frequencyRangeChanged(&_rsFreqRulerSpec);
|
||
|
|
||
|
// Draw partials.
|
||
|
loadPartials();
|
||
|
}
|
||
|
|
||
|
void PartialsSpecgramGraphicsScene::loadPartials()
|
||
|
{
|
||
|
ATSPropertiesProxyModel* pPropsModel = _pModelMgr->propertiesModel();
|
||
|
|
||
|
// Create items from model (1 line for each frame of each partial,
|
||
|
// 1 item group of line items for each partial).
|
||
|
// a) Get the number of partials.
|
||
|
const QModelIndex miNbPartials =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropNbPartials, QModelIndex());
|
||
|
const int nPartials = pPropsModel->data(miNbPartials, Qt::DisplayRole).toInt();
|
||
|
|
||
|
// b) Get the number of frames.
|
||
|
const QModelIndex miNbFrames =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropNbFrames, QModelIndex());
|
||
|
const int nFrames = pPropsModel->data(miNbFrames, Qt::DisplayRole).toInt();
|
||
|
|
||
|
// c) Get the sampling rate.
|
||
|
const QModelIndex miSampRate =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropSamplingRate, QModelIndex());
|
||
|
const double dSampRate = pPropsModel->data(miSampRate, Qt::DisplayRole).toDouble();
|
||
|
|
||
|
// d) Get the frame size (in samples).
|
||
|
const QModelIndex miFrameSize =
|
||
|
pPropsModel->index(0, ATSFilePropertiesItem::eSoundPropFrameSize, QModelIndex());
|
||
|
const int nFrameSize = pPropsModel->data(miFrameSize, Qt::DisplayRole).toInt();
|
||
|
|
||
|
// e) Compute frame duration (s).
|
||
|
const double dFrameDur = nFrameSize / dSampRate;
|
||
|
|
||
|
std::cout << "PartialsSpecgramGraphicsScene::loadPartials : "
|
||
|
<< "partials = " << nPartials
|
||
|
<< ", frames = " << nFrames
|
||
|
<< ", samp. rate = " << dSampRate
|
||
|
<< ", frame size = " << nFrameSize
|
||
|
<< ", frame dur. = " << dFrameDur << std::endl;
|
||
|
|
||
|
// f) For each partial :
|
||
|
_dMaxPartialMeanAmpl = -std::numeric_limits<double>::max();
|
||
|
ATSPartialsProxyModel* pPartsModel = _pModelMgr->partialsModel();
|
||
|
//QGraphicsItemGroup* pgigPartials = new QGraphicsItemGroup;
|
||
|
_pqvgiPartials = new QVector<QVector<PartialsSpecgramGraphicsFrameItem*>* >(nPartials);
|
||
|
for (int nPartInd = 0; nPartInd < nPartials; nPartInd++)
|
||
|
{
|
||
|
// g) Get index of "partial" model item of index "nPartInd".
|
||
|
const QModelIndex miPartial = pPartsModel->index(0, nPartInd, QModelIndex());
|
||
|
|
||
|
// if (nPartInd < 2 || nPartInd >= nPartials - 1)
|
||
|
// std::cout << "Partial #" << nPartInd << " : " << std::endl;
|
||
|
|
||
|
// h) For each frame :
|
||
|
double dAmplSum = 0.0; // Use to compute partial mean amplitude for item Z value.
|
||
|
//QGraphicsItemGroup* pgigPartial = new QGraphicsItemGroup;
|
||
|
(*_pqvgiPartials)[nPartInd] = new QVector<PartialsSpecgramGraphicsFrameItem*>(nFrames);
|
||
|
for (int nFrameInd = 0; nFrameInd < nFrames; nFrameInd++)
|
||
|
{
|
||
|
// i) Get partial amplitude for that frame
|
||
|
const QModelIndex miPartialFrameAmpl =
|
||
|
pPartsModel->index(nFrameInd, ATSPartialItem::ePropAmplitude, miPartial);
|
||
|
const double dAmpl =
|
||
|
pPartsModel->data(miPartialFrameAmpl, Qt::DisplayRole).toDouble();
|
||
|
|
||
|
// i) Don't draw frames with nul amplitude.
|
||
|
if (dAmpl <= 0.0)
|
||
|
{
|
||
|
(*(*_pqvgiPartials)[nPartInd])[nFrameInd] = 0;
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
dAmplSum += dAmpl;
|
||
|
|
||
|
// j) Get partial start time and frequency for that frame
|
||
|
const QModelIndex miPartialFrameStartTime =
|
||
|
pPartsModel->index(nFrameInd, ATSPartialItem::ePropTime, miPartial);
|
||
|
const double dStartTime =
|
||
|
pPartsModel->data(miPartialFrameStartTime, Qt::DisplayRole).toDouble();
|
||
|
|
||
|
const QModelIndex miPartialFrameFreq =
|
||
|
pPartsModel->index(nFrameInd, ATSPartialItem::ePropFrequency, miPartial);
|
||
|
const double dFreq =
|
||
|
pPartsModel->data(miPartialFrameFreq, Qt::DisplayRole).toDouble();
|
||
|
|
||
|
|
||
|
// if (nPartInd < 2 || nPartInd >= nPartials - 1)
|
||
|
// std::cout << " Frame #" << nFrameInd
|
||
|
// << " : dStartTime = " << dStartTime
|
||
|
// << ", dAmpl = " << dAmpl
|
||
|
// << ", dFreq = " << dFreq << std::endl;
|
||
|
|
||
|
// k) Create a line item for a constant frequency and color (amplitude)
|
||
|
// along the whole frame (Note: The item has attached custom data :
|
||
|
// its amplitude, used for selection unhighlight).
|
||
|
// TODO? interpolate frequencies between frames (const for the moment)
|
||
|
PartialsSpecgramGraphicsFrameItem* pgilPartFrame =
|
||
|
new PartialsSpecgramGraphicsFrameItem(0.0, 0.0, dFrameDur, 0.0, dAmpl,
|
||
|
&_cmAmplColorMap, nPartInd, nFrameInd);
|
||
|
pgilPartFrame->setPos(dStartTime, dFreq);
|
||
|
pgilPartFrame->setAmplitude(dAmpl); // Fixme: Already done by ctor ?
|
||
|
pgilPartFrame->setFlag(QGraphicsItem::ItemIsSelectable);
|
||
|
pgilPartFrame->setAcceptHoverEvents(true);
|
||
|
(*(*_pqvgiPartials)[nPartInd])[nFrameInd] = pgilPartFrame;
|
||
|
//pgigPartial->addToGroup(pgilPartFrame);
|
||
|
addItem(pgilPartFrame);
|
||
|
|
||
|
// if (nFrameInd == 0)
|
||
|
// {
|
||
|
// std::cout << "Mouse buttons: " << pgilPartFrame->acceptedMouseButtons()
|
||
|
// << ", AcceptHover: " << pgilPartFrame->acceptHoverEvents()
|
||
|
// << ", Enabled: " << pgilPartFrame->isEnabled() << std::endl;
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
// Set partial group Z value equal to the mean amplitude of its frames :
|
||
|
// that way, stronger partials normaly have visual priority over weaker ones.
|
||
|
//const double dMeanAmpl = dAmplSum / nFrames;
|
||
|
//pgigPartial->setZValue(dMeanAmpl);
|
||
|
//pgigPartial->setData(eMeanAmplitude, dMeanAmpl);
|
||
|
|
||
|
// Update max mean amplitude if lower than this partial's mean amplitude.
|
||
|
//if (dMeanAmpl > _dMaxPartialMeanAmpl)
|
||
|
// _dMaxPartialMeanAmpl = dMeanAmpl;
|
||
|
|
||
|
// Add partial to the partials group.
|
||
|
//pgigPartials->addToGroup(pgigPartial);
|
||
|
}
|
||
|
|
||
|
//addItem(pgigPartials);
|
||
|
}
|