soundeditor/qatsh/DocumentWindow.cpp

467 lines
14 KiB
C++

// DocumentWindow class definition
//
// The widget that aggregate all the views of an ATS document :
// - the partials list view
// - the partials spectrogram view
// - the residual list view
// - the residual spectrogram view
// and the associated controls (sliders, splitters, buttons, rulers, ...)
//
// QATSH Copyright 2009 Jean-Philippe MEURET <jpmeuret@free.fr>
#include <iostream>
#include <vector>
#include <QSettings>
#include <QItemSelection>
#include <QItemSelectionModel>
#include <QMessageBox>
#ifdef PHONON_AUDIO
#include <Phonon/MediaObject>
#include <Phonon/MediaSource>
#else
#include <QProcess>
#endif
#include "TypesAndConstants.h"
#include "ATSMath.h"
#include "ATSModel.h"
#include "ATSModelItems.h"
#include "ATSModelManager.h"
#include "IATSAnalysis.h"
#include "StandardAnalysis.h"
#include "ATSSynthesis.h"
#include "SampledSound.h"
#include "ATSSound.h"
#include "MainWindow.h"
#include "FilePropertiesDialog.h"
#include "TableAndToolsWidget.h"
#include "DocumentWindow.h"
#include "ui_DocumentWindow.h"
DocumentWindow::DocumentWindow(QWidget *parent)
: QWidget(parent), _pMainWin(static_cast<MainWindow*>(parent)),
_pModelMgr(0), _eCurrGraphView(ePartialsSpectrogram),
_eLastSyntFormat((SampledSound::ESampleFormat)-1), _pLastSyntParams(0)
{
_pui = new Ui::DocumentWindow;
_pui->setupUi(this);
}
DocumentWindow::~DocumentWindow()
{
delete _pui;
if (_pModelMgr)
delete _pModelMgr;
if (_pLastSyntParams)
delete _pLastSyntParams;
}
void DocumentWindow::connectModelAndViews()
{
// Give the model to the views (TableAndTools first, for correct selection model exchange).
_pui->qwTableAndTools->setModelManager(_pModelMgr);
_pui->qwPartialsSpecgram->setModelManager(_pModelMgr);
_pui->qwResidualsSpecgram->setModelManager(_pModelMgr);
// Connect user interactions to local slots.
connect(_pui->qwTableAndTools, SIGNAL(reAnalyse(const QString&, IATSAnalysis*)),
this, SLOT(onReAnalyse(const QString&, IATSAnalysis*)));
connect(_pui->qwTableAndTools, SIGNAL(reSynthesise()), this, SLOT(onReSynthesise()));
connect(_pui->qwTableAndTools, SIGNAL(startPlayingSynthesisResult()),
this, SLOT(onStartPlayingSynthesisResult()));
connect(_pui->qwTableAndTools, SIGNAL(stopPlayingSynthesisResult()),
this, SLOT(onStopPlayingSynthesisResult()));
// Put select/edit tool front.
showSelectionEditionTool();
// Set main window size from settings, now we have something inside.
QSettings qSettings;
const QSize qSize = qSettings.value("Window/Size", QSize(800, 600)).toSize();
_pMainWin->resize(qSize);
}
void DocumentWindow::onModelReset()
{
//std::cout << "DocumentWindow::onModelReset()" << std::endl;
_pMainWin->setWindowModified(true);
_pMainWin->enableAction(MainWindow::eSaveFile, true);
}
void DocumentWindow::onModelChanged(const QModelIndex&, const QModelIndex&)
{
//std::cout << "DocumentWindow::onModelChanged()" << std::endl;
_pMainWin->setWindowModified(true);
_pMainWin->enableAction(MainWindow::eSaveFile, true);
}
void DocumentWindow::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::LanguageChange:
_pui->retranslateUi(this);
break;
default:
break;
}
}
bool DocumentWindow::loadFile(const QString &qsFileName)
{
// Can not be called twice.
_pModelMgr = new ATSModelManager();
// Load model from file.
const bool bLoaded = _pModelMgr->mainModel()->load(qsFileName);
// Be ready for model changes / reset (but ignore this first one ;-).
connect(_pModelMgr->mainModel(),
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(onModelChanged(const QModelIndex&, const QModelIndex&)));
connect(_pModelMgr->mainModel(), SIGNAL(modelReset()), this, SLOT(onModelReset()));
// Connect models and views.
if (bLoaded)
{
connectModelAndViews();
_pMainWin->enableAction(MainWindow::eSaveFileAs, true);
_pMainWin->enableAction(MainWindow::eFileProperties, true);
_pMainWin->enableAction(MainWindow::eReSynthesize, true);
_pMainWin->enableAction(MainWindow::eWinPartials, true);
_pMainWin->enableAction(MainWindow::eWinResidual, true);
}
return bLoaded;
}
bool DocumentWindow::storeFile(const QString &qsFileName)
{
// Try and store data to the target file.
QApplication::setOverrideCursor(Qt::WaitCursor);
const bool bStored = _pModelMgr->mainModel()->store(qsFileName);
QApplication::restoreOverrideCursor();
if (bStored)
_pMainWin->enableAction(MainWindow::eSaveFile, false);
return bStored;
}
bool DocumentWindow::analyseFile(const QString &qsFileName, IATSAnalysis* pAnalysis)
{
// Note: May be called more than once (re-analysis).
QApplication::setOverrideCursor(Qt::WaitCursor);
// Load sampled sound from file.
ATSSound* pATSSound = 0;
SampledSound smpSound;
if (smpSound.load(qsFileName.toStdString().c_str()))
{
if (pAnalysis->isOfType(StandardAnalysis::typeName()))
{
// Run analysis
StandardAnalysis* pStdAnalysis = static_cast<StandardAnalysis*>(pAnalysis);
pATSSound = (*pStdAnalysis)(&smpSound);
}
else
{
std::cout << "DocumentWindow::analyseFile : Unsupported analysis type" << std::endl;
}
// Setup model if analysis was successfull.
if (pATSSound)
{
const bool bFirstTime = (_pModelMgr == 0);
if (bFirstTime)
{
// Create model manager (and managed models)
_pModelMgr = new ATSModelManager();
// Be ready for model changes / reset.
connect(_pModelMgr->mainModel(),
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(onModelChanged(const QModelIndex&, const QModelIndex&)));
connect(_pModelMgr->mainModel(), SIGNAL(modelReset()),
this, SLOT(onModelReset()));
}
// Set model data.
_pModelMgr->mainModel()->setSound(pATSSound);
// On first time analysis :
if (bFirstTime)
{
// Connect models and views
connectModelAndViews();
// As we have got here through the analysis dialog/wizard,
// copy associated analysis settings into the analysis tool (same widget inside).
_pui->qwTableAndTools->setAnalysisSampleFile(qsFileName);
_pui->qwTableAndTools->setAnalysisParams(pAnalysis);
}
// Enable relevant actions.
_pMainWin->enableAction(MainWindow::eSaveFileAs, true);
_pMainWin->enableAction(MainWindow::eFileProperties, true);
_pMainWin->enableAction(MainWindow::eReAnalyse, true);
_pMainWin->enableAction(MainWindow::eReSynthesize, true);
_pMainWin->enableAction(MainWindow::eWinPartials, true);
_pMainWin->enableAction(MainWindow::eWinResidual, true);
}
}
QApplication::restoreOverrideCursor();
return pATSSound != 0;
}
bool DocumentWindow::synthesiseFile(const QString &qsFileName,
SampledSound::ESampleFormat eFormat,
ATSSynthesis* pSynthesis)
{
// Note: May be called more than once (re-synthesis).
QApplication::setOverrideCursor(Qt::WaitCursor);
// Get selected partials if necessary.
std::vector<int> vecSelPartIndexes;
if (pSynthesis->useOnlySelectedPartials())
{
// Retrieve the selected partial indexes if any.
const QModelIndexList qlmiSelParts =
_pModelMgr->partialsSelectionModel()->selection().indexes();
// Convert this model indexes to ATSSynthesis indexes (ignore non-ePartial type indexes).
for (int nSelPartInd = 0; nSelPartInd < qlmiSelParts.size(); nSelPartInd++)
{
const QModelIndex qmiSelPart = qlmiSelParts.at(nSelPartInd);
if (qmiSelPart.isValid() && qmiSelPart.internalPointer()
&& static_cast<const ATSModelItem*>(qmiSelPart.internalPointer())->type()
== ATSModelItem::ePartial)
vecSelPartIndexes.push_back(qmiSelPart.column());
}
}
// Check synthesis parameters : if none changed and if synthesis must use all partials,
// nothing to do again. Same if none changed and if synthesis must use only selected partials,
// and partial selection did change.
bool bDone = false;
if (qsFileName == _qsLastSyntResFileName
&& *pSynthesis == *_pLastSyntParams && eFormat == _eLastSyntFormat
&& (!pSynthesis->useOnlySelectedPartials()
|| vecSelPartIndexes == _vecLastSelParts))
bDone = true;
// Finally, if really a need for re-synthesis, do it.
ATSSound* pATSSound = _pModelMgr->mainModel()->sound();
if (!bDone && pATSSound)
{
// Do the synthesis.
SampledSound* pSampSound = (*pSynthesis)(pATSSound, vecSelPartIndexes);
if (pSampSound)
{
// Save the sampled sound to the given file.
// TODO: Customizable sample format.
bDone = pSampSound->store(qsFileName.toStdString().c_str(), eFormat);
// Free now useless sampled sound.
delete pSampSound;
// Save a copy of the synthesis inputs for later.
_qsLastSyntResFileName = qsFileName;
if (!_pLastSyntParams)
_pLastSyntParams = new ATSSynthesis();
*_pLastSyntParams = *pSynthesis;
_eLastSyntFormat = eFormat;
_vecLastSelParts = vecSelPartIndexes;
}
}
QApplication::restoreOverrideCursor();
return bDone;
}
void DocumentWindow::showFileProperties()
{
if (_pModelMgr)
{
FilePropertiesDialog filePropsDlg(this);
filePropsDlg.setModelManager(_pModelMgr);
filePropsDlg.exec();
}
}
void DocumentWindow::showSelectionEditionTool()
{
_pui->qwTableAndTools->switchTool(TableAndToolsWidget::eSelectionEdition);
}
void DocumentWindow::showAnalysisTool()
{
_pui->qwTableAndTools->switchTool(TableAndToolsWidget::eAnalysis);
}
void DocumentWindow::showSynthesisTool()
{
_pui->qwTableAndTools->switchTool(TableAndToolsWidget::eSynthesis);
}
void DocumentWindow::showGraphicalView(DocumentWindow::EGraphicalView eView)
{
if (_pModelMgr)
{
// Toggle data table (on the left)
const TypesAndConstants::EDataType eDataType =
(eView == eResidualsSpectrogram)
? TypesAndConstants::eResidualBand : TypesAndConstants::ePartial;
_pui->qwTableAndTools->switchSelectEditDataList(eDataType);
// Toggle graphical view (on the right)
_pui->qswGraphicalViews->setCurrentIndex(eView);
}
}
void DocumentWindow::onReAnalyse(const QString& qsFileName, IATSAnalysis* pAnaParams)
{
if (!analyseFile(qsFileName, pAnaParams))
QMessageBox::warning(this, qApp->applicationName(),
tr("Re-analysis failed for file %1.").arg(qsFileName));
else
_pMainWin->setWindowModified(true);
}
void DocumentWindow::onReSynthesise()
{
// Get synthesis parameters.
QString qsFileName;
SampledSound::ESampleFormat eFormat;
ATSSynthesis* pSyntParams;
_pui->qwTableAndTools->getSynthesisParams(qsFileName, eFormat, pSyntParams);
if (!synthesiseFile(qsFileName, eFormat, pSyntParams))
QMessageBox::warning(this, qApp->applicationName(),
tr("Re-synthesis failed for file %1.").arg(qsFileName));
}
void DocumentWindow::onStartPlayingSynthesisResult()
{
// Get synthesis parameters.
QString qsFileName;
SampledSound::ESampleFormat eFormat;
ATSSynthesis* pSyntParams;
_pui->qwTableAndTools->getSynthesisParams(qsFileName, eFormat, pSyntParams);
// Re-synthesise (if necessary) and play the synthesized sound.
if (synthesiseFile(qsFileName, eFormat, pSyntParams))
{
#ifdef PHONON_AUDIO
// Through a Phonon media object (never tested).
Phonon::MediaObject *pmoSound =
Phonon::createPlayer(Phonon::MusicCategory, Phonon::MediaSource(qsFileName));
pmoSound->play();
delete pmoSound;
#else
// Through an external audio player :
// 1) Get the player command from settings
QSettings qSettings;
const QString qsAudioPlayerCmd =
qSettings.value("AudioPlayer/Command").toString().arg(qsFileName);
std::cout << "DocumentWindow::onStartPlayingSynthesisResult : qsAudioPlayerCmd='" << qsAudioPlayerCmd.toStdString() << "'" << std::endl;
// 2) Start playing.
QProcess qpPlayer(this);
//qpPlayer.setStandardOutputFile("audio-out.log");
if (!qpPlayer.startDetached(qsAudioPlayerCmd))
QMessageBox::warning(this, qApp->applicationName(),
tr("Failed to play synthesized sound\n"
"through command '%1'\nCheck player command preference.")
.arg(qsAudioPlayerCmd));
#endif
}
else
QMessageBox::warning(this, qApp->applicationName(),
tr("Re-synthesis failed for file %1.").arg(qsFileName));
}
void DocumentWindow::onStopPlayingSynthesisResult()
{
std::cout << "DocumentWindow::onStopPlayingSynthesisResult : Not implemented" << std::endl;
}
void DocumentWindow::zoomIn()
{
// Determine currently shown view.
const EGraphicalView eView =
(EGraphicalView)_pui->qswGraphicalViews->currentIndex();
// Zoom it as requested (only frequency here).
switch (eView)
{
case ePartialsSpectrogram:
_pui->qwPartialsSpecgram->zoom(0.5, 1.0, 0.5, sqrt(2.0));
break;
case eResidualsSpectrogram:
_pui->qwResidualsSpecgram->zoom(0.5, 1.0, 0.5, sqrt(2.0));
break;
}
}
void DocumentWindow::zoomOut()
{
// Determine currently shown view.
const EGraphicalView eView =
(EGraphicalView)_pui->qswGraphicalViews->currentIndex();
// Zoom it as requested (only frequency here).
switch (eView)
{
case ePartialsSpectrogram:
_pui->qwPartialsSpecgram->zoom(0.5, 1.0, 0.5, 1.0 / sqrt(2.0));
break;
case eResidualsSpectrogram:
_pui->qwResidualsSpecgram->zoom(0.5, 1.0, 0.5, 1.0 / sqrt(2.0));
break;
}
}
void DocumentWindow::zoomAll()
{
// Determine currently shown view.
const EGraphicalView eView =
(EGraphicalView)_pui->qswGraphicalViews->currentIndex();
// Zoom it as requested (only frequency here).
switch (eView)
{
case ePartialsSpectrogram:
_pui->qwPartialsSpecgram->zoomAll();
break;
case eResidualsSpectrogram:
_pui->qwResidualsSpecgram->zoomAll();
break;
}
}
void DocumentWindow::zoomSelection()
{
std::cout << "DocumentWindow::zoomSelection : Not implemented" << std::endl;
}