# HG changeset patch # User Chris Cannam # Date 1565780226 -3600 # Node ID f52766aa747b83787500bd705b4b0155a730c1ce # Parent 47f96711069f10450d802bf358d619dbac40669f Rename src -> main for consistency with SV/Sonic Lineup diff -r 47f96711069f -r f52766aa747b configure --- a/configure Wed Aug 14 11:55:35 2019 +0100 +++ b/configure Wed Aug 14 11:57:06 2019 +0100 @@ -583,7 +583,7 @@ PACKAGE_URL= ac_unique_file="Tony" -ac_unique_file="src/main.cpp" +ac_unique_file="main/main.cpp" # Factoring default headers for most tests. ac_includes_default="\ #include diff -r 47f96711069f -r f52766aa747b configure.ac --- a/configure.ac Wed Aug 14 11:55:35 2019 +0100 +++ b/configure.ac Wed Aug 14 11:57:06 2019 +0100 @@ -1,7 +1,7 @@ AC_INIT([Tony], [], cannam@all-day-breakfast.com) -AC_CONFIG_SRCDIR(src/main.cpp) +AC_CONFIG_SRCDIR(main/main.cpp) # Autoconf will set CXXFLAGS; we don't usually want it to, because we # either define our own flags (at least if GCC is in use) or else use diff -r 47f96711069f -r f52766aa747b main/Analyser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/Analyser.cpp Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,1063 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2012 Chris Cannam and QMUL. + + 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 "Analyser.h" + +#include "transform/TransformFactory.h" +#include "transform/ModelTransformer.h" +#include "transform/FeatureExtractionModelTransformer.h" +#include "framework/Document.h" +#include "data/model/WaveFileModel.h" +#include "view/Pane.h" +#include "view/PaneStack.h" +#include "layer/Layer.h" +#include "layer/TimeValueLayer.h" +#include "layer/NoteLayer.h" +#include "layer/FlexiNoteLayer.h" +#include "layer/WaveformLayer.h" +#include "layer/ColourDatabase.h" +#include "layer/ColourMapper.h" +#include "layer/LayerFactory.h" +#include "layer/SpectrogramLayer.h" +#include "layer/Colour3DPlotLayer.h" +#include "layer/ShowLayerCommand.h" + +#include +#include + +using std::vector; + +Analyser::Analyser() : + m_document(0), + m_paneStack(0), + m_pane(0), + m_currentCandidate(-1), + m_candidatesVisible(false), + m_currentAsyncHandle(0) +{ + QSettings settings; + settings.beginGroup("LayerDefaults"); + settings.setValue + ("timevalues", + QString("") + .arg(int(TimeValueLayer::AutoAlignScale)) + .arg(int(TimeValueLayer::PlotPoints)) + .arg(27.5f).arg(880.f)); // temporary values: better get the real extents of the data from the model + settings.setValue + ("flexinotes", + QString("") + .arg(int(FlexiNoteLayer::AutoAlignScale))); + settings.endGroup(); +} + +Analyser::~Analyser() +{ +} + +QString +Analyser::newFileLoaded(Document *doc, ModelId model, + PaneStack *paneStack, Pane *pane) +{ + m_document = doc; + m_fileModel = model; + m_paneStack = paneStack; + m_pane = pane; + + if (!ModelById::isa(m_fileModel)) { + return "Internal error: Analyser::newFileLoaded() called with no model, or a non-WaveFileModel"; + } + + connect(doc, SIGNAL(layerAboutToBeDeleted(Layer *)), + this, SLOT(layerAboutToBeDeleted(Layer *))); + + QSettings settings; + settings.beginGroup("Analyser"); + bool autoAnalyse = settings.value("auto-analysis", true).toBool(); + settings.endGroup(); + + return doAllAnalyses(autoAnalyse); +} + +QString +Analyser::analyseExistingFile() +{ + if (!m_document) return "Internal error: Analyser::analyseExistingFile() called with no document present"; + + if (!m_pane) return "Internal error: Analyser::analyseExistingFile() called with no pane present"; + + if (m_fileModel.isNone()) return "Internal error: Analyser::analyseExistingFile() called with no model present"; + + if (m_layers[PitchTrack]) { + m_document->removeLayerFromView(m_pane, m_layers[PitchTrack]); + m_layers[PitchTrack] = 0; + } + if (m_layers[Notes]) { + m_document->removeLayerFromView(m_pane, m_layers[Notes]); + m_layers[Notes] = 0; + } + + return doAllAnalyses(true); +} + +QString +Analyser::doAllAnalyses(bool withPitchTrack) +{ + m_reAnalysingSelection = Selection(); + m_reAnalysisCandidates.clear(); + m_currentCandidate = -1; + m_candidatesVisible = false; + + // Note that we need at least one main-model layer (time ruler, + // waveform or what have you). It could be hidden if we don't want + // to see it but it must exist. + + QString warning, error; + + cerr << "Analyser::newFileLoaded: about to check visualisations etc" << endl; + + // This isn't fatal -- we can proceed without + // visualisations. Other failures are fatal though. + warning = addVisualisations(); + + error = addWaveform(); + if (error != "") return error; + + if (withPitchTrack) { + error = addAnalyses(); + if (error != "") return error; + } + + loadState(Audio); + loadState(PitchTrack); + loadState(Notes); + loadState(Spectrogram); + + stackLayers(); + + emit layersChanged(); + + return warning; +} + +void +Analyser::fileClosed() +{ + cerr << "Analyser::fileClosed" << endl; + m_layers.clear(); + m_reAnalysisCandidates.clear(); + m_currentCandidate = -1; + m_reAnalysingSelection = Selection(); +} + +bool +Analyser::getDisplayFrequencyExtents(double &min, double &max) +{ + if (!m_layers[Spectrogram]) return false; + return m_layers[Spectrogram]->getDisplayExtents(min, max); +} + +bool +Analyser::setDisplayFrequencyExtents(double min, double max) +{ + if (!m_layers[Spectrogram]) return false; + m_layers[Spectrogram]->setDisplayExtents(min, max); + return true; +} + +int +Analyser::getInitialAnalysisCompletion() +{ + int completion = 0; + + if (m_layers[PitchTrack]) { + completion = m_layers[PitchTrack]->getCompletion(m_pane); + } + + if (m_layers[Notes]) { + int c = m_layers[Notes]->getCompletion(m_pane); + if (c < completion) completion = c; + } + + return completion; +} + +void +Analyser::layerCompletionChanged(ModelId) +{ + if (getInitialAnalysisCompletion() < 100) { + return; + } + + emit initialAnalysisCompleted(); + + if (!m_layers[Audio]) { + return; + } + + // Extend pitch-track and note layers so as to nominally end at + // the same time as the audio. This affects any time-filling done + // on export etc. + + auto audioModel = ModelById::get(m_layers[Audio]->getModel()); + sv_frame_t endFrame = audioModel->getEndFrame(); + + if (m_layers[PitchTrack]) { + auto model = ModelById::getAs + (m_layers[PitchTrack]->getModel()); + if (model) { + model->extendEndFrame(endFrame); + } + } + + if (m_layers[Notes]) { + auto model = ModelById::getAs + (m_layers[Notes]->getModel()); + if (model) { + model->extendEndFrame(endFrame); + } + } +} + +QString +Analyser::addVisualisations() +{ + if (m_fileModel.isNone()) return "Internal error: Analyser::addVisualisations() called with no model present"; + + // A spectrogram, off by default. Must go at the back because it's + // opaque + +/* This is roughly what we'd do for a constant-Q spectrogram, but it + currently has issues with y-axis alignment + + TransformFactory *tf = TransformFactory::getInstance(); + + QString name = "Constant-Q"; + QString base = "vamp:cqvamp:cqvamp:"; + QString out = "constantq"; + + QString notFound = tr("Transform \"%1\" not found, spectrogram will not be enabled.

Is the %2 Vamp plugin correctly installed?"); + if (!tf->haveTransform(base + out)) { + return notFound.arg(base + out).arg(name); + } + + Transform transform = tf->getDefaultTransformFor + (base + out, m_fileModel->getSampleRate()); + transform.setParameter("bpo", 36); + + Colour3DPlotLayer *spectrogram = qobject_cast + (m_document->createDerivedLayer(transform, m_fileModel)); + + if (!spectrogram) return tr("Transform \"%1\" did not run correctly (no layer or wrong layer type returned)").arg(base + out); +*/ + + // As with all the visualisation layers, if we already have one in + // the pane we do not create another, just record its + // existence. (We create a new one when loading a new audio file, + // but just note the existing one when loading a complete session.) + + for (int i = 0; i < m_pane->getLayerCount(); ++i) { + SpectrogramLayer *existing = qobject_cast + (m_pane->getLayer(i)); + if (existing) { + cerr << "recording existing spectrogram layer" << endl; + m_layers[Spectrogram] = existing; + return ""; + } + } + + SpectrogramLayer *spectrogram = qobject_cast + (m_document->createMainModelLayer(LayerFactory::MelodicRangeSpectrogram)); + + spectrogram->setColourMap((int)ColourMapper::BlackOnWhite); + spectrogram->setNormalization(ColumnNormalization::Hybrid); + // This magical-looking scale factor happens to get us an + // identical display to Tony v1.0 + spectrogram->setGain(100.f / 4096.f); + m_document->addLayerToView(m_pane, spectrogram); + spectrogram->setLayerDormant(m_pane, true); + + m_layers[Spectrogram] = spectrogram; + + return ""; +} + +QString +Analyser::addWaveform() +{ + // Our waveform layer is just a shadow, light grey and taking up + // little space at the bottom. + + // As with the spectrogram above, if one exists already we just + // use it + for (int i = 0; i < m_pane->getLayerCount(); ++i) { + WaveformLayer *existing = qobject_cast + (m_pane->getLayer(i)); + if (existing) { + cerr << "recording existing waveform layer" << endl; + m_layers[Audio] = existing; + return ""; + } + } + + WaveformLayer *waveform = qobject_cast + (m_document->createMainModelLayer(LayerFactory::Waveform)); + + waveform->setMiddleLineHeight(0.9); + waveform->setShowMeans(false); // too small & pale for this + waveform->setBaseColour + (ColourDatabase::getInstance()->getColourIndex(tr("Grey"))); + auto params = waveform->getPlayParameters(); + if (params) { + params->setPlayPan(-1); + params->setPlayGain(1); + } + + m_document->addLayerToView(m_pane, waveform); + + m_layers[Audio] = waveform; + return ""; +} + +QString +Analyser::addAnalyses() +{ + auto waveFileModel = ModelById::getAs(m_fileModel); + if (!waveFileModel) { + return "Internal error: Analyser::addAnalyses() called with no model present"; + } + + // As with the spectrogram above, if these layers exist we use + // them + TimeValueLayer *existingPitch = 0; + FlexiNoteLayer *existingNotes = 0; + for (int i = 0; i < m_pane->getLayerCount(); ++i) { + if (!existingPitch) { + existingPitch = qobject_cast(m_pane->getLayer(i)); + } + if (!existingNotes) { + existingNotes = qobject_cast(m_pane->getLayer(i)); + } + } + if (existingPitch && existingNotes) { + cerr << "recording existing pitch and notes layers" << endl; + m_layers[PitchTrack] = existingPitch; + m_layers[Notes] = existingNotes; + return ""; + } else { + if (existingPitch) { + m_document->removeLayerFromView(m_pane, existingPitch); + m_layers[PitchTrack] = 0; + } + if (existingNotes) { + m_document->removeLayerFromView(m_pane, existingNotes); + m_layers[Notes] = 0; + } + } + + TransformFactory *tf = TransformFactory::getInstance(); + + QString plugname = "pYIN"; + QString base = "vamp:pyin:pyin:"; + QString f0out = "smoothedpitchtrack"; + QString noteout = "notes"; + + Transforms transforms; + +/*!!! we could have more than one pitch track... + QString cx = "vamp:cepstral-pitchtracker:cepstral-pitchtracker:f0"; + if (tf->haveTransform(cx)) { + Transform tx = tf->getDefaultTransformFor(cx); + TimeValueLayer *lx = qobject_cast + (m_document->createDerivedLayer(tx, m_fileModel)); + lx->setVerticalScale(TimeValueLayer::AutoAlignScale); + lx->setBaseColour(ColourDatabase::getInstance()->getColourIndex(tr("Bright Red"))); + m_document->addLayerToView(m_pane, lx); + } +*/ + + QString notFound = tr("Transform \"%1\" not found. Unable to analyse audio file.

Is the %2 Vamp plugin correctly installed?"); + if (!tf->haveTransform(base + f0out)) { + return notFound.arg(base + f0out).arg(plugname); + } + if (!tf->haveTransform(base + noteout)) { + return notFound.arg(base + noteout).arg(plugname); + } + + QSettings settings; + settings.beginGroup("Analyser"); + bool precise = settings.value("precision-analysis", false).toBool(); + bool lowamp = settings.value("lowamp-analysis", false).toBool(); + bool onset = settings.value("onset-analysis", true).toBool(); // should these be the same as in MainWindow.cpp? + bool prune = settings.value("prune-analysis", true).toBool(); + settings.endGroup(); + + Transform t = tf->getDefaultTransformFor + (base + f0out, waveFileModel->getSampleRate()); + t.setStepSize(256); + t.setBlockSize(2048); + + if (precise) { + cerr << "setting parameters for precise mode" << endl; + t.setParameter("precisetime", 1); + } else { + cerr << "setting parameters for vague mode" << endl; + t.setParameter("precisetime", 0); + } + + if (lowamp) { + cerr << "setting parameters for lowamp suppression" << endl; + t.setParameter("lowampsuppression", 0.2f); + } else { + cerr << "setting parameters for no lowamp suppression" << endl; + t.setParameter("lowampsuppression", 0.0f); + } + + if (onset) { + cerr << "setting parameters for increased onset sensitivity" << endl; + t.setParameter("onsetsensitivity", 0.7f); + } else { + cerr << "setting parameters for non-increased onset sensitivity" << endl; + t.setParameter("onsetsensitivity", 0.0f); + } + + if (prune) { + cerr << "setting parameters for duration pruning" << endl; + t.setParameter("prunethresh", 0.1f); + } else { + cerr << "setting parameters for no duration pruning" << endl; + t.setParameter("prunethresh", 0.0f); + } + + transforms.push_back(t); + + t.setOutput(noteout); + + transforms.push_back(t); + + std::vector layers = + m_document->createDerivedLayers(transforms, m_fileModel); + + for (int i = 0; i < (int)layers.size(); ++i) { + + FlexiNoteLayer *f = qobject_cast(layers[i]); + TimeValueLayer *t = qobject_cast(layers[i]); + + if (f) m_layers[Notes] = f; + if (t) m_layers[PitchTrack] = t; + + m_document->addLayerToView(m_pane, layers[i]); + } + + ColourDatabase *cdb = ColourDatabase::getInstance(); + + TimeValueLayer *pitchLayer = + qobject_cast(m_layers[PitchTrack]); + if (pitchLayer) { + pitchLayer->setBaseColour(cdb->getColourIndex(tr("Black"))); + auto params = pitchLayer->getPlayParameters(); + if (params) { + params->setPlayPan(1); + params->setPlayGain(0.5); + } + connect(pitchLayer, SIGNAL(modelCompletionChanged(ModelId)), + this, SLOT(layerCompletionChanged(ModelId))); + } + + FlexiNoteLayer *flexiNoteLayer = + qobject_cast(m_layers[Notes]); + if (flexiNoteLayer) { + flexiNoteLayer->setBaseColour(cdb->getColourIndex(tr("Bright Blue"))); + auto params = flexiNoteLayer->getPlayParameters(); + if (params) { + params->setPlayPan(1); + params->setPlayGain(0.5); + } + connect(flexiNoteLayer, SIGNAL(modelCompletionChanged(ModelId)), + this, SLOT(layerCompletionChanged(ModelId))); + connect(flexiNoteLayer, SIGNAL(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float)), + this, SLOT(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float))); + connect(flexiNoteLayer, SIGNAL(materialiseReAnalysis()), + this, SLOT(materialiseReAnalysis())); + } + + return ""; +} + +void +Analyser::reAnalyseRegion(sv_frame_t frame0, sv_frame_t frame1, float freq0, float freq1) +{ + cerr << "Analyser::reAnalyseRegion(" << frame0 << ", " << frame1 + << ", " << freq0 << ", " << freq1 << ")" << endl; + showPitchCandidates(true); + (void)reAnalyseSelection(Selection(frame0, frame1), + FrequencyRange(freq0, freq1)); +} + +void +Analyser::materialiseReAnalysis() +{ + if (m_reAnalysingSelection.isEmpty()) return; + switchPitchCandidate(m_reAnalysingSelection, true); // or false, doesn't matter +} + +QString +Analyser::reAnalyseSelection(Selection sel, FrequencyRange range) +{ + QMutexLocker locker(&m_asyncMutex); + + auto waveFileModel = ModelById::getAs(m_fileModel); + if (!waveFileModel) { + return "Internal error: Analyser::reAnalyseSelection() called with no model present"; + } + + if (!m_reAnalysingSelection.isEmpty()) { + if (sel == m_reAnalysingSelection && range == m_reAnalysingRange) { + cerr << "selection & range are same as current analysis, ignoring" << endl; + return ""; + } + } + + if (sel.isEmpty()) return ""; + + if (m_currentAsyncHandle) { + m_document->cancelAsyncLayerCreation(m_currentAsyncHandle); + } + + if (!m_reAnalysisCandidates.empty()) { + CommandHistory::getInstance()->startCompoundOperation + (tr("Discard Previous Candidates"), true); + discardPitchCandidates(); + CommandHistory::getInstance()->endCompoundOperation(); + } + + m_reAnalysingSelection = sel; + m_reAnalysingRange = range; + + m_preAnalysis = Clipboard(); + Layer *myLayer = m_layers[PitchTrack]; + if (myLayer) { + myLayer->copy(m_pane, sel, m_preAnalysis); + } + + TransformFactory *tf = TransformFactory::getInstance(); + + QString plugname1 = "pYIN"; + QString plugname2 = "CHP"; + + QString base = "vamp:pyin:localcandidatepyin:"; + QString out = "pitchtrackcandidates"; + + if (range.isConstrained()) { + base = "vamp:chp:constrainedharmonicpeak:"; + out = "peak"; + } + + Transforms transforms; + + QString notFound = tr("Transform \"%1\" not found. Unable to perform interactive analysis.

Are the %2 and %3 Vamp plugins correctly installed?"); + if (!tf->haveTransform(base + out)) { + return notFound.arg(base + out).arg(plugname1).arg(plugname2); + } + + Transform t = tf->getDefaultTransformFor + (base + out, waveFileModel->getSampleRate()); + t.setStepSize(256); + t.setBlockSize(2048); + + if (range.isConstrained()) { + t.setParameter("minfreq", float(range.min)); + t.setParameter("maxfreq", float(range.max)); + t.setBlockSize(4096); + } + + // get time stamps that align with the 256-sample grid of the original extraction + const sv_frame_t grid = 256; + sv_frame_t startSample = (sel.getStartFrame() / grid) * grid; + if (startSample < sel.getStartFrame()) startSample += grid; + sv_frame_t endSample = (sel.getEndFrame() / grid) * grid; + if (endSample < sel.getEndFrame()) endSample += grid; + if (!range.isConstrained()) { + startSample -= 4*grid; // 4*256 is for 4 frames offset due to timestamp shift + endSample -= 4*grid; + } else { + endSample -= 9*grid; // MM says: not sure what the CHP plugin does there + } + RealTime start = RealTime::frame2RealTime(startSample, waveFileModel->getSampleRate()); + RealTime end = RealTime::frame2RealTime(endSample, waveFileModel->getSampleRate()); + + RealTime duration; + + if (sel.getEndFrame() > sel.getStartFrame()) { + duration = end - start; + } + + cerr << "Analyser::reAnalyseSelection: start " << start << " end " << end << " original selection start " << sel.getStartFrame() << " end " << sel.getEndFrame() << " duration " << duration << endl; + + if (duration <= RealTime::zeroTime) { + cerr << "Analyser::reAnalyseSelection: duration <= 0, not analysing" << endl; + return ""; + } + + t.setStartTime(start); + t.setDuration(duration); + + transforms.push_back(t); + + m_currentAsyncHandle = + m_document->createDerivedLayersAsync(transforms, m_fileModel, this); + + return ""; +} + +bool +Analyser::arePitchCandidatesShown() const +{ + return m_candidatesVisible; +} + +void +Analyser::showPitchCandidates(bool shown) +{ + if (m_candidatesVisible == shown) return; + + foreach (Layer *layer, m_reAnalysisCandidates) { + if (shown) { + CommandHistory::getInstance()->addCommand + (new ShowLayerCommand(m_pane, layer, true, + tr("Show Pitch Candidates"))); + } else { + CommandHistory::getInstance()->addCommand + (new ShowLayerCommand(m_pane, layer, false, + tr("Hide Pitch Candidates"))); + } + } + + m_candidatesVisible = shown; +} + +void +Analyser::layersCreated(Document::LayerCreationAsyncHandle handle, + vector primary, + vector additional) +{ + { + QMutexLocker locker(&m_asyncMutex); + + if (handle != m_currentAsyncHandle || + m_reAnalysingSelection == Selection()) { + // We don't want these! + for (int i = 0; i < (int)primary.size(); ++i) { + m_document->deleteLayer(primary[i]); + } + for (int i = 0; i < (int)additional.size(); ++i) { + m_document->deleteLayer(additional[i]); + } + return; + } + m_currentAsyncHandle = 0; + + CommandHistory::getInstance()->startCompoundOperation + (tr("Re-Analyse Selection"), true); + + m_reAnalysisCandidates.clear(); + + vector all; + for (int i = 0; i < (int)primary.size(); ++i) { + all.push_back(primary[i]); + } + for (int i = 0; i < (int)additional.size(); ++i) { + all.push_back(additional[i]); + } + + for (int i = 0; i < (int)all.size(); ++i) { + TimeValueLayer *t = qobject_cast(all[i]); + if (t) { + auto params = t->getPlayParameters(); + if (params) { + params->setPlayAudible(false); + } + t->setBaseColour + (ColourDatabase::getInstance()->getColourIndex(tr("Bright Orange"))); + t->setPresentationName("candidate"); + m_document->addLayerToView(m_pane, t); + m_reAnalysisCandidates.push_back(t); + /* + cerr << "New re-analysis candidate model has " + << ((SparseTimeValueModel *)t->getModel())->getAllEvents().size() << " point(s)" << endl; + */ + } + } + + if (!all.empty()) { + bool show = m_candidatesVisible; + m_candidatesVisible = !show; // to ensure the following takes effect + showPitchCandidates(show); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + + emit layersChanged(); +} + +bool +Analyser::haveHigherPitchCandidate() const +{ + if (m_reAnalysisCandidates.empty()) return false; + return (m_currentCandidate < 0 || + (m_currentCandidate + 1 < (int)m_reAnalysisCandidates.size())); +} + +bool +Analyser::haveLowerPitchCandidate() const +{ + if (m_reAnalysisCandidates.empty()) return false; + return (m_currentCandidate < 0 || m_currentCandidate >= 1); +} + +void +Analyser::switchPitchCandidate(Selection sel, bool up) +{ + if (m_reAnalysisCandidates.empty()) return; + + if (up) { + m_currentCandidate = m_currentCandidate + 1; + if (m_currentCandidate >= (int)m_reAnalysisCandidates.size()) { + m_currentCandidate = 0; + } + } else { + m_currentCandidate = m_currentCandidate - 1; + if (m_currentCandidate < 0) { + m_currentCandidate = (int)m_reAnalysisCandidates.size() - 1; + } + } + + Layer *pitchTrack = m_layers[PitchTrack]; + if (!pitchTrack) return; + + Clipboard clip; + pitchTrack->deleteSelection(sel); + m_reAnalysisCandidates[m_currentCandidate]->copy(m_pane, sel, clip); + pitchTrack->paste(m_pane, clip, 0, false); + + stackLayers(); +} + +void +Analyser::stackLayers() +{ + // raise the pitch track, then notes on top (if present) + if (m_layers[PitchTrack]) { + m_paneStack->setCurrentLayer(m_pane, m_layers[PitchTrack]); + } + if (m_layers[Notes] && !m_layers[Notes]->isLayerDormant(m_pane)) { + m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); + } +} + +void +Analyser::shiftOctave(Selection sel, bool up) +{ + float factor = (up ? 2.f : 0.5f); + + vector actOn; + + Layer *pitchTrack = m_layers[PitchTrack]; + if (pitchTrack) actOn.push_back(pitchTrack); + + foreach (Layer *layer, actOn) { + + Clipboard clip; + layer->copy(m_pane, sel, clip); + layer->deleteSelection(sel); + + Clipboard shifted; + foreach (Event e, clip.getPoints()) { + if (e.hasValue()) { + Event se = e.withValue(e.getValue() * factor); + shifted.addPoint(se); + } else { + shifted.addPoint(e); + } + } + + layer->paste(m_pane, shifted, 0, false); + } +} + +void +Analyser::deletePitches(Selection sel) +{ + Layer *pitchTrack = m_layers[PitchTrack]; + if (!pitchTrack) return; + + pitchTrack->deleteSelection(sel); +} + +void +Analyser::abandonReAnalysis(Selection sel) +{ + // A compound command is already in progress + + discardPitchCandidates(); + + Layer *myLayer = m_layers[PitchTrack]; + if (!myLayer) return; + myLayer->deleteSelection(sel); + myLayer->paste(m_pane, m_preAnalysis, 0, false); +} + +void +Analyser::clearReAnalysis() +{ + discardPitchCandidates(); +} + +void +Analyser::discardPitchCandidates() +{ + if (!m_reAnalysisCandidates.empty()) { + // We don't use a compound command here, because we may be + // already in one. Caller bears responsibility for doing that + foreach (Layer *layer, m_reAnalysisCandidates) { + // This will cause the layer to be deleted later (ownership is + // transferred to the remove command) + m_document->removeLayerFromView(m_pane, layer); + } + m_reAnalysisCandidates.clear(); + } + + m_currentCandidate = -1; + m_reAnalysingSelection = Selection(); + m_candidatesVisible = false; +} + +void +Analyser::layerAboutToBeDeleted(Layer *doomed) +{ + cerr << "Analyser::layerAboutToBeDeleted(" << doomed << ")" << endl; + + vector notDoomed; + + foreach (Layer *layer, m_reAnalysisCandidates) { + if (layer != doomed) { + notDoomed.push_back(layer); + } + } + + m_reAnalysisCandidates = notDoomed; +} + +void +Analyser::takePitchTrackFrom(Layer *otherLayer) +{ + Layer *myLayer = m_layers[PitchTrack]; + if (!myLayer || !otherLayer) return; + + auto myModel = ModelById::get(myLayer->getModel()); + auto otherModel = ModelById::get(otherLayer->getModel()); + if (!myModel || !otherModel) return; + + Clipboard clip; + + Selection sel = Selection(myModel->getStartFrame(), + myModel->getEndFrame()); + myLayer->deleteSelection(sel); + + sel = Selection(otherModel->getStartFrame(), + otherModel->getEndFrame()); + otherLayer->copy(m_pane, sel, clip); + + // Remove all pitches <= 0Hz -- we now save absent pitches as 0Hz + // values when exporting a pitch track, so we need to exclude them + // here when importing again + EventVector after; + int excl = 0; + for (const auto &p: clip.getPoints()) { + if (p.hasValue() && p.getValue() > 0.f) { + after.push_back(p); + } else { + ++excl; + } + } + clip.setPoints(after); + + myLayer->paste(m_pane, clip, 0, false); +} + +void +Analyser::getEnclosingSelectionScope(sv_frame_t f, sv_frame_t &f0, sv_frame_t &f1) +{ + FlexiNoteLayer *flexiNoteLayer = + qobject_cast(m_layers[Notes]); + + sv_frame_t f0i = f, f1i = f; + int res = 1; + + if (!flexiNoteLayer) { + f0 = f1 = f; + return; + } + + flexiNoteLayer->snapToFeatureFrame(m_pane, f0i, res, Layer::SnapLeft); + flexiNoteLayer->snapToFeatureFrame(m_pane, f1i, res, Layer::SnapRight); + + f0 = (f0i < 0 ? 0 : f0i); + f1 = (f1i < 0 ? 0 : f1i); +} + +void +Analyser::saveState(Component c) const +{ + bool v = isVisible(c); + bool a = isAudible(c); + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue(QString("visible-%1").arg(int(c)), v); + settings.setValue(QString("audible-%1").arg(int(c)), a); + settings.endGroup(); +} + +void +Analyser::loadState(Component c) +{ + QSettings settings; + settings.beginGroup("Analyser"); + bool deflt = (c == Spectrogram ? false : true); + bool v = settings.value(QString("visible-%1").arg(int(c)), deflt).toBool(); + bool a = settings.value(QString("audible-%1").arg(int(c)), true).toBool(); + settings.endGroup(); + setVisible(c, v); + setAudible(c, a); +} + +void +Analyser::setIntelligentActions(bool on) +{ + std::cerr << "toggle setIntelligentActions " << on << std::endl; + + FlexiNoteLayer *flexiNoteLayer = + qobject_cast(m_layers[Notes]); + if (flexiNoteLayer) { + flexiNoteLayer->setIntelligentActions(on); + } +} + +bool +Analyser::isVisible(Component c) const +{ + if (m_layers[c]) { + return !m_layers[c]->isLayerDormant(m_pane); + } else { + return false; + } +} + +void +Analyser::setVisible(Component c, bool v) +{ + if (m_layers[c]) { + m_layers[c]->setLayerDormant(m_pane, !v); + + if (v) { + if (c == Notes) { + m_paneStack->setCurrentLayer(m_pane, m_layers[c]); + } else if (c == PitchTrack) { + // raise the pitch track, then notes on top (if present) + m_paneStack->setCurrentLayer(m_pane, m_layers[c]); + if (m_layers[Notes] && + !m_layers[Notes]->isLayerDormant(m_pane)) { + m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); + } + } + } + + m_pane->layerParametersChanged(); + saveState(c); + } +} + +bool +Analyser::isAudible(Component c) const +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return false; + return params->isPlayAudible(); + } else { + return false; + } +} + +void +Analyser::setAudible(Component c, bool a) +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return; + params->setPlayAudible(a); + saveState(c); + } +} + +float +Analyser::getGain(Component c) const +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return 1.f; + return params->getPlayGain(); + } else { + return 1.f; + } +} + +void +Analyser::setGain(Component c, float gain) +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return; + params->setPlayGain(gain); + saveState(c); + } +} + +float +Analyser::getPan(Component c) const +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return 1.f; + return params->getPlayPan(); + } else { + return 1.f; + } +} + +void +Analyser::setPan(Component c, float pan) +{ + if (m_layers[c]) { + auto params = m_layers[c]->getPlayParameters(); + if (!params) return; + params->setPlayPan(pan); + saveState(c); + } +} + + + diff -r 47f96711069f -r f52766aa747b main/Analyser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/Analyser.h Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,266 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2012 Chris Cannam and QMUL. + + 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. +*/ + +#ifndef ANALYSER_H +#define ANALYSER_H + +#include +#include +#include + +#include +#include + +#include "framework/Document.h" +#include "base/Selection.h" +#include "base/Clipboard.h" +#include "data/model/WaveFileModel.h" + +class Pane; +class PaneStack; +class Layer; +class TimeValueLayer; +class Layer; + +class Analyser : public QObject, + public Document::LayerCreationHandler +{ + Q_OBJECT + +public: + Analyser(); + virtual ~Analyser(); + + // Process new main model, add derived layers; return "" on + // success or error string on failure + QString newFileLoaded(Document *newDocument, + ModelId model, + PaneStack *paneStack, + Pane *pane); + + // Remove any derived layers, process the main model, add derived + // layers; return "" on success or error string on failure + QString analyseExistingFile(); + + // Discard any layers etc associated with the current document + void fileClosed(); + + void setIntelligentActions(bool); + + bool getDisplayFrequencyExtents(double &min, double &max); + bool setDisplayFrequencyExtents(double min, double max); + + // Return completion %age for initial analysis -- 100 means it's done + int getInitialAnalysisCompletion(); + + enum Component { + Audio = 0, + PitchTrack = 1, + Notes = 2, + Spectrogram = 3, + }; + + bool isVisible(Component c) const; + void setVisible(Component c, bool v); + void toggleVisible(Component c) { setVisible(c, !isVisible(c)); } + + bool isAudible(Component c) const; + void setAudible(Component c, bool v); + void toggleAudible(Component c) { setAudible(c, !isAudible(c)); } + + void cycleStatus(Component c) { + if (isVisible(c)) { + if (isAudible(c)) { + setVisible(c, false); + setAudible(c, false); + } else { + setAudible(c, true); + } + } else { + setVisible(c, true); + setAudible(c, false); + } + } + + ModelId getMainModelId() const { + return m_fileModel; + } + std::shared_ptr getMainModel() const { + return ModelById::getAs(m_fileModel); + } + + float getGain(Component c) const; + void setGain(Component c, float gain); + + float getPan(Component c) const; + void setPan(Component c, float pan); + + void getEnclosingSelectionScope(sv_frame_t f, sv_frame_t &f0, sv_frame_t &f1); + + struct FrequencyRange { + FrequencyRange() : min(0), max(0) { } + FrequencyRange(double min_, double max_) : min(min_), max(max_) { } + bool isConstrained() const { return min != max; } + double min; + double max; + bool operator==(const FrequencyRange &r) { + return min == r.min && max == r.max; + } + }; + + /** + * Analyse the selection and schedule asynchronous adds of + * candidate layers for the region it contains. Returns "" on + * success or a user-readable error string on failure. If the + * frequency range isConstrained(), analysis will be constrained + * to that range. + */ + QString reAnalyseSelection(Selection sel, FrequencyRange range); + + /** + * Return true if the analysed pitch candidates are currently + * visible (they are hidden from the call to reAnalyseSelection + * until they are requested through showPitchCandidates()). Note + * that this may return true even when no pitch candidate layers + * actually exist yet, because they are constructed + * asynchronously. If that is the case, then the layers will + * appear when they are created (otherwise they will remain hidden + * after creation). + */ + bool arePitchCandidatesShown() const; + + /** + * Show or hide the analysed pitch candidate layers. This is reset + * (to "hide") with each new call to reAnalyseSelection. Because + * the layers are created asynchronously, setting this to true + * does not guarantee that they appear immediately, only that they + * will appear once they have been created. + */ + void showPitchCandidates(bool shown); + + /** + * If a re-analysis has been activated, switch the selected area + * of the main pitch track to a different candidate from the + * analysis results. + */ + void switchPitchCandidate(Selection sel, bool up); + + /** + * Return true if it is possible to switch up to another pitch + * candidate. This may mean that the currently selected pitch + * candidate is not the highest, or it may mean that no alternate + * pitch candidate has been selected at all yet (but some are + * available). + */ + bool haveHigherPitchCandidate() const; + + /** + * Return true if it is possible to switch down to another pitch + * candidate. This may mean that the currently selected pitch + * candidate is not the lowest, or it may mean that no alternate + * pitch candidate has been selected at all yet (but some are + * available). + */ + bool haveLowerPitchCandidate() const; + + /** + * Delete the pitch estimates from the selected area of the main + * pitch track. + */ + void deletePitches(Selection sel); + + /** + * Move the main pitch track and any active analysis candidate + * tracks up or down an octave in the selected area. + */ + void shiftOctave(Selection sel, bool up); + + /** + * Remove any re-analysis layers and also reset the pitch track in + * the given selection to its state prior to the last re-analysis, + * abandoning any changes made since then. No re-analysis layers + * will be available until after the next call to + * reAnalyseSelection. + */ + void abandonReAnalysis(Selection sel); + + /** + * Remove any re-analysis layers, without any expectation of + * adding them later, unlike showPitchCandidates(false), and + * without changing the current pitch track, unlike + * abandonReAnalysis(). + */ + void clearReAnalysis(); + + /** + * Import the pitch track from the given layer into our + * pitch-track layer. + */ + void takePitchTrackFrom(Layer *layer); + + Pane *getPane() { + return m_pane; + } + + Layer *getLayer(Component type) { + return m_layers[type]; + } + +signals: + void layersChanged(); + void initialAnalysisCompleted(); + +protected slots: + void layerAboutToBeDeleted(Layer *); + void layerCompletionChanged(ModelId); + void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float); + void materialiseReAnalysis(); + +protected: + Document *m_document; + ModelId m_fileModel; + PaneStack *m_paneStack; + Pane *m_pane; + + mutable std::map m_layers; + + Clipboard m_preAnalysis; + Selection m_reAnalysingSelection; + FrequencyRange m_reAnalysingRange; + std::vector m_reAnalysisCandidates; + int m_currentCandidate; + bool m_candidatesVisible; + Document::LayerCreationAsyncHandle m_currentAsyncHandle; + QMutex m_asyncMutex; + + QString doAllAnalyses(bool withPitchTrack); + + QString addVisualisations(); + QString addWaveform(); + QString addAnalyses(); + + void discardPitchCandidates(); + + void stackLayers(); + + // Document::LayerCreationHandler method + void layersCreated(Document::LayerCreationAsyncHandle, + std::vector, std::vector); + + void saveState(Component c) const; + void loadState(Component c); +}; + +#endif diff -r 47f96711069f -r f52766aa747b main/MainWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/MainWindow.cpp Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,3355 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2012 Chris Cannam and QMUL. + + 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 "../version.h" + +#include "MainWindow.h" +#include "NetworkPermissionTester.h" +#include "Analyser.h" + +#include "framework/Document.h" +#include "framework/VersionTester.h" + +#include "view/Pane.h" +#include "view/PaneStack.h" +#include "data/model/WaveFileModel.h" +#include "data/model/NoteModel.h" +#include "layer/FlexiNoteLayer.h" +#include "view/ViewManager.h" +#include "base/Preferences.h" +#include "base/RecordDirectory.h" +#include "base/AudioLevel.h" +#include "layer/WaveformLayer.h" +#include "layer/TimeInstantLayer.h" +#include "layer/TimeValueLayer.h" +#include "layer/SpectrogramLayer.h" +#include "widgets/Fader.h" +#include "view/Overview.h" +#include "widgets/AudioDial.h" +#include "widgets/IconLoader.h" +#include "widgets/KeyReference.h" +#include "widgets/LevelPanToolButton.h" +#include "audio/AudioCallbackPlaySource.h" +#include "audio/AudioCallbackRecordTarget.h" +#include "audio/PlaySpeedRangeMapper.h" +#include "base/Profiler.h" +#include "base/UnitDatabase.h" +#include "layer/ColourDatabase.h" +#include "base/Selection.h" + +#include "rdf/RDFImporter.h" +#include "data/fileio/DataFileReaderFactory.h" +#include "data/fileio/CSVFormat.h" +#include "data/fileio/CSVFileWriter.h" +#include "data/fileio/MIDIFileWriter.h" +#include "rdf/RDFExporter.h" + +#include "widgets/RangeInputDialog.h" +#include "widgets/ActivityLog.h" + +// For version information +#include "vamp/vamp.h" +#include "vamp-sdk/PluginBase.h" +#include "plugin/api/ladspa.h" +#include "plugin/api/dssi.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using std::vector; + + +MainWindow::MainWindow(SoundOptions options, bool withSonification, bool withSpectrogram) : + MainWindowBase(options), + m_overview(0), + m_mainMenusCreated(false), + m_playbackMenu(0), + m_recentFilesMenu(0), + m_rightButtonMenu(0), + m_rightButtonPlaybackMenu(0), + m_deleteSelectedAction(0), + m_ffwdAction(0), + m_rwdAction(0), + m_intelligentActionOn(true), //GF: !!! temporary + m_activityLog(new ActivityLog()), + m_keyReference(new KeyReference()), + m_selectionAnchor(0), + m_withSonification(withSonification), + m_withSpectrogram(withSpectrogram) +{ + setWindowTitle(QApplication::applicationName()); + +#ifdef Q_OS_MAC +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + setUnifiedTitleAndToolBarOnMac(true); +#endif +#endif + + UnitDatabase *udb = UnitDatabase::getInstance(); + udb->registerUnit("Hz"); + udb->registerUnit("dB"); + udb->registerUnit("s"); + + ColourDatabase *cdb = ColourDatabase::getInstance(); + cdb->addColour(Qt::black, tr("Black")); + cdb->addColour(Qt::darkRed, tr("Red")); + cdb->addColour(Qt::darkBlue, tr("Blue")); + cdb->addColour(Qt::darkGreen, tr("Green")); + cdb->addColour(QColor(200, 50, 255), tr("Purple")); + cdb->addColour(QColor(255, 150, 50), tr("Orange")); + cdb->addColour(QColor(180, 180, 180), tr("Grey")); + cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); + cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); + cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); + cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); + cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); + cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); + + Preferences::getInstance()->setResampleOnLoad(true); + Preferences::getInstance()->setFixedSampleRate(44100); + Preferences::getInstance()->setSpectrogramSmoothing + (Preferences::SpectrogramInterpolated); + Preferences::getInstance()->setNormaliseAudio(true); + + QSettings settings; + + settings.beginGroup("MainWindow"); + settings.setValue("showstatusbar", false); + settings.endGroup(); + + settings.beginGroup("Transformer"); + settings.setValue("use-flexi-note-model", true); + settings.endGroup(); + + settings.beginGroup("LayerDefaults"); + settings.setValue("waveform", + QString("") + .arg(int(WaveformLayer::LinearScale)) + .arg(int(WaveformLayer::MixChannels))); + settings.endGroup(); + + m_viewManager->setAlignMode(false); + m_viewManager->setPlaySoloMode(false); + m_viewManager->setToolMode(ViewManager::NavigateMode); + m_viewManager->setZoomWheelsEnabled(false); + m_viewManager->setIlluminateLocalFeatures(true); + m_viewManager->setShowWorkTitle(false); + m_viewManager->setShowCentreLine(false); + m_viewManager->setShowDuration(false); + m_viewManager->setOverlayMode(ViewManager::GlobalOverlays); + + connect(m_viewManager, SIGNAL(selectionChangedByUser()), + this, SLOT(selectionChangedByUser())); + + QFrame *frame = new QFrame; + setCentralWidget(frame); + + QGridLayout *layout = new QGridLayout; + + QScrollArea *scroll = new QScrollArea(frame); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scroll->setFrameShape(QFrame::NoFrame); + + // We have a pane stack: it comes with the territory. However, we + // have a fixed and known number of panes in it -- it isn't + // variable + m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); + m_paneStack->setShowPaneAccessories(false); + connect(m_paneStack, SIGNAL(doubleClickSelectInvoked(sv_frame_t)), + this, SLOT(doubleClickSelectInvoked(sv_frame_t))); + scroll->setWidget(m_paneStack); + + m_overview = new Overview(frame); + m_overview->setPlaybackFollow(PlaybackScrollPage); + m_overview->setViewManager(m_viewManager); + m_overview->setFixedHeight(60); +#ifndef _WIN32 + // For some reason, the contents of the overview never appear if we + // make this setting on Windows. I have no inclination at the moment + // to track down the reason why. + m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); +#endif + connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + + m_panLayer = new WaveformLayer; + m_panLayer->setChannelMode(WaveformLayer::MergeChannels); + m_panLayer->setAggressiveCacheing(true); + m_panLayer->setGain(0.5); + m_overview->addLayer(m_panLayer); + + if (m_viewManager->getGlobalDarkBackground()) { + m_panLayer->setBaseColour + (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green"))); + } else { + m_panLayer->setBaseColour + (ColourDatabase::getInstance()->getColourIndex(tr("Blue"))); + } + + m_fader = new Fader(frame, false); + connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_playSpeed = new AudioDial(frame); + m_playSpeed->setMeterColor(Qt::darkBlue); + m_playSpeed->setMinimum(0); + m_playSpeed->setMaximum(120); + m_playSpeed->setValue(60); + m_playSpeed->setFixedWidth(24); + m_playSpeed->setFixedHeight(24); + m_playSpeed->setNotchesVisible(true); + m_playSpeed->setPageStep(10); + m_playSpeed->setObjectName(tr("Playback Speed")); + m_playSpeed->setDefaultValue(60); + m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); + m_playSpeed->setShowToolTip(true); + connect(m_playSpeed, SIGNAL(valueChanged(int)), + this, SLOT(playSpeedChanged(int))); + connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); + connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); + + m_audioLPW = new LevelPanToolButton(frame); + m_audioLPW->setIncludeMute(false); + m_audioLPW->setObjectName(tr("Audio Track Level and Pan")); + connect(m_audioLPW, SIGNAL(levelChanged(float)), this, SLOT(audioGainChanged(float))); + connect(m_audioLPW, SIGNAL(panChanged(float)), this, SLOT(audioPanChanged(float))); + + if (m_withSonification) { + + m_pitchLPW = new LevelPanToolButton(frame); + m_pitchLPW->setIncludeMute(false); + m_pitchLPW->setObjectName(tr("Pitch Track Level and Pan")); + connect(m_pitchLPW, SIGNAL(levelChanged(float)), this, SLOT(pitchGainChanged(float))); + connect(m_pitchLPW, SIGNAL(panChanged(float)), this, SLOT(pitchPanChanged(float))); + + m_notesLPW = new LevelPanToolButton(frame); + m_notesLPW->setIncludeMute(false); + m_notesLPW->setObjectName(tr("Note Track Level and Pan")); + connect(m_notesLPW, SIGNAL(levelChanged(float)), this, SLOT(notesGainChanged(float))); + connect(m_notesLPW, SIGNAL(panChanged(float)), this, SLOT(notesPanChanged(float))); + } + + layout->setSpacing(4); + layout->addWidget(m_overview, 0, 1); + layout->addWidget(scroll, 1, 1); + + layout->setColumnStretch(1, 10); + + frame->setLayout(layout); + + m_analyser = new Analyser(); + connect(m_analyser, SIGNAL(layersChanged()), + this, SLOT(updateLayerStatuses())); + connect(m_analyser, SIGNAL(layersChanged()), + this, SLOT(updateMenuStates())); + + setupMenus(); + setupToolbars(); + setupHelpMenu(); + + statusBar(); + + finaliseMenus(); + + connect(m_viewManager, SIGNAL(activity(QString)), + m_activityLog, SLOT(activityHappened(QString))); + connect(m_playSource, SIGNAL(activity(QString)), + m_activityLog, SLOT(activityHappened(QString))); + connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), + m_activityLog, SLOT(activityHappened(QString))); + connect(this, SIGNAL(activity(QString)), + m_activityLog, SLOT(activityHappened(QString))); + connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); + connect(this, SIGNAL(sessionLoaded()), this, SLOT(analyseNewMainModel())); + connect(this, SIGNAL(audioFileLoaded()), this, SLOT(analyseNewMainModel())); + m_activityLog->hide(); + + setAudioRecordMode(RecordReplaceSession); + + newSession(); + + settings.beginGroup("MainWindow"); + settings.setValue("zoom-default", 512); + settings.endGroup(); + zoomDefault(); + + NetworkPermissionTester tester; + bool networkPermission = tester.havePermission(); + if (networkPermission) { + m_versionTester = new VersionTester + ("sonicvisualiser.org", "latest-tony-version.txt", TONY_VERSION); + connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), + this, SLOT(newerVersionAvailable(QString))); + } else { + m_versionTester = 0; + } +} + +MainWindow::~MainWindow() +{ + delete m_analyser; + delete m_keyReference; + Profiles::getInstance()->dump(); +} + +void +MainWindow::setupMenus() +{ + if (!m_mainMenusCreated) { + +#ifdef Q_OS_LINUX + // In Ubuntu 14.04 the window's menu bar goes missing entirely + // if the user is running any desktop environment other than Unity + // (in which the faux single-menubar appears). The user has a + // workaround, to remove the appmenu-qt5 package, but that is + // awkward and the problem is so severe that it merits disabling + // the system menubar integration altogether. Like this: + menuBar()->setNativeMenuBar(false); +#endif + + m_rightButtonMenu = new QMenu(); + } + + if (!m_mainMenusCreated) { + CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); + m_rightButtonMenu->addSeparator(); + } + + setupFileMenu(); + setupEditMenu(); + setupViewMenu(); + setupAnalysisMenu(); + + m_mainMenusCreated = true; +} + +void +MainWindow::setupFileMenu() +{ + if (m_mainMenusCreated) return; + + QMenu *menu = menuBar()->addMenu(tr("&File")); + menu->setTearOffEnabled(true); + QToolBar *toolbar = addToolBar(tr("File Toolbar")); + + m_keyReference->setCategory(tr("File and Session Management")); + + IconLoader il; + QIcon icon; + QAction *action; + + icon = il.load("fileopen"); + action = new QAction(icon, tr("&Open..."), this); + action->setShortcut(tr("Ctrl+O")); + action->setStatusTip(tr("Open a session or audio file")); + connect(action, SIGNAL(triggered()), this, SLOT(openFile())); + m_keyReference->registerShortcut(action); + menu->addAction(action); + toolbar->addAction(action); + + action = new QAction(tr("Open Lo&cation..."), this); + action->setShortcut(tr("Ctrl+Shift+O")); + action->setStatusTip(tr("Open a file from a remote URL")); + connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); + m_recentFilesMenu->setTearOffEnabled(true); + setupRecentFilesMenu(); + connect(&m_recentFiles, SIGNAL(recentChanged()), + this, SLOT(setupRecentFilesMenu())); + + menu->addSeparator(); + + icon = il.load("filesave"); + action = new QAction(icon, tr("&Save Session"), this); + action->setShortcut(tr("Ctrl+S")); + action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); + connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); + connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + toolbar->addAction(action); + + icon = il.load("filesaveas"); + action = new QAction(icon, tr("Save Session &As..."), this); + action->setShortcut(tr("Ctrl+Shift+S")); + action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); + connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); + connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + toolbar->addAction(action); + + action = new QAction(tr("Save Session to Audio File &Path"), this); + action->setShortcut(tr("Ctrl+Alt+S")); + action->setStatusTip(tr("Save the current session into a %1 session file with the same filename as the audio but a .ton extension.").arg(QApplication::applicationName())); + connect(action, SIGNAL(triggered()), this, SLOT(saveSessionInAudioPath())); + connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("I&mport Pitch Track Data..."), this); + action->setStatusTip(tr("Import pitch-track data from a CSV, RDF, or layer XML file")); + connect(action, SIGNAL(triggered()), this, SLOT(importPitchLayer())); + connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("E&xport Pitch Track Data..."), this); + action->setStatusTip(tr("Export pitch-track data to a CSV, RDF, or layer XML file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportPitchLayer())); + connect(this, SIGNAL(canExportPitchTrack(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(tr("&Export Note Data..."), this); + action->setStatusTip(tr("Export note data to a CSV, RDF, layer XML, or MIDI file")); + connect(action, SIGNAL(triggered()), this, SLOT(exportNoteLayer())); + connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Browse Recorded Audio"), this); + action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); + connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(il.load("exit"), tr("&Quit"), this); + action->setShortcut(tr("Ctrl+Q")); + action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); + connect(action, SIGNAL(triggered()), this, SLOT(close())); + m_keyReference->registerShortcut(action); + menu->addAction(action); +} + +void +MainWindow::setupEditMenu() +{ + if (m_mainMenusCreated) return; + + QMenu *menu = menuBar()->addMenu(tr("&Edit")); + menu->setTearOffEnabled(true); + CommandHistory::getInstance()->registerMenu(menu); + menu->addSeparator(); + + m_keyReference->setCategory + (tr("Selection Strip Mouse Actions")); + m_keyReference->registerShortcut + (tr("Jump"), tr("Left"), + tr("Click left button to move the playback position to a time")); + m_keyReference->registerShortcut + (tr("Select"), tr("Left"), + tr("Click left button and drag to select a region of time")); + m_keyReference->registerShortcut + (tr("Select Note Duration"), tr("Double-Click Left"), + tr("Double-click left button to select the region of time corresponding to a note")); + + QToolBar *toolbar = addToolBar(tr("Tools Toolbar")); + + CommandHistory::getInstance()->registerToolbar(toolbar); + + QActionGroup *group = new QActionGroup(this); + + IconLoader il; + + m_keyReference->setCategory(tr("Tool Selection")); + QAction *action = toolbar->addAction(il.load("navigate"), + tr("Navigate")); + action->setCheckable(true); + action->setChecked(true); + action->setShortcut(tr("1")); + action->setStatusTip(tr("Navigate")); + connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); + connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); + group->addAction(action); + menu->addAction(action); + m_keyReference->registerShortcut(action); + + m_keyReference->setCategory + (tr("Navigate Tool Mouse Actions")); + m_keyReference->registerShortcut + (tr("Navigate"), tr("Left"), + tr("Click left button and drag to move around")); + m_keyReference->registerShortcut + (tr("Re-Analyse Area"), tr("Shift+Left"), + tr("Shift-click left button and drag to define a specific pitch and time range to re-analyse")); + m_keyReference->registerShortcut + (tr("Edit"), tr("Double-Click Left"), + tr("Double-click left button on an item to edit it")); + + m_keyReference->setCategory(tr("Tool Selection")); + action = toolbar->addAction(il.load("move"), + tr("Edit")); + action->setCheckable(true); + action->setShortcut(tr("2")); + action->setStatusTip(tr("Edit with Note Intelligence")); + connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); + group->addAction(action); + menu->addAction(action); + m_keyReference->registerShortcut(action); + + m_keyReference->setCategory + (tr("Note Edit Tool Mouse Actions")); + m_keyReference->registerShortcut + (tr("Adjust Pitch"), tr("Left"), + tr("Click left button on the main part of a note and drag to move it up or down")); + m_keyReference->registerShortcut + (tr("Split"), tr("Left"), + tr("Click left button on the bottom edge of a note to split it at the click point")); + m_keyReference->registerShortcut + (tr("Resize"), tr("Left"), + tr("Click left button on the left or right edge of a note and drag to change the time or duration of the note")); + m_keyReference->registerShortcut + (tr("Erase"), tr("Shift+Left"), + tr("Shift-click left button on a note to remove it")); + + +/* Remove for now... + + m_keyReference->setCategory(tr("Tool Selection")); + action = toolbar->addAction(il.load("notes"), + tr("Free Edit")); + action->setCheckable(true); + action->setShortcut(tr("3")); + action->setStatusTip(tr("Free Edit")); + connect(action, SIGNAL(triggered()), this, SLOT(toolFreeEditSelected())); + group->addAction(action); + m_keyReference->registerShortcut(action); +*/ + + menu->addSeparator(); + + m_keyReference->setCategory(tr("Selection")); + + action = new QAction(tr("Select &All"), this); + action->setShortcut(tr("Ctrl+A")); + action->setStatusTip(tr("Select the whole duration of the current session")); + connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("C&lear Selection"), this); + action->setShortcuts(QList() + << QKeySequence(tr("Esc")) + << QKeySequence(tr("Ctrl+Esc"))); + action->setStatusTip(tr("Clear the selection and abandon any pending pitch choices in it")); + connect(action, SIGNAL(triggered()), this, SLOT(abandonSelection())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + m_keyReference->registerAlternativeShortcut(action, QKeySequence(tr("Ctrl+Esc"))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + m_rightButtonMenu->addSeparator(); + + m_keyReference->setCategory(tr("Pitch Track")); + + action = new QAction(tr("Choose Higher Pitch"), this); + action->setShortcut(tr("Ctrl+Up")); + action->setStatusTip(tr("Move pitches up an octave, or to the next higher pitch candidate")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(switchPitchUp())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Choose Lower Pitch"), this); + action->setShortcut(tr("Ctrl+Down")); + action->setStatusTip(tr("Move pitches down an octave, or to the next lower pitch candidate")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(switchPitchDown())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + m_showCandidatesAction = new QAction(tr("Show Pitch Candidates"), this); + m_showCandidatesAction->setShortcut(tr("Ctrl+Return")); + m_showCandidatesAction->setStatusTip(tr("Toggle the display of alternative pitch candidates for the selected region")); + m_keyReference->registerShortcut(m_showCandidatesAction); + connect(m_showCandidatesAction, SIGNAL(triggered()), this, SLOT(togglePitchCandidates())); + connect(this, SIGNAL(canClearSelection(bool)), m_showCandidatesAction, SLOT(setEnabled(bool))); + menu->addAction(m_showCandidatesAction); + m_rightButtonMenu->addAction(m_showCandidatesAction); + + action = new QAction(tr("Remove Pitches"), this); + action->setShortcut(tr("Ctrl+Backspace")); + action->setStatusTip(tr("Remove all pitch estimates within the selected region, making it unvoiced")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(clearPitches())); + connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + menu->addSeparator(); + m_rightButtonMenu->addSeparator(); + + m_keyReference->setCategory(tr("Note Track")); + + action = new QAction(tr("Split Note"), this); + action->setShortcut(tr("/")); + action->setStatusTip(tr("Split the note at the current playback position into two")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(splitNote())); + connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Merge Notes"), this); + action->setShortcut(tr("\\")); + action->setStatusTip(tr("Merge all notes within the selected region into a single note")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(mergeNotes())); + connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Delete Notes"), this); + action->setShortcut(tr("Backspace")); + action->setStatusTip(tr("Delete all notes within the selected region")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(deleteNotes())); + connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Form Note from Selection"), this); + action->setShortcut(tr("=")); + action->setStatusTip(tr("Form a note spanning the selected region, splitting any existing notes at its boundaries")); + m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(formNoteFromSelection())); + connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); + + action = new QAction(tr("Snap Notes to Pitch Track"), this); + action->setStatusTip(tr("Set notes within the selected region to the median frequency of their underlying pitches, or remove them if there are no underlying pitches")); + // m_keyReference->registerShortcut(action); + connect(action, SIGNAL(triggered()), this, SLOT(snapNotesToPitches())); + connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + m_rightButtonMenu->addAction(action); +} + +void +MainWindow::setupViewMenu() +{ + if (m_mainMenusCreated) return; + + IconLoader il; + + QAction *action = 0; + + m_keyReference->setCategory(tr("Panning and Navigation")); + + QMenu *menu = menuBar()->addMenu(tr("&View")); + menu->setTearOffEnabled(true); + action = new QAction(tr("Peek &Left"), this); + action->setShortcut(tr("Alt+Left")); + action->setStatusTip(tr("Scroll the current pane to the left without changing the play position")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + action = new QAction(tr("Peek &Right"), this); + action->setShortcut(tr("Alt+Right")); + action->setStatusTip(tr("Scroll the current pane to the right without changing the play position")); + connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); + connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + menu->addSeparator(); + + m_keyReference->setCategory(tr("Zoom")); + + action = new QAction(il.load("zoom-in"), + tr("Zoom &In"), this); + action->setShortcut(tr("Up")); + action->setStatusTip(tr("Increase the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + action = new QAction(il.load("zoom-out"), + tr("Zoom &Out"), this); + action->setShortcut(tr("Down")); + action->setStatusTip(tr("Decrease the zoom level")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + action = new QAction(tr("Restore &Default Zoom"), this); + action->setStatusTip(tr("Restore the zoom level to the default")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + menu->addAction(action); + + action = new QAction(il.load("zoom-fit"), + tr("Zoom to &Fit"), this); + action->setShortcut(tr("F")); + action->setStatusTip(tr("Zoom to show the whole file")); + connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); + connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + menu->addSeparator(); + + action = new QAction(tr("Set Displayed Fre&quency Range..."), this); + action->setStatusTip(tr("Set the minimum and maximum frequencies in the visible display")); + connect(action, SIGNAL(triggered()), this, SLOT(editDisplayExtents())); + menu->addAction(action); +} + +void +MainWindow::setupAnalysisMenu() +{ + if (m_mainMenusCreated) return; + + IconLoader il; + + QAction *action = 0; + + QMenu *menu = menuBar()->addMenu(tr("&Analysis")); + menu->setTearOffEnabled(true); + + m_autoAnalyse = new QAction(tr("Auto-Analyse &New Audio"), this); + m_autoAnalyse->setStatusTip(tr("Automatically trigger analysis upon opening of a new audio file.")); + m_autoAnalyse->setCheckable(true); + connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled())); + menu->addAction(m_autoAnalyse); + + action = new QAction(tr("&Analyse Now!"), this); + action->setStatusTip(tr("Trigger analysis of pitches and notes. (This will delete all existing pitches and notes.)")); + connect(action, SIGNAL(triggered()), this, SLOT(analyseNow())); + menu->addAction(action); + m_keyReference->registerShortcut(action); + + menu->addSeparator(); + + m_precise = new QAction(tr("&Unbiased Timing (slow)"), this); + m_precise->setStatusTip(tr("Use a symmetric window in YIN to remove frequency-dependent timing bias. (This is slow!)")); + m_precise->setCheckable(true); + connect(m_precise, SIGNAL(triggered()), this, SLOT(precisionAnalysisToggled())); + menu->addAction(m_precise); + + m_lowamp = new QAction(tr("&Penalise Soft Pitches"), this); + m_lowamp->setStatusTip(tr("Reduce the likelihood of detecting a pitch when the signal has low amplitude.")); + m_lowamp->setCheckable(true); + connect(m_lowamp, SIGNAL(triggered()), this, SLOT(lowampAnalysisToggled())); + menu->addAction(m_lowamp); + + m_onset = new QAction(tr("&High Onset Sensitivity"), this); + m_onset->setStatusTip(tr("Increase likelihood of separating notes, especially consecutive notes at the same pitch.")); + m_onset->setCheckable(true); + connect(m_onset, SIGNAL(triggered()), this, SLOT(onsetAnalysisToggled())); + menu->addAction(m_onset); + + m_prune = new QAction(tr("&Drop Short Notes"), this); + m_prune->setStatusTip(tr("Duration-based pruning: automatic note estimator will not output notes of less than 100ms duration.")); + m_prune->setCheckable(true); + connect(m_prune, SIGNAL(triggered()), this, SLOT(pruneAnalysisToggled())); + menu->addAction(m_prune); + + menu->addSeparator(); + + action = new QAction(tr("Reset Options to Defaults"), this); + action->setStatusTip(tr("Reset all of the Analyse menu options to their default settings.")); + connect(action, SIGNAL(triggered()), this, SLOT(resetAnalyseOptions())); + menu->addAction(action); + + updateAnalyseStates(); +} + +void +MainWindow::resetAnalyseOptions() +{ + //!!! oh no, we need to update the menu states as well... + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("auto-analysis", true); + settings.setValue("precision-analysis", false); + settings.setValue("lowamp-analysis", true); + settings.setValue("onset-analysis", true); + settings.setValue("prune-analysis", true); + settings.endGroup(); + updateAnalyseStates(); +} + +void +MainWindow::updateAnalyseStates() +{ + QSettings settings; + settings.beginGroup("Analyser"); + bool autoAnalyse = settings.value("auto-analysis", true).toBool(); + bool precise = settings.value("precision-analysis", false).toBool(); + bool lowamp = settings.value("lowamp-analysis", true).toBool(); + bool onset = settings.value("onset-analysis", true).toBool(); + bool prune = settings.value("prune-analysis", true).toBool(); + settings.endGroup(); + + m_autoAnalyse->setChecked(autoAnalyse); + m_precise->setChecked(precise); + m_lowamp->setChecked(lowamp); + m_onset->setChecked(onset); + m_prune->setChecked(prune); +} + +void +MainWindow::autoAnalysisToggled() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + + bool set = a->isChecked(); + + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("auto-analysis", set); + settings.endGroup(); +} + +void +MainWindow::precisionAnalysisToggled() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + + bool set = a->isChecked(); + + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("precision-analysis", set); + settings.endGroup(); + + // don't run analyseNow() automatically -- it's a destructive operation +} + +void +MainWindow::lowampAnalysisToggled() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + + bool set = a->isChecked(); + + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("lowamp-analysis", set); + settings.endGroup(); + + // don't run analyseNow() automatically -- it's a destructive operation +} + +void +MainWindow::onsetAnalysisToggled() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + + bool set = a->isChecked(); + + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("onset-analysis", set); + settings.endGroup(); + + // don't run analyseNow() automatically -- it's a destructive operation +} + +void +MainWindow::pruneAnalysisToggled() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + + bool set = a->isChecked(); + + QSettings settings; + settings.beginGroup("Analyser"); + settings.setValue("prune-analysis", set); + settings.endGroup(); + + // don't run analyseNow() automatically -- it's a destructive operation +} + +void +MainWindow::setupHelpMenu() +{ + QMenu *menu = menuBar()->addMenu(tr("&Help")); + menu->setTearOffEnabled(true); + + m_keyReference->setCategory(tr("Help")); + + IconLoader il; + + QString name = QApplication::applicationName(); + QAction *action; + + action = new QAction(tr("&Key and Mouse Reference"), this); + action->setShortcut(tr("F2")); + action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); + connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + action = new QAction(il.load("help"), + tr("&Help Reference"), this); + action->setShortcut(tr("F1")); + action->setStatusTip(tr("Open the %1 reference manual").arg(name)); + connect(action, SIGNAL(triggered()), this, SLOT(help())); + m_keyReference->registerShortcut(action); + menu->addAction(action); + + + action = new QAction(tr("%1 on the &Web").arg(name), this); + action->setStatusTip(tr("Open the %1 website").arg(name)); + connect(action, SIGNAL(triggered()), this, SLOT(website())); + menu->addAction(action); + + action = new QAction(tr("&About %1").arg(name), this); + action->setStatusTip(tr("Show information about %1").arg(name)); + connect(action, SIGNAL(triggered()), this, SLOT(about())); + menu->addAction(action); +} + +void +MainWindow::setupRecentFilesMenu() +{ + m_recentFilesMenu->clear(); + vector files = m_recentFiles.getRecent(); + for (size_t i = 0; i < files.size(); ++i) { + QAction *action = new QAction(files[i], this); + connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); + if (i == 0) { + action->setShortcut(tr("Ctrl+R")); + m_keyReference->registerShortcut + (tr("Re-open"), + action->shortcut().toString(), + tr("Re-open the current or most recently opened file")); + } + m_recentFilesMenu->addAction(action); + } +} + +void +MainWindow::setupToolbars() +{ + m_keyReference->setCategory(tr("Playback and Transport Controls")); + + IconLoader il; + + QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); + menu->setTearOffEnabled(true); + m_rightButtonMenu->addSeparator(); + m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); + + QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); + + QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"), + tr("Rewind to Start")); + rwdStartAction->setShortcut(tr("Home")); + rwdStartAction->setStatusTip(tr("Rewind to the start")); + connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); + connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool))); + + QAction *m_rwdAction = toolbar->addAction(il.load("rewind"), + tr("Rewind")); + m_rwdAction->setShortcut(tr("Left")); + m_rwdAction->setStatusTip(tr("Rewind to the previous one-second boundary")); + connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); + connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); + + setDefaultFfwdRwdStep(RealTime(1, 0)); + + QAction *playAction = toolbar->addAction(il.load("playpause"), + tr("Play / Pause")); + playAction->setCheckable(true); + playAction->setShortcut(tr("Space")); + playAction->setStatusTip(tr("Start or stop playback from the current position")); + connect(playAction, SIGNAL(triggered()), this, SLOT(play())); + connect(m_playSource, SIGNAL(playStatusChanged(bool)), + playAction, SLOT(setChecked(bool))); + connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool))); + + m_ffwdAction = toolbar->addAction(il.load("ffwd"), + tr("Fast Forward")); + m_ffwdAction->setShortcut(tr("Right")); + m_ffwdAction->setStatusTip(tr("Fast-forward to the next one-second boundary")); + connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); + connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); + + QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), + tr("Fast Forward to End")); + ffwdEndAction->setShortcut(tr("End")); + ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); + connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); + connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool))); + + QAction *recordAction = toolbar->addAction(il.load("record"), + tr("Record")); + recordAction->setCheckable(true); + recordAction->setShortcut(tr("Ctrl+Space")); + recordAction->setStatusTip(tr("Record a new audio file")); + connect(recordAction, SIGNAL(triggered()), this, SLOT(record())); + connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), + recordAction, SLOT(setChecked(bool))); + connect(m_recordTarget, SIGNAL(recordCompleted()), + this, SLOT(analyseNow())); + connect(this, SIGNAL(canRecord(bool)), + recordAction, SLOT(setEnabled(bool))); + + toolbar = addToolBar(tr("Play Mode Toolbar")); + + QAction *psAction = toolbar->addAction(il.load("playselection"), + tr("Constrain Playback to Selection")); + psAction->setCheckable(true); + psAction->setChecked(m_viewManager->getPlaySelectionMode()); + psAction->setShortcut(tr("s")); + psAction->setStatusTip(tr("Constrain playback to the selected regions")); + connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), + psAction, SLOT(setChecked(bool))); + connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); + connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool))); + + QAction *plAction = toolbar->addAction(il.load("playloop"), + tr("Loop Playback")); + plAction->setCheckable(true); + plAction->setChecked(m_viewManager->getPlayLoopMode()); + plAction->setShortcut(tr("l")); + plAction->setStatusTip(tr("Loop playback")); + connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), + plAction, SLOT(setChecked(bool))); + connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); + connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool))); + + QAction *oneLeftAction = new QAction(tr("&One Note Left"), this); + oneLeftAction->setShortcut(tr("Ctrl+Left")); + oneLeftAction->setStatusTip(tr("Move cursor to the preceding note (or silence) onset.")); + connect(oneLeftAction, SIGNAL(triggered()), this, SLOT(moveOneNoteLeft())); + connect(this, SIGNAL(canScroll(bool)), oneLeftAction, SLOT(setEnabled(bool))); + + QAction *oneRightAction = new QAction(tr("O&ne Note Right"), this); + oneRightAction->setShortcut(tr("Ctrl+Right")); + oneRightAction->setStatusTip(tr("Move cursor to the succeeding note (or silence).")); + connect(oneRightAction, SIGNAL(triggered()), this, SLOT(moveOneNoteRight())); + connect(this, SIGNAL(canScroll(bool)), oneRightAction, SLOT(setEnabled(bool))); + + QAction *selectOneLeftAction = new QAction(tr("&Select One Note Left"), this); + selectOneLeftAction->setShortcut(tr("Ctrl+Shift+Left")); + selectOneLeftAction->setStatusTip(tr("Select to the preceding note (or silence) onset.")); + connect(selectOneLeftAction, SIGNAL(triggered()), this, SLOT(selectOneNoteLeft())); + connect(this, SIGNAL(canScroll(bool)), selectOneLeftAction, SLOT(setEnabled(bool))); + + QAction *selectOneRightAction = new QAction(tr("S&elect One Note Right"), this); + selectOneRightAction->setShortcut(tr("Ctrl+Shift+Right")); + selectOneRightAction->setStatusTip(tr("Select to the succeeding note (or silence).")); + connect(selectOneRightAction, SIGNAL(triggered()), this, SLOT(selectOneNoteRight())); + connect(this, SIGNAL(canScroll(bool)), selectOneRightAction, SLOT(setEnabled(bool))); + + m_keyReference->registerShortcut(psAction); + m_keyReference->registerShortcut(plAction); + m_keyReference->registerShortcut(playAction); + m_keyReference->registerShortcut(recordAction); + m_keyReference->registerShortcut(m_rwdAction); + m_keyReference->registerShortcut(m_ffwdAction); + m_keyReference->registerShortcut(rwdStartAction); + m_keyReference->registerShortcut(ffwdEndAction); + m_keyReference->registerShortcut(recordAction); + m_keyReference->registerShortcut(oneLeftAction); + m_keyReference->registerShortcut(oneRightAction); + m_keyReference->registerShortcut(selectOneLeftAction); + m_keyReference->registerShortcut(selectOneRightAction); + + menu->addAction(playAction); + menu->addAction(psAction); + menu->addAction(plAction); + menu->addSeparator(); + menu->addAction(m_rwdAction); + menu->addAction(m_ffwdAction); + menu->addSeparator(); + menu->addAction(rwdStartAction); + menu->addAction(ffwdEndAction); + menu->addSeparator(); + menu->addAction(oneLeftAction); + menu->addAction(oneRightAction); + menu->addAction(selectOneLeftAction); + menu->addAction(selectOneRightAction); + menu->addSeparator(); + menu->addAction(recordAction); + menu->addSeparator(); + + m_rightButtonPlaybackMenu->addAction(playAction); + m_rightButtonPlaybackMenu->addAction(psAction); + m_rightButtonPlaybackMenu->addAction(plAction); + m_rightButtonPlaybackMenu->addSeparator(); + m_rightButtonPlaybackMenu->addAction(m_rwdAction); + m_rightButtonPlaybackMenu->addAction(m_ffwdAction); + m_rightButtonPlaybackMenu->addSeparator(); + m_rightButtonPlaybackMenu->addAction(rwdStartAction); + m_rightButtonPlaybackMenu->addAction(ffwdEndAction); + m_rightButtonPlaybackMenu->addSeparator(); + m_rightButtonPlaybackMenu->addAction(oneLeftAction); + m_rightButtonPlaybackMenu->addAction(oneRightAction); + m_rightButtonPlaybackMenu->addAction(selectOneLeftAction); + m_rightButtonPlaybackMenu->addAction(selectOneRightAction); + m_rightButtonPlaybackMenu->addSeparator(); + m_rightButtonPlaybackMenu->addAction(recordAction); + m_rightButtonPlaybackMenu->addSeparator(); + + QAction *fastAction = menu->addAction(tr("Speed Up")); + fastAction->setShortcut(tr("Ctrl+PgUp")); + fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); + connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); + connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); + + QAction *slowAction = menu->addAction(tr("Slow Down")); + slowAction->setShortcut(tr("Ctrl+PgDown")); + slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); + connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); + connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); + + QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); + normalAction->setShortcut(tr("Ctrl+Home")); + normalAction->setStatusTip(tr("Restore non-time-stretched playback")); + connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); + connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); + + m_keyReference->registerShortcut(fastAction); + m_keyReference->registerShortcut(slowAction); + m_keyReference->registerShortcut(normalAction); + + m_rightButtonPlaybackMenu->addAction(fastAction); + m_rightButtonPlaybackMenu->addAction(slowAction); + m_rightButtonPlaybackMenu->addAction(normalAction); + + toolbar = new QToolBar(tr("Playback Controls")); + addToolBar(Qt::BottomToolBarArea, toolbar); + + toolbar->addWidget(m_playSpeed); + toolbar->addWidget(m_fader); + + toolbar = addToolBar(tr("Show and Play")); + addToolBar(Qt::BottomToolBarArea, toolbar); + + m_showAudio = toolbar->addAction(il.load("waveform"), tr("Show Audio")); + m_showAudio->setCheckable(true); + connect(m_showAudio, SIGNAL(triggered()), this, SLOT(showAudioToggled())); + connect(this, SIGNAL(canPlay(bool)), m_showAudio, SLOT(setEnabled(bool))); + + m_playAudio = toolbar->addAction(il.load("speaker"), tr("Play Audio")); + m_playAudio->setCheckable(true); + connect(m_playAudio, SIGNAL(triggered()), this, SLOT(playAudioToggled())); + connect(this, SIGNAL(canPlayWaveform(bool)), m_playAudio, SLOT(setEnabled(bool))); + + int lpwSize, bigLpwSize; +#ifdef Q_OS_MAC + lpwSize = m_viewManager->scalePixelSize(32); // Mac toolbars are fatter + bigLpwSize = int(lpwSize * 2.2); +#else + lpwSize = m_viewManager->scalePixelSize(26); + bigLpwSize = int(lpwSize * 2.8); +#endif + + m_audioLPW->setImageSize(lpwSize); + m_audioLPW->setBigImageSize(bigLpwSize); + toolbar->addWidget(m_audioLPW); + + // Pitch (f0) + QLabel *spacer = new QLabel; // blank + spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); + toolbar->addWidget(spacer); + + m_showPitch = toolbar->addAction(il.load("values"), tr("Show Pitch Track")); + m_showPitch->setCheckable(true); + connect(m_showPitch, SIGNAL(triggered()), this, SLOT(showPitchToggled())); + connect(this, SIGNAL(canPlay(bool)), m_showPitch, SLOT(setEnabled(bool))); + + if (m_withSonification) { + m_playPitch = toolbar->addAction(il.load("speaker"), tr("Play Pitch Track")); + m_playPitch->setCheckable(true); + connect(m_playPitch, SIGNAL(triggered()), this, SLOT(playPitchToggled())); + connect(this, SIGNAL(canPlayPitch(bool)), m_playPitch, SLOT(setEnabled(bool))); + + m_pitchLPW->setImageSize(lpwSize); + m_pitchLPW->setBigImageSize(bigLpwSize); + toolbar->addWidget(m_pitchLPW); + } else { + m_playPitch = 0; + } + + // Notes + spacer = new QLabel; + spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); + toolbar->addWidget(spacer); + + m_showNotes = toolbar->addAction(il.load("notes"), tr("Show Notes")); + m_showNotes->setCheckable(true); + connect(m_showNotes, SIGNAL(triggered()), this, SLOT(showNotesToggled())); + connect(this, SIGNAL(canPlay(bool)), m_showNotes, SLOT(setEnabled(bool))); + + if (m_withSonification) { + m_playNotes = toolbar->addAction(il.load("speaker"), tr("Play Notes")); + m_playNotes->setCheckable(true); + connect(m_playNotes, SIGNAL(triggered()), this, SLOT(playNotesToggled())); + connect(this, SIGNAL(canPlayNotes(bool)), m_playNotes, SLOT(setEnabled(bool))); + + m_notesLPW->setImageSize(lpwSize); + m_notesLPW->setBigImageSize(bigLpwSize); + toolbar->addWidget(m_notesLPW); + } else { + m_playNotes = 0; + } + + // Spectrogram + spacer = new QLabel; + spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); + toolbar->addWidget(spacer); + + if (!m_withSpectrogram) + { + m_showSpect = new QAction(tr("Show Spectrogram"), this); + } else { + m_showSpect = toolbar->addAction(il.load("spectrogram"), tr("Show Spectrogram")); + } + m_showSpect->setCheckable(true); + connect(m_showSpect, SIGNAL(triggered()), this, SLOT(showSpectToggled())); + connect(this, SIGNAL(canPlay(bool)), m_showSpect, SLOT(setEnabled(bool))); + + Pane::registerShortcuts(*m_keyReference); + + updateLayerStatuses(); +} + + +void +MainWindow::moveOneNoteRight() +{ + // cerr << "MainWindow::moveOneNoteRight" << endl; + moveByOneNote(true, false); +} + +void +MainWindow::moveOneNoteLeft() +{ + // cerr << "MainWindow::moveOneNoteLeft" << endl; + moveByOneNote(false, false); +} + +void +MainWindow::selectOneNoteRight() +{ + moveByOneNote(true, true); +} + +void +MainWindow::selectOneNoteLeft() +{ + moveByOneNote(false, true); +} + + +void +MainWindow::moveByOneNote(bool right, bool doSelect) +{ + sv_frame_t frame = m_viewManager->getPlaybackFrame(); + cerr << "MainWindow::moveByOneNote startframe: " << frame << endl; + + bool isAtSelectionBoundary = false; + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + if (!selections.empty()) { + Selection sel = *selections.begin(); + isAtSelectionBoundary = (frame == sel.getStartFrame()) || (frame == sel.getEndFrame()); + } + if (!doSelect || !isAtSelectionBoundary) { + m_selectionAnchor = frame; + } + + Layer *layer = m_analyser->getLayer(Analyser::Notes); + if (!layer) return; + + auto model = ModelById::getAs(layer->getModel()); + if (!model) return; + + //!!! This seems like a strange and inefficient way to do this - + //!!! there is almost certainly a better way making use of + //!!! EventSeries api + + EventVector points = model->getAllEvents(); + if (points.empty()) return; + + EventVector::iterator i = points.begin(); + std::set snapFrames; + snapFrames.insert(0); + while (i != points.end()) { + snapFrames.insert(i->getFrame()); + snapFrames.insert(i->getFrame() + i->getDuration() + 1); + ++i; + } + std::set::iterator i2; + if (snapFrames.find(frame) == snapFrames.end()) { + // we're not on an existing snap point, so go to previous + snapFrames.insert(frame); + } + i2 = snapFrames.find(frame); + if (right) { + i2++; + if (i2 == snapFrames.end()) i2--; + } else { + if (i2 != snapFrames.begin()) i2--; + } + frame = *i2; + m_viewManager->setPlaybackFrame(frame); + if (doSelect) { + Selection sel; + if (frame > m_selectionAnchor) { + sel = Selection(m_selectionAnchor, frame); + } else { + sel = Selection(frame, m_selectionAnchor); + } + m_viewManager->setSelection(sel); + } + cerr << "MainWindow::moveByOneNote endframe: " << frame << endl; +} + +void +MainWindow::toolNavigateSelected() +{ + m_viewManager->setToolMode(ViewManager::NavigateMode); + m_intelligentActionOn = true; +} + +void +MainWindow::toolEditSelected() +{ + cerr << "MainWindow::toolEditSelected" << endl; + m_viewManager->setToolMode(ViewManager::NoteEditMode); + m_intelligentActionOn = true; + m_analyser->setIntelligentActions(m_intelligentActionOn); +} + +void +MainWindow::toolFreeEditSelected() +{ + m_viewManager->setToolMode(ViewManager::NoteEditMode); + m_intelligentActionOn = false; + m_analyser->setIntelligentActions(m_intelligentActionOn); +} + +void +MainWindow::updateMenuStates() +{ + MainWindowBase::updateMenuStates(); + + Pane *currentPane = 0; + Layer *currentLayer = 0; + + if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentLayer = currentPane->getSelectedLayer(); + + bool haveMainModel = + (getMainModel() != 0); + bool havePlayTarget = + (m_playTarget != 0 || m_audioIO != 0); + bool haveCurrentPane = + (currentPane != 0); + bool haveCurrentLayer = + (haveCurrentPane && + (currentLayer != 0)); + bool haveSelection = + (m_viewManager && + !m_viewManager->getSelections().empty()); + bool haveCurrentTimeInstantsLayer = + (haveCurrentLayer && + qobject_cast(currentLayer)); + bool haveCurrentTimeValueLayer = + (haveCurrentLayer && + qobject_cast(currentLayer)); + bool pitchCandidatesVisible = + m_analyser->arePitchCandidatesShown(); + + emit canChangePlaybackSpeed(true); + int v = m_playSpeed->value(); + emit canSpeedUpPlayback(v < m_playSpeed->maximum()); + emit canSlowDownPlayback(v > m_playSpeed->minimum()); + + bool haveWaveform = + m_analyser->isVisible(Analyser::Audio) && + m_analyser->getLayer(Analyser::Audio); + + bool havePitchTrack = + m_analyser->isVisible(Analyser::PitchTrack) && + m_analyser->getLayer(Analyser::PitchTrack); + + bool haveNotes = + m_analyser->isVisible(Analyser::Notes) && + m_analyser->getLayer(Analyser::Notes); + + emit canExportPitchTrack(havePitchTrack); + emit canExportNotes(haveNotes); + emit canSnapNotes(haveSelection && haveNotes); + + emit canPlayWaveform(haveWaveform && haveMainModel && havePlayTarget); + emit canPlayPitch(havePitchTrack && haveMainModel && havePlayTarget); + emit canPlayNotes(haveNotes && haveMainModel && havePlayTarget); + + if (pitchCandidatesVisible) { + m_showCandidatesAction->setText(tr("Hide Pitch Candidates")); + m_showCandidatesAction->setStatusTip(tr("Remove the display of alternate pitch candidates for the selected region")); + } else { + m_showCandidatesAction->setText(tr("Show Pitch Candidates")); + m_showCandidatesAction->setStatusTip(tr("Show alternate pitch candidates for the selected region")); + } + + if (m_ffwdAction && m_rwdAction) { + if (haveCurrentTimeInstantsLayer) { + m_ffwdAction->setText(tr("Fast Forward to Next Instant")); + m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); + m_rwdAction->setText(tr("Rewind to Previous Instant")); + m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); + } else if (haveCurrentTimeValueLayer) { + m_ffwdAction->setText(tr("Fast Forward to Next Point")); + m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); + m_rwdAction->setText(tr("Rewind to Previous Point")); + m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); + } else { + m_ffwdAction->setText(tr("Fast Forward")); + m_ffwdAction->setStatusTip(tr("Fast forward")); + m_rwdAction->setText(tr("Rewind")); + m_rwdAction->setStatusTip(tr("Rewind")); + } + } +} + +void +MainWindow::showAudioToggled() +{ + m_analyser->toggleVisible(Analyser::Audio); + + QSettings settings; + settings.beginGroup("MainWindow"); + + bool playOn = false; + if (m_analyser->isVisible(Analyser::Audio)) { + // just switched layer on; check whether playback was also on previously + playOn = settings.value("playaudiowas", true).toBool(); + } else { + settings.setValue("playaudiowas", m_playAudio->isChecked()); + } + m_analyser->setAudible(Analyser::Audio, playOn); + + settings.endGroup(); + + updateMenuStates(); + updateLayerStatuses(); +} + +void +MainWindow::showPitchToggled() +{ + m_analyser->toggleVisible(Analyser::PitchTrack); + + QSettings settings; + settings.beginGroup("MainWindow"); + + bool playOn = false; + if (m_analyser->isVisible(Analyser::PitchTrack)) { + // just switched layer on; check whether playback was also on previously + playOn = settings.value("playpitchwas", true).toBool(); + } else { + settings.setValue("playpitchwas", m_playPitch->isChecked()); + } + m_analyser->setAudible(Analyser::PitchTrack, playOn); + + settings.endGroup(); + + updateMenuStates(); + updateLayerStatuses(); +} + +void +MainWindow::showSpectToggled() +{ + m_analyser->toggleVisible(Analyser::Spectrogram); +} + +void +MainWindow::showNotesToggled() +{ + m_analyser->toggleVisible(Analyser::Notes); + + QSettings settings; + settings.beginGroup("MainWindow"); + + bool playOn = false; + if (m_analyser->isVisible(Analyser::Notes)) { + // just switched layer on; check whether playback was also on previously + playOn = settings.value("playnoteswas", true).toBool(); + } else { + settings.setValue("playnoteswas", m_playNotes->isChecked()); + } + m_analyser->setAudible(Analyser::Notes, playOn); + + settings.endGroup(); + + updateMenuStates(); + updateLayerStatuses(); +} + +void +MainWindow::playAudioToggled() +{ + m_analyser->toggleAudible(Analyser::Audio); + updateLayerStatuses(); +} + +void +MainWindow::playPitchToggled() +{ + m_analyser->toggleAudible(Analyser::PitchTrack); + updateLayerStatuses(); +} + +void +MainWindow::playNotesToggled() +{ + m_analyser->toggleAudible(Analyser::Notes); + updateLayerStatuses(); +} + +void +MainWindow::updateLayerStatuses() +{ + m_showAudio->setChecked(m_analyser->isVisible(Analyser::Audio)); + m_playAudio->setChecked(m_analyser->isAudible(Analyser::Audio)); + m_audioLPW->setEnabled(m_analyser->isAudible(Analyser::Audio)); + m_audioLPW->setLevel(m_analyser->getGain(Analyser::Audio)); + m_audioLPW->setPan(m_analyser->getPan(Analyser::Audio)); + + m_showPitch->setChecked(m_analyser->isVisible(Analyser::PitchTrack)); + m_playPitch->setChecked(m_analyser->isAudible(Analyser::PitchTrack)); + m_pitchLPW->setEnabled(m_analyser->isAudible(Analyser::PitchTrack)); + m_pitchLPW->setLevel(m_analyser->getGain(Analyser::PitchTrack)); + m_pitchLPW->setPan(m_analyser->getPan(Analyser::PitchTrack)); + + m_showNotes->setChecked(m_analyser->isVisible(Analyser::Notes)); + m_playNotes->setChecked(m_analyser->isAudible(Analyser::Notes)); + m_notesLPW->setEnabled(m_analyser->isAudible(Analyser::Notes)); + m_notesLPW->setLevel(m_analyser->getGain(Analyser::Notes)); + m_notesLPW->setPan(m_analyser->getPan(Analyser::Notes)); + + m_showSpect->setChecked(m_analyser->isVisible(Analyser::Spectrogram)); +} + +void +MainWindow::editDisplayExtents() +{ + double min, max; + double vmin = 0; + double vmax = getMainModel()->getSampleRate() /2; + + if (!m_analyser->getDisplayFrequencyExtents(min, max)) { + //!!! + return; + } + + RangeInputDialog dialog(tr("Set frequency range"), + tr("Enter new frequency range, from %1 to %2 Hz.\nThese values will be rounded to the nearest spectrogram bin.") + .arg(vmin).arg(vmax), + "Hz", float(vmin), float(vmax), this); + dialog.setRange(float(min), float(max)); + + if (dialog.exec() == QDialog::Accepted) { + float fmin, fmax; + dialog.getRange(fmin, fmax); + min = fmin; + max = fmax; + if (min > max) { + double tmp = max; + max = min; + min = tmp; + } + m_analyser->setDisplayFrequencyExtents(min, max); + } +} + +void +MainWindow::updateDescriptionLabel() +{ + // Nothing, we don't have one +} + +void +MainWindow::documentModified() +{ + MainWindowBase::documentModified(); +} + +void +MainWindow::documentRestored() +{ + MainWindowBase::documentRestored(); +} + +void +MainWindow::newSession() +{ + if (!checkSaveModified()) return; + + closeSession(); + createDocument(); + m_document->setAutoAlignment(true); + + Pane *pane = m_paneStack->addPane(); + pane->setPlaybackFollow(PlaybackScrollPage); + + m_viewManager->setGlobalCentreFrame + (pane->getFrameForX(width() / 2)); + + connect(pane, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + +// Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); +// m_document->addLayerToView(pane, waveform); + + m_overview->registerView(pane); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + updateMenuStates(); +} + +void +MainWindow::documentReplaced() +{ + if (m_document) { + connect(m_document, SIGNAL(activity(QString)), + m_activityLog, SLOT(activityHappened(QString))); + } +} + +void +MainWindow::closeSession() +{ + if (!checkSaveModified()) return; + + m_analyser->fileClosed(); + + while (m_paneStack->getPaneCount() > 0) { + + Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_overview->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + while (m_paneStack->getHiddenPaneCount() > 0) { + + Pane *pane = m_paneStack->getHiddenPane + (m_paneStack->getHiddenPaneCount() - 1); + + while (pane->getLayerCount() > 0) { + m_document->removeLayerFromView + (pane, pane->getLayer(pane->getLayerCount() - 1)); + } + + m_overview->unregisterView(pane); + m_paneStack->deletePane(pane); + } + + delete m_document; + m_document = 0; + m_viewManager->clearSelections(); + m_timeRulerLayer = 0; // document owned this + + m_sessionFile = ""; + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + documentRestored(); +} + +void +MainWindow::openFile() +{ + QString orig = m_audioFile; + if (orig == "") orig = "."; + else orig = QFileInfo(orig).absoluteDir().canonicalPath(); + + QString path = getOpenFileName(FileFinder::AnyFile); + + if (path.isEmpty()) return; + + FileOpenStatus status = openPath(path, ReplaceSession); + + if (status == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("File open failed

File \"%1\" could not be opened").arg(path)); + } else if (status == FileOpenWrongMode) { + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio required

Please load at least one audio file before importing annotation data")); + } +} + +void +MainWindow::openLocation() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + QString lastLocation = settings.value("lastremote", "").toString(); + + bool ok = false; + QString text = QInputDialog::getText + (this, tr("Open Location"), + tr("Please enter the URL of the location to open:"), + QLineEdit::Normal, lastLocation, &ok); + + if (!ok) return; + + settings.setValue("lastremote", text); + + if (text.isEmpty()) return; + + FileOpenStatus status = openPath(text, ReplaceSession); + + if (status == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open location"), + tr("Open failed

URL \"%1\" could not be opened").arg(text)); + } else if (status == FileOpenWrongMode) { + QMessageBox::critical(this, tr("Failed to open location"), + tr("Audio required

Please load at least one audio file before importing annotation data")); + } +} + +void +MainWindow::openRecentFile() +{ + QObject *obj = sender(); + QAction *action = qobject_cast(obj); + + if (!action) { + cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" + << endl; + return; + } + + QString path = action->text(); + if (path == "") return; + + FileOpenStatus status = openPath(path, ReplaceSession); + + if (status == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open location"), + tr("Open failed

File or URL \"%1\" could not be opened").arg(path)); + } else if (status == FileOpenWrongMode) { + QMessageBox::critical(this, tr("Failed to open location"), + tr("Audio required

Please load at least one audio file before importing annotation data")); + } +} + +void +MainWindow::paneAdded(Pane *pane) +{ + pane->setPlaybackFollow(PlaybackScrollPage); + m_paneStack->sizePanesEqually(); + if (m_overview) m_overview->registerView(pane); +} + +void +MainWindow::paneHidden(Pane *pane) +{ + if (m_overview) m_overview->unregisterView(pane); +} + +void +MainWindow::paneAboutToBeDeleted(Pane *pane) +{ + if (m_overview) m_overview->unregisterView(pane); +} + +void +MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) +{ + if (pane) m_paneStack->setCurrentPane(pane); + + for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { + + FileOpenStatus status = openPath(*i, ReplaceSession); + + if (status == FileOpenFailed) { + QMessageBox::critical(this, tr("Failed to open dropped URL"), + tr("Open failed

Dropped URL \"%1\" could not be opened").arg(*i)); + } else if (status == FileOpenWrongMode) { + QMessageBox::critical(this, tr("Failed to open dropped URL"), + tr("Audio required

Please load at least one audio file before importing annotation data")); + } + } +} + +void +MainWindow::paneDropAccepted(Pane *pane, QString text) +{ + if (pane) m_paneStack->setCurrentPane(pane); + + QUrl testUrl(text); + if (testUrl.scheme() == "file" || + testUrl.scheme() == "http" || + testUrl.scheme() == "ftp") { + QStringList list; + list.push_back(text); + paneDropAccepted(pane, list); + return; + } + + //!!! open as text -- but by importing as if a CSV, or just adding + //to a text layer? +} + +void +MainWindow::closeEvent(QCloseEvent *e) +{ +// cerr << "MainWindow::closeEvent" << endl; + + if (m_openingAudioFile) { +// cerr << "Busy - ignoring close event" << endl; + e->ignore(); + return; + } + + if (!m_abandoning && !checkSaveModified()) { +// cerr << "Ignoring close event" << endl; + e->ignore(); + return; + } + + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("size", size()); + settings.setValue("position", pos()); + settings.endGroup(); + + delete m_keyReference; + m_keyReference = 0; + + closeSession(); + + e->accept(); + return; +} + +bool +MainWindow::commitData(bool mayAskUser) +{ + if (mayAskUser) { + bool rv = checkSaveModified(); + return rv; + } else { + if (!m_documentModified) return true; + + // If we can't check with the user first, then we can't save + // to the original session file (even if we have it) -- have + // to use a temporary file + + QString svDirBase = ".sv1"; + QString svDir = QDir::home().filePath(svDirBase); + + if (!QFileInfo(svDir).exists()) { + if (!QDir::home().mkdir(svDirBase)) return false; + } else { + if (!QFileInfo(svDir).isDir()) return false; + } + + // This name doesn't have to be unguessable +#ifndef _WIN32 + QString fname = QString("tmp-%1-%2.sv") + .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) + .arg(QProcess().pid()); +#else + QString fname = QString("tmp-%1.sv") + .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); +#endif + QString fpath = QDir(svDir).filePath(fname); + if (saveSessionFile(fpath)) { + m_recentFiles.addFile(fpath); + return true; + } else { + return false; + } + } +} + +bool +MainWindow::checkSaveModified() +{ + // Called before some destructive operation (e.g. new session, + // exit program). Return true if we can safely proceed, false to + // cancel. + + if (!m_documentModified) return true; + + int button = + QMessageBox::warning(this, + tr("Session modified"), + tr("The current session has been modified.\nDo you want to save it?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Yes); + + if (button == QMessageBox::Yes) { + saveSession(); + if (m_documentModified) { // save failed -- don't proceed! + return false; + } else { + return true; // saved, so it's safe to continue now + } + } else if (button == QMessageBox::No) { + m_documentModified = false; // so we know to abandon it + return true; + } + + // else cancel + return false; +} + +bool +MainWindow::waitForInitialAnalysis() +{ + // Called before saving a session. We can't safely save while the + // initial analysis is happening, because then we end up with an + // incomplete session on reload. There are certainly theoretically + // better ways to handle this... + + QSettings settings; + settings.beginGroup("Analyser"); + bool autoAnalyse = settings.value("auto-analysis", true).toBool(); + settings.endGroup(); + + if (!autoAnalyse) { + return true; + } + + if (!m_analyser || m_analyser->getInitialAnalysisCompletion() >= 100) { + return true; + } + + QMessageBox mb(QMessageBox::Information, + tr("Waiting for analysis"), + tr("Waiting for initial analysis to finish before loading or saving..."), + QMessageBox::Cancel, + this); + + connect(m_analyser, SIGNAL(initialAnalysisCompleted()), + &mb, SLOT(accept())); + + if (mb.exec() == QDialog::Accepted) { + return true; + } else { + return false; + } +} + +void +MainWindow::saveSession() +{ + // We do not want to save mid-analysis regions -- that would cause + // confusion on reloading + m_analyser->clearReAnalysis(); + clearSelection(); + + if (m_sessionFile != "") { + if (!saveSessionFile(m_sessionFile)) { + QMessageBox::critical + (this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); + } else { + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + } + } else { + saveSessionAs(); + } +} + +void +MainWindow::saveSessionInAudioPath() +{ + if (m_audioFile == "") return; + + if (!waitForInitialAnalysis()) return; + + // We do not want to save mid-analysis regions -- that would cause + // confusion on reloading + m_analyser->clearReAnalysis(); + clearSelection(); + + QString filepath = QFileInfo(m_audioFile).absoluteDir().canonicalPath(); + QString basename = QFileInfo(m_audioFile).completeBaseName(); + + QString path = QDir(filepath).filePath(basename + ".ton"); + + cerr << path << endl; + + // We don't want to overwrite an existing .ton file unless we put + // it there in the first place + bool shouldVerify = true; + if (m_sessionFile == path) { + shouldVerify = false; + } + + if (shouldVerify && QFileInfo(path).exists()) { + if (QMessageBox::question(0, tr("File exists"), + tr("File exists

The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), + QMessageBox::Ok, + QMessageBox::Cancel) != QMessageBox::Ok) { + return; + } + } + + if (!waitForInitialAnalysis()) { + QMessageBox::warning(this, tr("File not saved"), + tr("Wait cancelled: the session has not been saved.")); + } + + if (!saveSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(path)); + } else { + setWindowTitle(tr("%1: %2") + .arg(QApplication::applicationName()) + .arg(QFileInfo(path).fileName())); + m_sessionFile = path; + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + m_recentFiles.addFile(path); + } +} + +void +MainWindow::saveSessionAs() +{ + // We do not want to save mid-analysis regions -- that would cause + // confusion on reloading + m_analyser->clearReAnalysis(); + clearSelection(); + + QString path = getSaveFileName(FileFinder::SessionFile); + + if (path == "") { + return; + } + + if (!waitForInitialAnalysis()) { + QMessageBox::warning(this, tr("File not saved"), + tr("Wait cancelled: the session has not been saved.")); + return; + } + + if (!saveSessionFile(path)) { + QMessageBox::critical(this, tr("Failed to save file"), + tr("Session file \"%1\" could not be saved.").arg(path)); + } else { + setWindowTitle(tr("%1: %2") + .arg(QApplication::applicationName()) + .arg(QFileInfo(path).fileName())); + m_sessionFile = path; + CommandHistory::getInstance()->documentSaved(); + documentRestored(); + m_recentFiles.addFile(path); + } +} + +QString +MainWindow::exportToSVL(QString path, Layer *layer) +{ + auto model = ModelById::get(layer->getModel()); + if (!model) return "Internal error: No model in layer"; + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return tr("Failed to open file %1 for writing").arg(path); + } else { + QTextStream out(&file); + out << "\n" + << "\n" + << "\n" + << " \n"; + + model->toXml(out, " "); + + out << " \n" + << " \n"; + + layer->toXml(out, " "); + + out << " \n" + << "\n"; + + return ""; + } +} + +void +MainWindow::importPitchLayer() +{ + QString path = getOpenFileName(FileFinder::LayerFileNoMidiNonSV); + if (path == "") return; + + FileOpenStatus status = importPitchLayer(path); + + if (status == FileOpenFailed) { + emit hideSplash(); + QMessageBox::critical(this, tr("Failed to open file"), + tr("File open failed

Layer file %1 could not be opened.").arg(path)); + return; + } else if (status == FileOpenWrongMode) { + emit hideSplash(); + QMessageBox::critical(this, tr("Failed to open file"), + tr("Audio required

Unable to load layer data from \"%1\" without an audio file.
Please load at least one audio file before importing annotations.").arg(path)); + } +} + +MainWindow::FileOpenStatus +MainWindow::importPitchLayer(FileSource source) +{ + if (!source.isAvailable()) return FileOpenFailed; + source.waitForData(); + + if (!waitForInitialAnalysis()) return FileOpenCancelled; + + QString path = source.getLocalFilename(); + + RDFImporter::RDFDocumentType rdfType = + RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); + + if (rdfType != RDFImporter::NotRDF) { + + //!!! + return FileOpenFailed; + + } else if (source.getExtension().toLower() == "svl" || + (source.getExtension().toLower() == "xml" && + (SVFileReader::identifyXmlFile(source.getLocalFilename()) + == SVFileReader::SVLayerFile))) { + + //!!! + return FileOpenFailed; + + } else { + + try { + + CSVFormat format(path); + format.setSampleRate(getMainModel()->getSampleRate()); + + if (format.getModelType() != CSVFormat::TwoDimensionalModel) { + //!!! error report + return FileOpenFailed; + } + + Model *model = DataFileReaderFactory::loadCSV + (path, format, getMainModel()->getSampleRate()); + + if (model) { + + SVDEBUG << "MainWindow::importPitchLayer: Have model" << endl; + + ModelId modelId = ModelById::add + (std::shared_ptr(model)); + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import Pitch Track"), true); + + Layer *newLayer = m_document->createImportedLayer(modelId); + + m_analyser->takePitchTrackFrom(newLayer); + + m_document->deleteLayer(newLayer); + + CommandHistory::getInstance()->endCompoundOperation(); + + if (!source.isRemote()) { + registerLastOpenedFilePath + (FileFinder::LayerFile, + path); // for file dialog + } + + return FileOpenSucceeded; + } + } catch (DataFileReaderFactory::Exception e) { + if (e == DataFileReaderFactory::ImportCancelled) { + return FileOpenCancelled; + } + } + } + + return FileOpenFailed; +} + +void +MainWindow::exportPitchLayer() +{ + Layer *layer = m_analyser->getLayer(Analyser::PitchTrack); + if (!layer) return; + + auto model = ModelById::getAs(layer->getModel()); + if (!model) return; + + FileFinder::FileType type = FileFinder::LayerFileNoMidiNonSV; + + QString path = getSaveFileName(type); + + if (path == "") return; + + if (!waitForInitialAnalysis()) return; + + if (QFileInfo(path).suffix() == "") path += ".svl"; + + QString suffix = QFileInfo(path).suffix().toLower(); + + QString error; + + if (suffix == "xml" || suffix == "svl") { + + error = exportToSVL(path, layer); + + } else if (suffix == "ttl" || suffix == "n3") { + + RDFExporter exporter(path, model.get()); + exporter.write(); + if (!exporter.isOK()) { + error = exporter.getError(); + } + + } else { + + DataExportOptions options = DataExportFillGaps; + + CSVFileWriter writer(path, model.get(), + ((suffix == "csv") ? "," : "\t"), + options); + writer.write(); + + if (!writer.isOK()) { + error = writer.getError(); + } + } + + if (error != "") { + QMessageBox::critical(this, tr("Failed to write file"), error); + } else { + emit activity(tr("Export layer to \"%1\"").arg(path)); + } +} + +void +MainWindow::exportNoteLayer() +{ + Layer *layer = m_analyser->getLayer(Analyser::Notes); + if (!layer) return; + + auto model = ModelById::getAs(layer->getModel()); + if (!model) return; + + FileFinder::FileType type = FileFinder::LayerFileNonSV; + + QString path = getSaveFileName(type); + + if (path == "") return; + + if (QFileInfo(path).suffix() == "") path += ".svl"; + + QString suffix = QFileInfo(path).suffix().toLower(); + + QString error; + + if (suffix == "xml" || suffix == "svl") { + + error = exportToSVL(path, layer); + + } else if (suffix == "mid" || suffix == "midi") { + + MIDIFileWriter writer(path, model.get(), model->getSampleRate()); + writer.write(); + if (!writer.isOK()) { + error = writer.getError(); + } + + } else if (suffix == "ttl" || suffix == "n3") { + + RDFExporter exporter(path, model.get()); + exporter.write(); + if (!exporter.isOK()) { + error = exporter.getError(); + } + + } else { + + DataExportOptions options = DataExportOmitLevels; + + CSVFileWriter writer(path, model.get(), + ((suffix == "csv") ? "," : "\t"), + options); + writer.write(); + + if (!writer.isOK()) { + error = writer.getError(); + } + } + + if (error != "") { + QMessageBox::critical(this, tr("Failed to write file"), error); + } else { + emit activity(tr("Export layer to \"%1\"").arg(path)); + } +} + +void +MainWindow::browseRecordedAudio() +{ + if (!m_recordTarget) return; + + QString path = RecordDirectory::getRecordContainerDirectory(); + if (path == "") path = RecordDirectory::getRecordDirectory(); + if (path == "") return; + + openLocalFolder(path); +} + +void +MainWindow::doubleClickSelectInvoked(sv_frame_t frame) +{ + sv_frame_t f0, f1; + m_analyser->getEnclosingSelectionScope(frame, f0, f1); + + cerr << "MainWindow::doubleClickSelectInvoked(" << frame << "): [" << f0 << "," << f1 << "]" << endl; + + Selection sel(f0, f1); + m_viewManager->setSelection(sel); +} + +void +MainWindow::abandonSelection() +{ + // Named abandonSelection rather than clearSelection to indicate + // that this is an active operation -- it restores the original + // content of the pitch track in the selected region rather than + // simply un-selecting. + + cerr << "MainWindow::abandonSelection()" << endl; + + CommandHistory::getInstance()->startCompoundOperation(tr("Abandon Selection"), true); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + if (!selections.empty()) { + Selection sel = *selections.begin(); + m_analyser->abandonReAnalysis(sel); + auxSnapNotes(sel); + } + + MainWindowBase::clearSelection(); + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindow::selectionChangedByUser() +{ + if (!m_document) { + // we're exiting, most likely + return; + } + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + cerr << "MainWindow::selectionChangedByUser" << endl; + + m_analyser->showPitchCandidates(m_pendingConstraint.isConstrained()); + + if (!selections.empty()) { + Selection sel = *selections.begin(); + cerr << "MainWindow::selectionChangedByUser: have selection" << endl; + QString error = m_analyser->reAnalyseSelection + (sel, m_pendingConstraint); + if (error != "") { + QMessageBox::critical + (this, tr("Failed to analyse selection"), + tr("Analysis failed

%2

").arg(error)); + } + } + + m_pendingConstraint = Analyser::FrequencyRange(); +} + +void +MainWindow::regionOutlined(QRect r) +{ + cerr << "MainWindow::regionOutlined(" << r.x() << "," << r.y() << "," << r.width() << "," << r.height() << ")" << endl; + + Pane *pane = qobject_cast(sender()); + if (!pane) { + cerr << "MainWindow::regionOutlined: not sent by pane, ignoring" << endl; + return; + } + + if (!m_analyser) { + cerr << "MainWindow::regionOutlined: no analyser, ignoring" << endl; + return; + } + + SpectrogramLayer *spectrogram = qobject_cast + (m_analyser->getLayer(Analyser::Spectrogram)); + if (!spectrogram) { + cerr << "MainWindow::regionOutlined: no spectrogram layer, ignoring" << endl; + return; + } + + sv_frame_t f0 = pane->getFrameForX(r.x()); + sv_frame_t f1 = pane->getFrameForX(r.x() + r.width()); + + double v0 = spectrogram->getFrequencyForY(pane, r.y() + r.height()); + double v1 = spectrogram->getFrequencyForY(pane, r.y()); + + cerr << "MainWindow::regionOutlined: frame " << f0 << " -> " << f1 + << ", frequency " << v0 << " -> " << v1 << endl; + + m_pendingConstraint = Analyser::FrequencyRange(v0, v1); + + Selection sel(f0, f1); + m_viewManager->setSelection(sel); +} + +void +MainWindow::clearPitches() +{ + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + CommandHistory::getInstance()->startCompoundOperation(tr("Clear Pitches"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + m_analyser->deletePitches(*k); + auxSnapNotes(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindow::octaveShift(bool up) +{ + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + CommandHistory::getInstance()->startCompoundOperation + (up ? tr("Choose Higher Octave") : tr("Choose Lower Octave"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + + m_analyser->shiftOctave(*k, up); + auxSnapNotes(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindow::togglePitchCandidates() +{ + CommandHistory::getInstance()->startCompoundOperation(tr("Toggle Pitch Candidates"), true); + + m_analyser->showPitchCandidates(!m_analyser->arePitchCandidatesShown()); + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +void +MainWindow::switchPitchUp() +{ + if (m_analyser->arePitchCandidatesShown()) { + if (m_analyser->haveHigherPitchCandidate()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Choose Higher Pitch Candidate"), true); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + m_analyser->switchPitchCandidate(*k, true); + auxSnapNotes(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + } else { + octaveShift(true); + } +} + +void +MainWindow::switchPitchDown() +{ + if (m_analyser->arePitchCandidatesShown()) { + if (m_analyser->haveLowerPitchCandidate()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Choose Lower Pitch Candidate"), true); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + m_analyser->switchPitchCandidate(*k, false); + auxSnapNotes(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + } else { + octaveShift(false); + } +} + +void +MainWindow::snapNotesToPitches() +{ + cerr << "in snapNotesToPitches" << endl; + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + if (!selections.empty()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Snap Notes to Pitches"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + auxSnapNotes(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } +} + +void +MainWindow::auxSnapNotes(Selection s) +{ + cerr << "in auxSnapNotes" << endl; + FlexiNoteLayer *layer = + qobject_cast(m_analyser->getLayer(Analyser::Notes)); + if (!layer) return; + + layer->snapSelectedNotesToPitchTrack(m_analyser->getPane(), s); +} + +void +MainWindow::splitNote() +{ + FlexiNoteLayer *layer = + qobject_cast(m_analyser->getLayer(Analyser::Notes)); + if (!layer) return; + + layer->splitNotesAt(m_analyser->getPane(), m_viewManager->getPlaybackFrame()); +} + +void +MainWindow::mergeNotes() +{ + FlexiNoteLayer *layer = + qobject_cast(m_analyser->getLayer(Analyser::Notes)); + if (!layer) return; + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + if (!selections.empty()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Merge Notes"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + layer->mergeNotes(m_analyser->getPane(), *k, true); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } +} + +void +MainWindow::deleteNotes() +{ + FlexiNoteLayer *layer = + qobject_cast(m_analyser->getLayer(Analyser::Notes)); + if (!layer) return; + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + if (!selections.empty()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Delete Notes"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + layer->deleteSelectionInclusive(*k); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } +} + + +void +MainWindow::formNoteFromSelection() +{ + Pane *pane = m_analyser->getPane(); + Layer *layer0 = m_analyser->getLayer(Analyser::Notes); + auto model = ModelById::getAs(layer0->getModel()); + FlexiNoteLayer *layer = qobject_cast(layer0); + if (!layer || !model) return; + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + if (!selections.empty()) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Form Note from Selection"), true); + + for (MultiSelection::SelectionList::iterator k = selections.begin(); + k != selections.end(); ++k) { + + // Chop existing events at start and end frames; remember + // the first starting pitch, to use as default for new + // note; delete existing events; create new note; ask + // layer to merge, just in order to adapt the note to the + // existing pitch track if possible. This way we should + // handle all the possible cases of existing notes that + // may or may not overlap the start or end times + + sv_frame_t start = k->getStartFrame(); + sv_frame_t end = k->getEndFrame(); + + EventVector existing = + model->getEventsStartingWithin(start, end - start); + + int defaultPitch = 100; + if (!existing.empty()) { + defaultPitch = int(roundf(existing.begin()->getValue())); + } + + layer->splitNotesAt(pane, start); + layer->splitNotesAt(pane, end); + layer->deleteSelection(*k); + + layer->addNoteOn(start, defaultPitch, 100); + layer->addNoteOff(end, defaultPitch); + + layer->mergeNotes(pane, *k, false); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } +} + +void +MainWindow::playSpeedChanged(int position) +{ + PlaySpeedRangeMapper mapper; + + double percent = m_playSpeed->mappedValue(); + double factor = mapper.getFactorForValue(percent); + + int centre = m_playSpeed->defaultValue(); + + // Percentage is shown to 0dp if >100, to 1dp if <100; factor is + // shown to 3sf + + char pcbuf[30]; + char facbuf[30]; + + if (position == centre) { + contextHelpChanged(tr("Playback speed: Normal")); + } else if (position < centre) { + sprintf(pcbuf, "%.1f", percent); + sprintf(facbuf, "%.3g", 1.0 / factor); + contextHelpChanged(tr("Playback speed: %1% (%2x slower)") + .arg(pcbuf) + .arg(facbuf)); + } else { + sprintf(pcbuf, "%.0f", percent); + sprintf(facbuf, "%.3g", factor); + contextHelpChanged(tr("Playback speed: %1% (%2x faster)") + .arg(pcbuf) + .arg(facbuf)); + } + + m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup + + updateMenuStates(); +} + +void +MainWindow::playSharpenToggled() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("playsharpen", m_playSharpen->isChecked()); + settings.endGroup(); + + playSpeedChanged(m_playSpeed->value()); + // TODO: pitch gain? +} + +void +MainWindow::playMonoToggled() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("playmono", m_playMono->isChecked()); + settings.endGroup(); + + playSpeedChanged(m_playSpeed->value()); + // TODO: pitch gain? +} + +void +MainWindow::speedUpPlayback() +{ + int value = m_playSpeed->value(); + value = value + m_playSpeed->pageStep(); + if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); + m_playSpeed->setValue(value); +} + +void +MainWindow::slowDownPlayback() +{ + int value = m_playSpeed->value(); + value = value - m_playSpeed->pageStep(); + if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); + m_playSpeed->setValue(value); +} + +void +MainWindow::restoreNormalPlayback() +{ + m_playSpeed->setValue(m_playSpeed->defaultValue()); +} + +void +MainWindow::audioGainChanged(float gain) +{ + double db = AudioLevel::multiplier_to_dB(gain); + cerr << "gain = " << gain << " (" << db << " dB)" << endl; + contextHelpChanged(tr("Audio Gain: %1 dB").arg(db)); + if (gain == 0.f) { + m_analyser->setAudible(Analyser::Audio, false); + } else { + m_analyser->setAudible(Analyser::Audio, true); + m_analyser->setGain(Analyser::Audio, gain); + } + updateMenuStates(); +} + +void +MainWindow::pitchGainChanged(float gain) +{ + double db = AudioLevel::multiplier_to_dB(gain); + cerr << "gain = " << gain << " (" << db << " dB)" << endl; + contextHelpChanged(tr("Pitch Gain: %1 dB").arg(db)); + if (gain == 0.f) { + m_analyser->setAudible(Analyser::PitchTrack, false); + } else { + m_analyser->setAudible(Analyser::PitchTrack, true); + m_analyser->setGain(Analyser::PitchTrack, gain); + } + updateMenuStates(); +} + +void +MainWindow::notesGainChanged(float gain) +{ + double db = AudioLevel::multiplier_to_dB(gain); + cerr << "gain = " << gain << " (" << db << " dB)" << endl; + contextHelpChanged(tr("Notes Gain: %1 dB").arg(db)); + if (gain == 0.f) { + m_analyser->setAudible(Analyser::Notes, false); + } else { + m_analyser->setAudible(Analyser::Notes, true); + m_analyser->setGain(Analyser::Notes, gain); + } + updateMenuStates(); +} + +void +MainWindow::audioPanChanged(float pan) +{ + contextHelpChanged(tr("Audio Pan: %1").arg(pan)); + m_analyser->setPan(Analyser::Audio, pan); + updateMenuStates(); +} + +void +MainWindow::pitchPanChanged(float pan) +{ + contextHelpChanged(tr("Pitch Pan: %1").arg(pan)); + m_analyser->setPan(Analyser::PitchTrack, pan); + updateMenuStates(); +} + +void +MainWindow::notesPanChanged(float pan) +{ + contextHelpChanged(tr("Notes Pan: %1").arg(pan)); + m_analyser->setPan(Analyser::Notes, pan); + updateMenuStates(); +} + +void +MainWindow::updateVisibleRangeDisplay(Pane *p) const +{ + if (!getMainModel() || !p) { + return; + } + + bool haveSelection = false; + sv_frame_t startFrame = 0, endFrame = 0; + + if (m_viewManager && m_viewManager->haveInProgressSelection()) { + + bool exclusive = false; + Selection s = m_viewManager->getInProgressSelection(exclusive); + + if (!s.isEmpty()) { + haveSelection = true; + startFrame = s.getStartFrame(); + endFrame = s.getEndFrame(); + } + } + + if (!haveSelection) { + startFrame = p->getFirstVisibleFrame(); + endFrame = p->getLastVisibleFrame(); + } + + RealTime start = RealTime::frame2RealTime + (startFrame, getMainModel()->getSampleRate()); + + RealTime end = RealTime::frame2RealTime + (endFrame, getMainModel()->getSampleRate()); + + RealTime duration = end - start; + + QString startStr, endStr, durationStr; + startStr = start.toText(true).c_str(); + endStr = end.toText(true).c_str(); + durationStr = duration.toText(true).c_str(); + + if (haveSelection) { + m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") + .arg(startStr).arg(endStr).arg(durationStr); + } else { + m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") + .arg(startStr).arg(endStr).arg(durationStr); + } + + getStatusLabel()->setText(m_myStatusMessage); +} + +void +MainWindow::updatePositionStatusDisplays() const +{ + if (!statusBar()->isVisible()) return; + +} + +void +MainWindow::monitoringLevelsChanged(float left, float right) +{ + m_fader->setPeakLeft(left); + m_fader->setPeakRight(right); +} + +void +MainWindow::sampleRateMismatch(sv_samplerate_t , + sv_samplerate_t , + bool ) +{ + updateDescriptionLabel(); +} + +void +MainWindow::audioOverloadPluginDisabled() +{ + QMessageBox::information + (this, tr("Audio processing overload"), + tr("Overloaded

Audio effects plugin auditioning has been disabled due to a processing overload.")); +} + +void +MainWindow::audioTimeStretchMultiChannelDisabled() +{ + static bool shownOnce = false; + if (shownOnce) return; + QMessageBox::information + (this, tr("Audio processing overload"), + tr("Overloaded

Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); + shownOnce = true; +} + +void +MainWindow::layerRemoved(Layer *layer) +{ + MainWindowBase::layerRemoved(layer); +} + +void +MainWindow::layerInAView(Layer *layer, bool inAView) +{ + MainWindowBase::layerInAView(layer, inAView); +} + +void +MainWindow::modelAdded(ModelId model) +{ + MainWindowBase::modelAdded(model); + auto dtvm = ModelById::getAs(model); + if (dtvm) { + cerr << "A dense time-value model (such as an audio file) has been loaded" << endl; + } +} + +void +MainWindow::mainModelChanged(ModelId model) +{ + m_panLayer->setModel(model); + + MainWindowBase::mainModelChanged(model); + + if (m_playTarget || m_audioIO) { + connect(m_fader, SIGNAL(valueChanged(float)), + this, SLOT(mainModelGainChanged(float))); + } +} + +void +MainWindow::mainModelGainChanged(float gain) +{ + if (m_playTarget) { + m_playTarget->setOutputGain(gain); + } else if (m_audioIO) { + m_audioIO->setOutputGain(gain); + } +} + +void +MainWindow::analyseNow() +{ + cerr << "analyseNow called" << endl; + if (!m_analyser) return; + + CommandHistory::getInstance()->startCompoundOperation + (tr("Analyse Audio"), true); + + QString error = m_analyser->analyseExistingFile(); + + CommandHistory::getInstance()->endCompoundOperation(); + + if (error != "") { + QMessageBox::warning + (this, + tr("Failed to analyse audio"), + tr("Analysis failed

%1

").arg(error), + QMessageBox::Ok); + } +} + +void +MainWindow::analyseNewMainModel() +{ + auto model = getMainModel(); + + cerr << "MainWindow::analyseNewMainModel: main model is " << model << endl; + + cerr << "(document is " << m_document << ", it says main model is " << m_document->getMainModel() << ")" << endl; + + if (!model) { + cerr << "no main model!" << endl; + return; + } + + if (!m_paneStack) { + cerr << "no pane stack!" << endl; + return; + } + + int pc = m_paneStack->getPaneCount(); + Pane *pane = 0; + Pane *selectionStrip = 0; + + if (pc < 2) { + pane = m_paneStack->addPane(); + selectionStrip = m_paneStack->addPane(); + m_document->addLayerToView + (selectionStrip, + m_document->createMainModelLayer(LayerFactory::TimeRuler)); + } else { + pane = m_paneStack->getPane(0); + selectionStrip = m_paneStack->getPane(1); + } + + pane->setPlaybackFollow(PlaybackScrollPage); + + if (selectionStrip) { + selectionStrip->setPlaybackFollow(PlaybackScrollPage); + selectionStrip->setFixedHeight(26); + m_paneStack->sizePanesEqually(); + m_viewManager->clearToolModeOverrides(); + m_viewManager->setToolModeFor(selectionStrip, + ViewManager::SelectMode); + } + + if (pane) { + + disconnect(pane, SIGNAL(regionOutlined(QRect)), + pane, SLOT(zoomToRegion(QRect))); + connect(pane, SIGNAL(regionOutlined(QRect)), + this, SLOT(regionOutlined(QRect))); + + QString error = m_analyser->newFileLoaded + (m_document, getMainModelId(), m_paneStack, pane); + if (error != "") { + QMessageBox::warning + (this, + tr("Failed to analyse audio"), + tr("Analysis failed

%1

").arg(error), + QMessageBox::Ok); + } + } + + if (!m_withSpectrogram) { + m_analyser->setVisible(Analyser::Spectrogram, false); + } + + if (!m_withSonification) { + m_analyser->setAudible(Analyser::PitchTrack, false); + m_analyser->setAudible(Analyser::Notes, false); + } + + updateLayerStatuses(); + documentRestored(); +} + +void +MainWindow::modelGenerationFailed(QString transformName, QString message) +{ + if (message != "") { + + QMessageBox::warning + (this, + tr("Failed to generate layer"), + tr("Layer generation failed

Failed to generate derived layer.

The layer transform \"%1\" failed:

%2") + .arg(transformName).arg(message), + QMessageBox::Ok); + } else { + QMessageBox::warning + (this, + tr("Failed to generate layer"), + tr("Layer generation failed

Failed to generate a derived layer.

The layer transform \"%1\" failed.

No error information is available.") + .arg(transformName), + QMessageBox::Ok); + } +} + +void +MainWindow::modelGenerationWarning(QString /* transformName */, QString message) +{ + QMessageBox::warning + (this, tr("Warning"), message, QMessageBox::Ok); +} + +void +MainWindow::modelRegenerationFailed(QString layerName, + QString transformName, + QString message) +{ + if (message != "") { + + QMessageBox::warning + (this, + tr("Failed to regenerate layer"), + tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed:

%3") + .arg(layerName).arg(transformName).arg(message), + QMessageBox::Ok); + } else { + QMessageBox::warning + (this, + tr("Failed to regenerate layer"), + tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed.

No error information is available.") + .arg(layerName).arg(transformName), + QMessageBox::Ok); + } +} + +void +MainWindow::modelRegenerationWarning(QString layerName, + QString /* transformName */, + QString message) +{ + QMessageBox::warning + (this, tr("Warning"), tr("Warning when regenerating layer

When regenerating the derived layer \"%1\" using new data model as input:

%2").arg(layerName).arg(message), QMessageBox::Ok); +} + +void +MainWindow::alignmentFailed(QString message) +{ + QMessageBox::warning + (this, + tr("Failed to calculate alignment"), + tr("Alignment calculation failed

Failed to calculate an audio alignment:

%1") + .arg(message), + QMessageBox::Ok); +} + +void +MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) +{ +// cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl; + m_paneStack->setCurrentPane(pane); + m_rightButtonMenu->popup(position); +} + +void +MainWindow::handleOSCMessage(const OSCMessage &) +{ + cerr << "MainWindow::handleOSCMessage: Not implemented" << endl; +} + +void +MainWindow::mouseEnteredWidget() +{ + QWidget *w = qobject_cast(sender()); + if (!w) return; + + if (w == m_fader) { + contextHelpChanged(tr("Adjust the master playback level")); + } else if (w == m_playSpeed) { + contextHelpChanged(tr("Adjust the master playback speed")); + } else if (w == m_playSharpen && w->isEnabled()) { + contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); + } else if (w == m_playMono && w->isEnabled()) { + contextHelpChanged(tr("Toggle mono mode for playback time scaling")); + } +} + +void +MainWindow::mouseLeftWidget() +{ + contextHelpChanged(""); +} + +void +MainWindow::website() +{ + //!!! todo: URL! + openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/")); +} + +void +MainWindow::help() +{ + //!!! todo: help URL! + openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/wiki/Reference")); +} + +void +MainWindow::about() +{ + bool debug = false; + QString version = "(unknown version)"; + +#ifdef BUILD_DEBUG + debug = true; +#endif + version = tr("Release %1").arg(TONY_VERSION); + + QString aboutText; + + aboutText += tr("

About Tony

"); + aboutText += tr("

Tony is a program for interactive note and pitch analysis and annotation.

"); + aboutText += tr("

%1 : %2 configuration

") + .arg(version) + .arg(debug ? tr("Debug") : tr("Release")); + aboutText += tr("

Using Qt framework version %1.

") + .arg(QT_VERSION_STR); + + aboutText += + "

Copyright © 2005–2015 Chris Cannam, Queen Mary University of London, and the Tony project authors: Matthias Mauch, George Fazekas, Justin Salamon, and Rachel Bittner.

" + "

pYIN analysis plugin written by Matthias Mauch.

" + "

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.

"; + + QMessageBox::about(this, tr("About %1").arg(QApplication::applicationName()), aboutText); +} + +void +MainWindow::keyReference() +{ + m_keyReference->show(); +} + +void +MainWindow::newerVersionAvailable(QString version) +{ + //!!! nicer URL would be nicer + QSettings settings; + settings.beginGroup("NewerVersionWarning"); + QString tag = QString("version-%1-available-show").arg(version); + if (settings.value(tag, true).toBool()) { + QString title(tr("Newer version available")); + QString text(tr("

Newer version available

You are using version %1 of Tony, but version %2 is now available.

Please see the Tony website for more information.

").arg(TONY_VERSION).arg(version)); + QMessageBox::information(this, title, text); + settings.setValue(tag, false); + } + settings.endGroup(); +} + +void +MainWindow::ffwd() +{ + if (!getMainModel()) return; + + sv_frame_t frame = m_viewManager->getPlaybackFrame(); + ++frame; + + sv_samplerate_t sr = getMainModel()->getSampleRate(); + + // The step is supposed to scale and be as wide as a step of + // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 + + ZoomLevel zoom = m_viewManager->getGlobalZoom(); + double framesPerPixel = 1.0; + if (zoom.zone == ZoomLevel::FramesPerPixel) { + framesPerPixel = zoom.level; + } else { + framesPerPixel = 1.0 / zoom.level; + } + double defaultFramesPerPixel = (720 * 44100) / sr; + double scaler = framesPerPixel / defaultFramesPerPixel; + RealTime step = m_defaultFfwdRwdStep * scaler; + + frame = RealTime::realTime2Frame + (RealTime::frame2RealTime(frame, sr) + step, sr); + + if (frame > getMainModel()->getEndFrame()) { + frame = getMainModel()->getEndFrame(); + } + + if (frame < 0) frame = 0; + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(frame); + } + + m_viewManager->setPlaybackFrame(frame); + + if (frame == getMainModel()->getEndFrame() && + m_playSource && + m_playSource->isPlaying() && + !m_viewManager->getPlayLoopMode()) { + stop(); + } +} + +void +MainWindow::rewind() +{ + if (!getMainModel()) return; + + sv_frame_t frame = m_viewManager->getPlaybackFrame(); + if (frame > 0) --frame; + + sv_samplerate_t sr = getMainModel()->getSampleRate(); + + // The step is supposed to scale and be as wide as a step of + // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 + + ZoomLevel zoom = m_viewManager->getGlobalZoom(); + double framesPerPixel = 1.0; + if (zoom.zone == ZoomLevel::FramesPerPixel) { + framesPerPixel = zoom.level; + } else { + framesPerPixel = 1.0 / zoom.level; + } + double defaultFramesPerPixel = (720 * 44100) / sr; + double scaler = framesPerPixel / defaultFramesPerPixel; + RealTime step = m_defaultFfwdRwdStep * scaler; + + frame = RealTime::realTime2Frame + (RealTime::frame2RealTime(frame, sr) - step, sr); + + if (frame < getMainModel()->getStartFrame()) { + frame = getMainModel()->getStartFrame(); + } + + if (frame < 0) frame = 0; + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(frame); + } + + m_viewManager->setPlaybackFrame(frame); +} diff -r 47f96711069f -r f52766aa747b main/MainWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/MainWindow.h Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,254 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2012 Chris Cannam and QMUL. + + 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. +*/ + +#ifndef _MAIN_WINDOW_H_ +#define _MAIN_WINDOW_H_ + +#include "framework/MainWindowBase.h" +#include "Analyser.h" + +class VersionTester; +class ActivityLog; +class LevelPanToolButton; + +class MainWindow : public MainWindowBase +{ + Q_OBJECT + +public: + MainWindow(SoundOptions options, bool withSonification = true, bool withSpectrogram = true); + virtual ~MainWindow(); + +signals: + void canExportPitchTrack(bool); + void canExportNotes(bool); + void canSnapNotes(bool); + void canPlayWaveform(bool); + void canPlayPitch(bool); + void canPlayNotes(bool); + +public slots: + virtual bool commitData(bool mayAskUser); // on session shutdown + +protected slots: + virtual void openFile(); + virtual void openLocation(); + virtual void openRecentFile(); + virtual void saveSession(); + virtual void saveSessionInAudioPath(); + virtual void saveSessionAs(); + virtual void exportPitchLayer(); + virtual void exportNoteLayer(); + virtual void importPitchLayer(); + virtual void browseRecordedAudio(); + virtual void newSession(); + virtual void closeSession(); + + virtual void toolNavigateSelected(); + virtual void toolEditSelected(); + virtual void toolFreeEditSelected(); + + virtual void clearPitches(); + virtual void togglePitchCandidates(); + virtual void switchPitchUp(); + virtual void switchPitchDown(); + + virtual void snapNotesToPitches(); + virtual void splitNote(); + virtual void mergeNotes(); + virtual void deleteNotes(); + virtual void formNoteFromSelection(); + + virtual void showAudioToggled(); + virtual void showSpectToggled(); + virtual void showPitchToggled(); + virtual void showNotesToggled(); + + virtual void playAudioToggled(); + virtual void playPitchToggled(); + virtual void playNotesToggled(); + + virtual void editDisplayExtents(); + + virtual void analyseNow(); + virtual void resetAnalyseOptions(); + virtual void autoAnalysisToggled(); + virtual void precisionAnalysisToggled(); + virtual void lowampAnalysisToggled(); + virtual void onsetAnalysisToggled(); + virtual void pruneAnalysisToggled(); + virtual void updateAnalyseStates(); + + virtual void doubleClickSelectInvoked(sv_frame_t); + virtual void abandonSelection(); + + virtual void paneAdded(Pane *); + virtual void paneHidden(Pane *); + virtual void paneAboutToBeDeleted(Pane *); + + virtual void paneDropAccepted(Pane *, QStringList); + virtual void paneDropAccepted(Pane *, QString); + + virtual void playSpeedChanged(int); + virtual void playSharpenToggled(); + virtual void playMonoToggled(); + + virtual void speedUpPlayback(); + virtual void slowDownPlayback(); + virtual void restoreNormalPlayback(); + + virtual void monitoringLevelsChanged(float, float); + + virtual void audioGainChanged(float); + virtual void pitchGainChanged(float); + virtual void notesGainChanged(float); + + virtual void audioPanChanged(float); + virtual void pitchPanChanged(float); + virtual void notesPanChanged(float); + + virtual void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool); + virtual void audioOverloadPluginDisabled(); + virtual void audioTimeStretchMultiChannelDisabled(); + + virtual void documentModified(); + virtual void documentRestored(); + virtual void documentReplaced(); + + virtual void updateMenuStates(); + virtual void updateDescriptionLabel(); + virtual void updateLayerStatuses(); + + virtual void layerRemoved(Layer *); + virtual void layerInAView(Layer *, bool); + + virtual void mainModelChanged(ModelId); + virtual void mainModelGainChanged(float); + virtual void modelAdded(ModelId); + + virtual void modelGenerationFailed(QString, QString); + virtual void modelGenerationWarning(QString, QString); + virtual void modelRegenerationFailed(QString, QString, QString); + virtual void modelRegenerationWarning(QString, QString, QString); + virtual void alignmentFailed(QString); + + virtual void rightButtonMenuRequested(Pane *, QPoint point); + + virtual void setupRecentFilesMenu(); + + virtual void handleOSCMessage(const OSCMessage &); + + virtual void mouseEnteredWidget(); + virtual void mouseLeftWidget(); + + virtual void website(); + virtual void help(); + virtual void about(); + virtual void keyReference(); + + virtual void newerVersionAvailable(QString); + + virtual void selectionChangedByUser(); + virtual void regionOutlined(QRect); + + virtual void analyseNewMainModel(); + + void moveOneNoteRight(); + void moveOneNoteLeft(); + void selectOneNoteRight(); + void selectOneNoteLeft(); + + void ffwd(); + void rewind(); + +protected: + Analyser *m_analyser; + + Overview *m_overview; + Fader *m_fader; + AudioDial *m_playSpeed; + QPushButton *m_playSharpen; + QPushButton *m_playMono; + WaveformLayer *m_panLayer; + + bool m_mainMenusCreated; + QMenu *m_playbackMenu; + QMenu *m_recentFilesMenu; + QMenu *m_rightButtonMenu; + QMenu *m_rightButtonPlaybackMenu; + + QAction *m_deleteSelectedAction; + QAction *m_ffwdAction; + QAction *m_rwdAction; + QAction *m_editSelectAction; + QAction *m_showCandidatesAction; + QAction *m_toggleIntelligenceAction; + bool m_intelligentActionOn; // GF: !!! temporary + + QAction *m_autoAnalyse; + QAction *m_precise; + QAction *m_lowamp; + QAction *m_onset; + QAction *m_prune; + + QAction *m_showAudio; + QAction *m_showSpect; + QAction *m_showPitch; + QAction *m_showNotes; + QAction *m_playAudio; + QAction *m_playPitch; + QAction *m_playNotes; + LevelPanToolButton *m_audioLPW; + LevelPanToolButton *m_pitchLPW; + LevelPanToolButton *m_notesLPW; + + ActivityLog *m_activityLog; + KeyReference *m_keyReference; + VersionTester *m_versionTester; + + sv_frame_t m_selectionAnchor; + + bool m_withSonification; + bool m_withSpectrogram; + + Analyser::FrequencyRange m_pendingConstraint; + + QString exportToSVL(QString path, Layer *layer); + FileOpenStatus importPitchLayer(FileSource source); + + virtual void setupMenus(); + virtual void setupFileMenu(); + virtual void setupEditMenu(); + virtual void setupViewMenu(); + virtual void setupAnalysisMenu(); + virtual void setupHelpMenu(); + virtual void setupToolbars(); + + virtual void octaveShift(bool up); + + virtual void auxSnapNotes(Selection s); + + virtual void closeEvent(QCloseEvent *e); + bool checkSaveModified(); + bool waitForInitialAnalysis(); + + virtual void updateVisibleRangeDisplay(Pane *p) const; + virtual void updatePositionStatusDisplays() const; + + void moveByOneNote(bool right, bool doSelect); +}; + + +#endif diff -r 47f96711069f -r f52766aa747b main/NetworkPermissionTester.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/NetworkPermissionTester.cpp Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,88 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + 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 "NetworkPermissionTester.h" + +#include "../version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool +NetworkPermissionTester::havePermission() +{ + QSettings settings; + settings.beginGroup("Preferences"); + + QString tag = QString("network-permission-%1").arg(TONY_VERSION); + + bool permish = false; + + if (settings.contains(tag)) { + permish = settings.value(tag, false).toBool(); + } else { + + QDialog d; + d.setWindowTitle(QCoreApplication::translate + ("NetworkPermissionTester", + "Tony: a tool for melody annotation")); + + QGridLayout *layout = new QGridLayout; + d.setLayout(layout); + + QLabel *label = new QLabel; + label->setWordWrap(true); + label->setText + (QCoreApplication::translate + ("NetworkPermissionTester", + "

Tony: a tool for melody annotation

" + "

Tony is a program for computer-assisted pitch and note annotation of unaccompanied melody.

" + "

Developed in the Centre for Digital Music at Queen Mary, University of London, Tony is provided free as open source software under the GNU General Public License.

" + "


" + "

Before we go on...

" + "

Tony needs to make occasional network requests to our servers.

" + "

This is to:

" + "
  • tell you when updates are available.
" + "

No personal information will be sent, no tracking is carried out, and all requests happen in the background without interrupting your work.

" + "

We recommend that you allow this. But if you do not wish to do so, please un-check the box below.

")); + layout->addWidget(label, 0, 0); + + QCheckBox *cb = new QCheckBox(QCoreApplication::translate("NetworkPermissionTester", "Allow this")); + cb->setChecked(true); + layout->addWidget(cb, 1, 0); + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); + QObject::connect(bb, SIGNAL(accepted()), &d, SLOT(accept())); + layout->addWidget(bb, 2, 0); + + d.exec(); + + bool permish = cb->isChecked(); + settings.setValue(tag, permish); + } + + settings.endGroup(); + + return permish; +} + + + diff -r 47f96711069f -r f52766aa747b main/NetworkPermissionTester.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/NetworkPermissionTester.h Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,27 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + 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. +*/ + +#ifndef NETWORK_PERMISSION_TESTER_H +#define NETWORK_PERMISSION_TESTER_H + +class NetworkPermissionTester +{ +public: + NetworkPermissionTester() { } + bool havePermission(); +}; + +#endif + + diff -r 47f96711069f -r f52766aa747b main/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/main.cpp Wed Aug 14 11:57:06 2019 +0100 @@ -0,0 +1,406 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Tony + An intonation analysis and annotation tool + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2012 Chris Cannam and QMUL. + + 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 "MainWindow.h" + +#include "system/System.h" +#include "system/Init.h" +#include "base/TempDirectory.h" +#include "base/PropertyContainer.h" +#include "base/Preferences.h" +#include "widgets/TipDialog.h" +#include "widgets/InteractiveFileFinder.h" +#include "transform/TransformFactory.h" +#include "svcore/plugin/PluginScan.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static QMutex cleanupMutex; +static bool cleanedUp = false; + +static void +signalHandler(int /* signal */) +{ + // Avoid this happening more than once across threads + + cerr << "signalHandler: cleaning up and exiting" << endl; + cleanupMutex.lock(); + if (!cleanedUp) { + TempDirectory::getInstance()->cleanup(); + cleanedUp = true; + } + cleanupMutex.unlock(); + exit(0); +} + +class TonyApplication : public QApplication +{ +public: + TonyApplication(int &argc, char **argv) : + QApplication(argc, argv), + m_mainWindow(0), + m_readyForFiles(false) + { + // tidier without, I reckon + setAttribute(Qt::AA_DontShowIconsInMenus); + } + virtual ~TonyApplication() { + } + + void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } + void releaseMainWindow() { m_mainWindow = 0; } + + virtual void commitData(QSessionManager &manager) { + if (!m_mainWindow) return; + bool mayAskUser = manager.allowsInteraction(); + bool success = m_mainWindow->commitData(mayAskUser); + manager.release(); + if (!success) manager.cancel(); + } + + void readyForFiles() { + m_readyForFiles = true; + } + + void handleFilepathArgument(QString path, QSplashScreen *splash); + + void handleQueuedPaths(QSplashScreen *splash) { + foreach (QString f, m_filepathQueue) { + handleFilepathArgument(f, splash); + } + } + +protected: + MainWindow *m_mainWindow; + + bool m_readyForFiles; + QStringList m_filepathQueue; + + virtual bool event(QEvent *event) { + + if (event->type() == QEvent::FileOpen) { + QString path = static_cast(event)->file(); + if (m_readyForFiles) { + handleFilepathArgument(path, NULL); + } else { + m_filepathQueue.append(path); + } + return true; + } else { + return QApplication::event(event); + } + } +}; + +static QString +getEnvQStr(QString variable) +{ +#ifdef Q_OS_WIN32 + std::wstring wvar = variable.toStdWString(); + wchar_t *value = _wgetenv(wvar.c_str()); + if (!value) return QString(); + else return QString::fromStdWString(std::wstring(value)); +#else + std::string var = variable.toStdString(); + return QString::fromUtf8(qgetenv(var.c_str())); +#endif +} + +static void +putEnvQStr(QString assignment) +{ +#ifdef Q_OS_WIN32 + std::wstring wassignment = assignment.toStdWString(); + _wputenv(_wcsdup(wassignment.c_str())); +#else + putenv(strdup(assignment.toUtf8().data())); +#endif +} + +static void +setupTonyVampPath() +{ + QString tonyVampPath = getEnvQStr("TONY_VAMP_PATH"); + +#ifdef Q_OS_WIN32 + QChar sep(';'); +#else + QChar sep(':'); +#endif + + if (tonyVampPath == "") { + tonyVampPath = QApplication::applicationDirPath(); + +#ifdef Q_OS_WIN32 + QString programFiles = getEnvQStr("ProgramFiles"); + if (programFiles == "") programFiles = "C:\\Program Files"; + QString defaultTonyPath(programFiles + "\\Tony"); + tonyVampPath = tonyVampPath + sep + defaultTonyPath; +#else +#ifdef Q_OS_MAC + tonyVampPath = tonyVampPath + "/../Resources:" + tonyVampPath; +#else + QString defaultTonyPath("/usr/local/lib/tony:/usr/lib/tony"); + tonyVampPath = tonyVampPath + sep + defaultTonyPath; +#endif +#endif + } + + std::vector vampPathList = + Vamp::PluginHostAdapter::getPluginPath(); + + for (auto p: vampPathList) { + tonyVampPath = tonyVampPath + sep + QString::fromUtf8(p.c_str()); + } + + SVCERR << "Setting VAMP_PATH to " << tonyVampPath + << " for Tony plugins" << endl; + + QString env = "VAMP_PATH=" + tonyVampPath; + + // Windows lacks setenv, must use putenv (different arg convention) + putEnvQStr(env); +} + +int +main(int argc, char **argv) +{ + svSystemSpecificInitialisation(); + +#ifdef Q_OS_MAC + if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { + // Fix for OS/X 10.9 font problem + QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); + } +#endif + + TonyApplication application(argc, argv); + + QApplication::setOrganizationName("sonic-visualiser"); + QApplication::setOrganizationDomain("sonicvisualiser.org"); + QApplication::setApplicationName("Tony"); + + setupTonyVampPath(); + + QStringList args = application.arguments(); + + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + +#ifndef Q_OS_WIN32 + signal(SIGHUP, signalHandler); + signal(SIGQUIT, signalHandler); +#endif + + bool audioOutput = true; + bool sonification = true; + bool spectrogram = true; + + if (args.contains("--help") || args.contains("-h") || args.contains("-?")) { + std::cerr << QApplication::tr( + "\nTony is a program for interactive note and pitch analysis and annotation.\n\nUsage:\n\n %1 [--no-audio] [--no-sonification] [--no-spectrogram] [ ...]\n\n --no-audio: Do not attempt to open an audio output device\n --no-sonification: Disable sonification of pitch tracks and notes and hide their toggles.\n --no-spectrogram: Disable spectrogram.\n : One or more Tony (.ton) and audio files may be provided.").arg(argv[0]).toStdString() << std::endl; + exit(2); + } + + if (args.contains("--no-audio")) audioOutput = false; + + if (args.contains("--no-sonification")) sonification = false; + + if (args.contains("--no-spectrogram")) spectrogram = false; + + InteractiveFileFinder::getInstance()->setApplicationSessionExtension("ton"); + + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + + QSplashScreen *splash = 0; + // If we had a splash screen, we would show it here + + QIcon icon; + int sizes[] = { 16, 22, 24, 32, 48, 64, 128 }; + for (size_t i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) { + icon.addFile(QString(":icons/tony-%1x%2.png").arg(sizes[i]).arg(sizes[i])); + } + QApplication::setWindowIcon(icon); + + QString language = QLocale::system().name(); + + QTranslator qtTranslator; + QString qtTrName = QString("qt_%1").arg(language); + std::cerr << "Loading " << qtTrName.toStdString() << "..." << std::endl; + bool success = false; + if (!(success = qtTranslator.load(qtTrName))) { + QString qtDir = getenv("QTDIR"); + if (qtDir != "") { + success = qtTranslator.load + (qtTrName, QDir(qtDir).filePath("translations")); + } + } + if (!success) { + std::cerr << "Failed to load Qt translation for locale" << std::endl; + } + application.installTranslator(&qtTranslator); + + StoreStartupLocale(); + + // Permit size_t and PropertyName to be used as args in queued signal calls + qRegisterMetaType("size_t"); + qRegisterMetaType("PropertyContainer::PropertyName"); + + MainWindow::SoundOptions options = MainWindow::WithEverything; + if (!audioOutput) options = 0; + + MainWindow *gui = new MainWindow(options, sonification, spectrogram); + application.setMainWindow(gui); + if (splash) { + QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide())); + } + + QScreen *screen = QApplication::primaryScreen(); + QRect available = screen->availableGeometry(); + + int width = (available.width() * 2) / 3; + int height = available.height() / 2; + if (height < 450) height = (available.height() * 2) / 3; + if (width > height * 2) width = height * 2; + + QSettings settings; + settings.beginGroup("MainWindow"); + QSize size = settings.value("size", QSize(width, height)).toSize(); + gui->resizeConstrained(size); + if (settings.contains("position")) { + QRect prevrect(settings.value("position").toPoint(), size); + if (!(available & prevrect).isEmpty()) { + gui->move(prevrect.topLeft()); + } + } + settings.endGroup(); + + gui->show(); + + application.readyForFiles(); + + for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { + + if (i == args.begin()) continue; + if (i->startsWith('-')) continue; + + QString path = *i; + + application.handleFilepathArgument(path, splash); + } + + application.handleQueuedPaths(splash); + + if (splash) splash->finish(gui); + delete splash; + + int rv = application.exec(); + + gui->hide(); + + cleanupMutex.lock(); + + if (!cleanedUp) { + TransformFactory::deleteInstance(); + TempDirectory::getInstance()->cleanup(); + cleanedUp = true; + } + + application.releaseMainWindow(); + + delete gui; + + cleanupMutex.unlock(); + + return rv; +} + +/** Application-global handler for filepaths passed in, e.g. as + * command-line arguments or apple events */ + +void TonyApplication::handleFilepathArgument(QString path, + QSplashScreen *splash) +{ + static bool haveSession = false; + static bool haveMainModel = false; + static bool havePriorCommandLineModel = false; + + if (!m_mainWindow) return; + + MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; + +#ifdef Q_OS_WIN32 + path.replace("\\", "/"); +#endif + + if (path.endsWith("ton")) { + if (!haveSession) { + status = m_mainWindow->openSessionPath(path); + if (status == MainWindow::FileOpenSucceeded) { + haveSession = true; + haveMainModel = true; + } + } else { + std::cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << std::endl; + status = MainWindow::FileOpenSucceeded; + } + } + if (status != MainWindow::FileOpenSucceeded) { + if (!haveMainModel) { + status = m_mainWindow->openPath(path, MainWindow::ReplaceSession); + if (status == MainWindow::FileOpenSucceeded) { + haveMainModel = true; + } + } else { + if (haveSession && !havePriorCommandLineModel) { + status = m_mainWindow->openPath(path, MainWindow::AskUser); + if (status == MainWindow::FileOpenSucceeded) { + havePriorCommandLineModel = true; + } + } else { + status = m_mainWindow->openPath(path, MainWindow::CreateAdditionalModel); + } + } + } + if (status == MainWindow::FileOpenFailed) { + if (splash) splash->hide(); + QMessageBox::critical + (m_mainWindow, QMessageBox::tr("Failed to open file"), + QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path)); + } else if (status == MainWindow::FileOpenWrongMode) { + if (splash) splash->hide(); + QMessageBox::critical + (m_mainWindow, QMessageBox::tr("Failed to open file"), + QMessageBox::tr("Audio required

Please load at least one audio file before importing annotation data")); + } +} + diff -r 47f96711069f -r f52766aa747b src/Analyser.cpp --- a/src/Analyser.cpp Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1063 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - 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 "Analyser.h" - -#include "transform/TransformFactory.h" -#include "transform/ModelTransformer.h" -#include "transform/FeatureExtractionModelTransformer.h" -#include "framework/Document.h" -#include "data/model/WaveFileModel.h" -#include "view/Pane.h" -#include "view/PaneStack.h" -#include "layer/Layer.h" -#include "layer/TimeValueLayer.h" -#include "layer/NoteLayer.h" -#include "layer/FlexiNoteLayer.h" -#include "layer/WaveformLayer.h" -#include "layer/ColourDatabase.h" -#include "layer/ColourMapper.h" -#include "layer/LayerFactory.h" -#include "layer/SpectrogramLayer.h" -#include "layer/Colour3DPlotLayer.h" -#include "layer/ShowLayerCommand.h" - -#include -#include - -using std::vector; - -Analyser::Analyser() : - m_document(0), - m_paneStack(0), - m_pane(0), - m_currentCandidate(-1), - m_candidatesVisible(false), - m_currentAsyncHandle(0) -{ - QSettings settings; - settings.beginGroup("LayerDefaults"); - settings.setValue - ("timevalues", - QString("") - .arg(int(TimeValueLayer::AutoAlignScale)) - .arg(int(TimeValueLayer::PlotPoints)) - .arg(27.5f).arg(880.f)); // temporary values: better get the real extents of the data from the model - settings.setValue - ("flexinotes", - QString("") - .arg(int(FlexiNoteLayer::AutoAlignScale))); - settings.endGroup(); -} - -Analyser::~Analyser() -{ -} - -QString -Analyser::newFileLoaded(Document *doc, ModelId model, - PaneStack *paneStack, Pane *pane) -{ - m_document = doc; - m_fileModel = model; - m_paneStack = paneStack; - m_pane = pane; - - if (!ModelById::isa(m_fileModel)) { - return "Internal error: Analyser::newFileLoaded() called with no model, or a non-WaveFileModel"; - } - - connect(doc, SIGNAL(layerAboutToBeDeleted(Layer *)), - this, SLOT(layerAboutToBeDeleted(Layer *))); - - QSettings settings; - settings.beginGroup("Analyser"); - bool autoAnalyse = settings.value("auto-analysis", true).toBool(); - settings.endGroup(); - - return doAllAnalyses(autoAnalyse); -} - -QString -Analyser::analyseExistingFile() -{ - if (!m_document) return "Internal error: Analyser::analyseExistingFile() called with no document present"; - - if (!m_pane) return "Internal error: Analyser::analyseExistingFile() called with no pane present"; - - if (m_fileModel.isNone()) return "Internal error: Analyser::analyseExistingFile() called with no model present"; - - if (m_layers[PitchTrack]) { - m_document->removeLayerFromView(m_pane, m_layers[PitchTrack]); - m_layers[PitchTrack] = 0; - } - if (m_layers[Notes]) { - m_document->removeLayerFromView(m_pane, m_layers[Notes]); - m_layers[Notes] = 0; - } - - return doAllAnalyses(true); -} - -QString -Analyser::doAllAnalyses(bool withPitchTrack) -{ - m_reAnalysingSelection = Selection(); - m_reAnalysisCandidates.clear(); - m_currentCandidate = -1; - m_candidatesVisible = false; - - // Note that we need at least one main-model layer (time ruler, - // waveform or what have you). It could be hidden if we don't want - // to see it but it must exist. - - QString warning, error; - - cerr << "Analyser::newFileLoaded: about to check visualisations etc" << endl; - - // This isn't fatal -- we can proceed without - // visualisations. Other failures are fatal though. - warning = addVisualisations(); - - error = addWaveform(); - if (error != "") return error; - - if (withPitchTrack) { - error = addAnalyses(); - if (error != "") return error; - } - - loadState(Audio); - loadState(PitchTrack); - loadState(Notes); - loadState(Spectrogram); - - stackLayers(); - - emit layersChanged(); - - return warning; -} - -void -Analyser::fileClosed() -{ - cerr << "Analyser::fileClosed" << endl; - m_layers.clear(); - m_reAnalysisCandidates.clear(); - m_currentCandidate = -1; - m_reAnalysingSelection = Selection(); -} - -bool -Analyser::getDisplayFrequencyExtents(double &min, double &max) -{ - if (!m_layers[Spectrogram]) return false; - return m_layers[Spectrogram]->getDisplayExtents(min, max); -} - -bool -Analyser::setDisplayFrequencyExtents(double min, double max) -{ - if (!m_layers[Spectrogram]) return false; - m_layers[Spectrogram]->setDisplayExtents(min, max); - return true; -} - -int -Analyser::getInitialAnalysisCompletion() -{ - int completion = 0; - - if (m_layers[PitchTrack]) { - completion = m_layers[PitchTrack]->getCompletion(m_pane); - } - - if (m_layers[Notes]) { - int c = m_layers[Notes]->getCompletion(m_pane); - if (c < completion) completion = c; - } - - return completion; -} - -void -Analyser::layerCompletionChanged(ModelId) -{ - if (getInitialAnalysisCompletion() < 100) { - return; - } - - emit initialAnalysisCompleted(); - - if (!m_layers[Audio]) { - return; - } - - // Extend pitch-track and note layers so as to nominally end at - // the same time as the audio. This affects any time-filling done - // on export etc. - - auto audioModel = ModelById::get(m_layers[Audio]->getModel()); - sv_frame_t endFrame = audioModel->getEndFrame(); - - if (m_layers[PitchTrack]) { - auto model = ModelById::getAs - (m_layers[PitchTrack]->getModel()); - if (model) { - model->extendEndFrame(endFrame); - } - } - - if (m_layers[Notes]) { - auto model = ModelById::getAs - (m_layers[Notes]->getModel()); - if (model) { - model->extendEndFrame(endFrame); - } - } -} - -QString -Analyser::addVisualisations() -{ - if (m_fileModel.isNone()) return "Internal error: Analyser::addVisualisations() called with no model present"; - - // A spectrogram, off by default. Must go at the back because it's - // opaque - -/* This is roughly what we'd do for a constant-Q spectrogram, but it - currently has issues with y-axis alignment - - TransformFactory *tf = TransformFactory::getInstance(); - - QString name = "Constant-Q"; - QString base = "vamp:cqvamp:cqvamp:"; - QString out = "constantq"; - - QString notFound = tr("Transform \"%1\" not found, spectrogram will not be enabled.

Is the %2 Vamp plugin correctly installed?"); - if (!tf->haveTransform(base + out)) { - return notFound.arg(base + out).arg(name); - } - - Transform transform = tf->getDefaultTransformFor - (base + out, m_fileModel->getSampleRate()); - transform.setParameter("bpo", 36); - - Colour3DPlotLayer *spectrogram = qobject_cast - (m_document->createDerivedLayer(transform, m_fileModel)); - - if (!spectrogram) return tr("Transform \"%1\" did not run correctly (no layer or wrong layer type returned)").arg(base + out); -*/ - - // As with all the visualisation layers, if we already have one in - // the pane we do not create another, just record its - // existence. (We create a new one when loading a new audio file, - // but just note the existing one when loading a complete session.) - - for (int i = 0; i < m_pane->getLayerCount(); ++i) { - SpectrogramLayer *existing = qobject_cast - (m_pane->getLayer(i)); - if (existing) { - cerr << "recording existing spectrogram layer" << endl; - m_layers[Spectrogram] = existing; - return ""; - } - } - - SpectrogramLayer *spectrogram = qobject_cast - (m_document->createMainModelLayer(LayerFactory::MelodicRangeSpectrogram)); - - spectrogram->setColourMap((int)ColourMapper::BlackOnWhite); - spectrogram->setNormalization(ColumnNormalization::Hybrid); - // This magical-looking scale factor happens to get us an - // identical display to Tony v1.0 - spectrogram->setGain(100.f / 4096.f); - m_document->addLayerToView(m_pane, spectrogram); - spectrogram->setLayerDormant(m_pane, true); - - m_layers[Spectrogram] = spectrogram; - - return ""; -} - -QString -Analyser::addWaveform() -{ - // Our waveform layer is just a shadow, light grey and taking up - // little space at the bottom. - - // As with the spectrogram above, if one exists already we just - // use it - for (int i = 0; i < m_pane->getLayerCount(); ++i) { - WaveformLayer *existing = qobject_cast - (m_pane->getLayer(i)); - if (existing) { - cerr << "recording existing waveform layer" << endl; - m_layers[Audio] = existing; - return ""; - } - } - - WaveformLayer *waveform = qobject_cast - (m_document->createMainModelLayer(LayerFactory::Waveform)); - - waveform->setMiddleLineHeight(0.9); - waveform->setShowMeans(false); // too small & pale for this - waveform->setBaseColour - (ColourDatabase::getInstance()->getColourIndex(tr("Grey"))); - auto params = waveform->getPlayParameters(); - if (params) { - params->setPlayPan(-1); - params->setPlayGain(1); - } - - m_document->addLayerToView(m_pane, waveform); - - m_layers[Audio] = waveform; - return ""; -} - -QString -Analyser::addAnalyses() -{ - auto waveFileModel = ModelById::getAs(m_fileModel); - if (!waveFileModel) { - return "Internal error: Analyser::addAnalyses() called with no model present"; - } - - // As with the spectrogram above, if these layers exist we use - // them - TimeValueLayer *existingPitch = 0; - FlexiNoteLayer *existingNotes = 0; - for (int i = 0; i < m_pane->getLayerCount(); ++i) { - if (!existingPitch) { - existingPitch = qobject_cast(m_pane->getLayer(i)); - } - if (!existingNotes) { - existingNotes = qobject_cast(m_pane->getLayer(i)); - } - } - if (existingPitch && existingNotes) { - cerr << "recording existing pitch and notes layers" << endl; - m_layers[PitchTrack] = existingPitch; - m_layers[Notes] = existingNotes; - return ""; - } else { - if (existingPitch) { - m_document->removeLayerFromView(m_pane, existingPitch); - m_layers[PitchTrack] = 0; - } - if (existingNotes) { - m_document->removeLayerFromView(m_pane, existingNotes); - m_layers[Notes] = 0; - } - } - - TransformFactory *tf = TransformFactory::getInstance(); - - QString plugname = "pYIN"; - QString base = "vamp:pyin:pyin:"; - QString f0out = "smoothedpitchtrack"; - QString noteout = "notes"; - - Transforms transforms; - -/*!!! we could have more than one pitch track... - QString cx = "vamp:cepstral-pitchtracker:cepstral-pitchtracker:f0"; - if (tf->haveTransform(cx)) { - Transform tx = tf->getDefaultTransformFor(cx); - TimeValueLayer *lx = qobject_cast - (m_document->createDerivedLayer(tx, m_fileModel)); - lx->setVerticalScale(TimeValueLayer::AutoAlignScale); - lx->setBaseColour(ColourDatabase::getInstance()->getColourIndex(tr("Bright Red"))); - m_document->addLayerToView(m_pane, lx); - } -*/ - - QString notFound = tr("Transform \"%1\" not found. Unable to analyse audio file.

Is the %2 Vamp plugin correctly installed?"); - if (!tf->haveTransform(base + f0out)) { - return notFound.arg(base + f0out).arg(plugname); - } - if (!tf->haveTransform(base + noteout)) { - return notFound.arg(base + noteout).arg(plugname); - } - - QSettings settings; - settings.beginGroup("Analyser"); - bool precise = settings.value("precision-analysis", false).toBool(); - bool lowamp = settings.value("lowamp-analysis", false).toBool(); - bool onset = settings.value("onset-analysis", true).toBool(); // should these be the same as in MainWindow.cpp? - bool prune = settings.value("prune-analysis", true).toBool(); - settings.endGroup(); - - Transform t = tf->getDefaultTransformFor - (base + f0out, waveFileModel->getSampleRate()); - t.setStepSize(256); - t.setBlockSize(2048); - - if (precise) { - cerr << "setting parameters for precise mode" << endl; - t.setParameter("precisetime", 1); - } else { - cerr << "setting parameters for vague mode" << endl; - t.setParameter("precisetime", 0); - } - - if (lowamp) { - cerr << "setting parameters for lowamp suppression" << endl; - t.setParameter("lowampsuppression", 0.2f); - } else { - cerr << "setting parameters for no lowamp suppression" << endl; - t.setParameter("lowampsuppression", 0.0f); - } - - if (onset) { - cerr << "setting parameters for increased onset sensitivity" << endl; - t.setParameter("onsetsensitivity", 0.7f); - } else { - cerr << "setting parameters for non-increased onset sensitivity" << endl; - t.setParameter("onsetsensitivity", 0.0f); - } - - if (prune) { - cerr << "setting parameters for duration pruning" << endl; - t.setParameter("prunethresh", 0.1f); - } else { - cerr << "setting parameters for no duration pruning" << endl; - t.setParameter("prunethresh", 0.0f); - } - - transforms.push_back(t); - - t.setOutput(noteout); - - transforms.push_back(t); - - std::vector layers = - m_document->createDerivedLayers(transforms, m_fileModel); - - for (int i = 0; i < (int)layers.size(); ++i) { - - FlexiNoteLayer *f = qobject_cast(layers[i]); - TimeValueLayer *t = qobject_cast(layers[i]); - - if (f) m_layers[Notes] = f; - if (t) m_layers[PitchTrack] = t; - - m_document->addLayerToView(m_pane, layers[i]); - } - - ColourDatabase *cdb = ColourDatabase::getInstance(); - - TimeValueLayer *pitchLayer = - qobject_cast(m_layers[PitchTrack]); - if (pitchLayer) { - pitchLayer->setBaseColour(cdb->getColourIndex(tr("Black"))); - auto params = pitchLayer->getPlayParameters(); - if (params) { - params->setPlayPan(1); - params->setPlayGain(0.5); - } - connect(pitchLayer, SIGNAL(modelCompletionChanged(ModelId)), - this, SLOT(layerCompletionChanged(ModelId))); - } - - FlexiNoteLayer *flexiNoteLayer = - qobject_cast(m_layers[Notes]); - if (flexiNoteLayer) { - flexiNoteLayer->setBaseColour(cdb->getColourIndex(tr("Bright Blue"))); - auto params = flexiNoteLayer->getPlayParameters(); - if (params) { - params->setPlayPan(1); - params->setPlayGain(0.5); - } - connect(flexiNoteLayer, SIGNAL(modelCompletionChanged(ModelId)), - this, SLOT(layerCompletionChanged(ModelId))); - connect(flexiNoteLayer, SIGNAL(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float)), - this, SLOT(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float))); - connect(flexiNoteLayer, SIGNAL(materialiseReAnalysis()), - this, SLOT(materialiseReAnalysis())); - } - - return ""; -} - -void -Analyser::reAnalyseRegion(sv_frame_t frame0, sv_frame_t frame1, float freq0, float freq1) -{ - cerr << "Analyser::reAnalyseRegion(" << frame0 << ", " << frame1 - << ", " << freq0 << ", " << freq1 << ")" << endl; - showPitchCandidates(true); - (void)reAnalyseSelection(Selection(frame0, frame1), - FrequencyRange(freq0, freq1)); -} - -void -Analyser::materialiseReAnalysis() -{ - if (m_reAnalysingSelection.isEmpty()) return; - switchPitchCandidate(m_reAnalysingSelection, true); // or false, doesn't matter -} - -QString -Analyser::reAnalyseSelection(Selection sel, FrequencyRange range) -{ - QMutexLocker locker(&m_asyncMutex); - - auto waveFileModel = ModelById::getAs(m_fileModel); - if (!waveFileModel) { - return "Internal error: Analyser::reAnalyseSelection() called with no model present"; - } - - if (!m_reAnalysingSelection.isEmpty()) { - if (sel == m_reAnalysingSelection && range == m_reAnalysingRange) { - cerr << "selection & range are same as current analysis, ignoring" << endl; - return ""; - } - } - - if (sel.isEmpty()) return ""; - - if (m_currentAsyncHandle) { - m_document->cancelAsyncLayerCreation(m_currentAsyncHandle); - } - - if (!m_reAnalysisCandidates.empty()) { - CommandHistory::getInstance()->startCompoundOperation - (tr("Discard Previous Candidates"), true); - discardPitchCandidates(); - CommandHistory::getInstance()->endCompoundOperation(); - } - - m_reAnalysingSelection = sel; - m_reAnalysingRange = range; - - m_preAnalysis = Clipboard(); - Layer *myLayer = m_layers[PitchTrack]; - if (myLayer) { - myLayer->copy(m_pane, sel, m_preAnalysis); - } - - TransformFactory *tf = TransformFactory::getInstance(); - - QString plugname1 = "pYIN"; - QString plugname2 = "CHP"; - - QString base = "vamp:pyin:localcandidatepyin:"; - QString out = "pitchtrackcandidates"; - - if (range.isConstrained()) { - base = "vamp:chp:constrainedharmonicpeak:"; - out = "peak"; - } - - Transforms transforms; - - QString notFound = tr("Transform \"%1\" not found. Unable to perform interactive analysis.

Are the %2 and %3 Vamp plugins correctly installed?"); - if (!tf->haveTransform(base + out)) { - return notFound.arg(base + out).arg(plugname1).arg(plugname2); - } - - Transform t = tf->getDefaultTransformFor - (base + out, waveFileModel->getSampleRate()); - t.setStepSize(256); - t.setBlockSize(2048); - - if (range.isConstrained()) { - t.setParameter("minfreq", float(range.min)); - t.setParameter("maxfreq", float(range.max)); - t.setBlockSize(4096); - } - - // get time stamps that align with the 256-sample grid of the original extraction - const sv_frame_t grid = 256; - sv_frame_t startSample = (sel.getStartFrame() / grid) * grid; - if (startSample < sel.getStartFrame()) startSample += grid; - sv_frame_t endSample = (sel.getEndFrame() / grid) * grid; - if (endSample < sel.getEndFrame()) endSample += grid; - if (!range.isConstrained()) { - startSample -= 4*grid; // 4*256 is for 4 frames offset due to timestamp shift - endSample -= 4*grid; - } else { - endSample -= 9*grid; // MM says: not sure what the CHP plugin does there - } - RealTime start = RealTime::frame2RealTime(startSample, waveFileModel->getSampleRate()); - RealTime end = RealTime::frame2RealTime(endSample, waveFileModel->getSampleRate()); - - RealTime duration; - - if (sel.getEndFrame() > sel.getStartFrame()) { - duration = end - start; - } - - cerr << "Analyser::reAnalyseSelection: start " << start << " end " << end << " original selection start " << sel.getStartFrame() << " end " << sel.getEndFrame() << " duration " << duration << endl; - - if (duration <= RealTime::zeroTime) { - cerr << "Analyser::reAnalyseSelection: duration <= 0, not analysing" << endl; - return ""; - } - - t.setStartTime(start); - t.setDuration(duration); - - transforms.push_back(t); - - m_currentAsyncHandle = - m_document->createDerivedLayersAsync(transforms, m_fileModel, this); - - return ""; -} - -bool -Analyser::arePitchCandidatesShown() const -{ - return m_candidatesVisible; -} - -void -Analyser::showPitchCandidates(bool shown) -{ - if (m_candidatesVisible == shown) return; - - foreach (Layer *layer, m_reAnalysisCandidates) { - if (shown) { - CommandHistory::getInstance()->addCommand - (new ShowLayerCommand(m_pane, layer, true, - tr("Show Pitch Candidates"))); - } else { - CommandHistory::getInstance()->addCommand - (new ShowLayerCommand(m_pane, layer, false, - tr("Hide Pitch Candidates"))); - } - } - - m_candidatesVisible = shown; -} - -void -Analyser::layersCreated(Document::LayerCreationAsyncHandle handle, - vector primary, - vector additional) -{ - { - QMutexLocker locker(&m_asyncMutex); - - if (handle != m_currentAsyncHandle || - m_reAnalysingSelection == Selection()) { - // We don't want these! - for (int i = 0; i < (int)primary.size(); ++i) { - m_document->deleteLayer(primary[i]); - } - for (int i = 0; i < (int)additional.size(); ++i) { - m_document->deleteLayer(additional[i]); - } - return; - } - m_currentAsyncHandle = 0; - - CommandHistory::getInstance()->startCompoundOperation - (tr("Re-Analyse Selection"), true); - - m_reAnalysisCandidates.clear(); - - vector all; - for (int i = 0; i < (int)primary.size(); ++i) { - all.push_back(primary[i]); - } - for (int i = 0; i < (int)additional.size(); ++i) { - all.push_back(additional[i]); - } - - for (int i = 0; i < (int)all.size(); ++i) { - TimeValueLayer *t = qobject_cast(all[i]); - if (t) { - auto params = t->getPlayParameters(); - if (params) { - params->setPlayAudible(false); - } - t->setBaseColour - (ColourDatabase::getInstance()->getColourIndex(tr("Bright Orange"))); - t->setPresentationName("candidate"); - m_document->addLayerToView(m_pane, t); - m_reAnalysisCandidates.push_back(t); - /* - cerr << "New re-analysis candidate model has " - << ((SparseTimeValueModel *)t->getModel())->getAllEvents().size() << " point(s)" << endl; - */ - } - } - - if (!all.empty()) { - bool show = m_candidatesVisible; - m_candidatesVisible = !show; // to ensure the following takes effect - showPitchCandidates(show); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } - - emit layersChanged(); -} - -bool -Analyser::haveHigherPitchCandidate() const -{ - if (m_reAnalysisCandidates.empty()) return false; - return (m_currentCandidate < 0 || - (m_currentCandidate + 1 < (int)m_reAnalysisCandidates.size())); -} - -bool -Analyser::haveLowerPitchCandidate() const -{ - if (m_reAnalysisCandidates.empty()) return false; - return (m_currentCandidate < 0 || m_currentCandidate >= 1); -} - -void -Analyser::switchPitchCandidate(Selection sel, bool up) -{ - if (m_reAnalysisCandidates.empty()) return; - - if (up) { - m_currentCandidate = m_currentCandidate + 1; - if (m_currentCandidate >= (int)m_reAnalysisCandidates.size()) { - m_currentCandidate = 0; - } - } else { - m_currentCandidate = m_currentCandidate - 1; - if (m_currentCandidate < 0) { - m_currentCandidate = (int)m_reAnalysisCandidates.size() - 1; - } - } - - Layer *pitchTrack = m_layers[PitchTrack]; - if (!pitchTrack) return; - - Clipboard clip; - pitchTrack->deleteSelection(sel); - m_reAnalysisCandidates[m_currentCandidate]->copy(m_pane, sel, clip); - pitchTrack->paste(m_pane, clip, 0, false); - - stackLayers(); -} - -void -Analyser::stackLayers() -{ - // raise the pitch track, then notes on top (if present) - if (m_layers[PitchTrack]) { - m_paneStack->setCurrentLayer(m_pane, m_layers[PitchTrack]); - } - if (m_layers[Notes] && !m_layers[Notes]->isLayerDormant(m_pane)) { - m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); - } -} - -void -Analyser::shiftOctave(Selection sel, bool up) -{ - float factor = (up ? 2.f : 0.5f); - - vector actOn; - - Layer *pitchTrack = m_layers[PitchTrack]; - if (pitchTrack) actOn.push_back(pitchTrack); - - foreach (Layer *layer, actOn) { - - Clipboard clip; - layer->copy(m_pane, sel, clip); - layer->deleteSelection(sel); - - Clipboard shifted; - foreach (Event e, clip.getPoints()) { - if (e.hasValue()) { - Event se = e.withValue(e.getValue() * factor); - shifted.addPoint(se); - } else { - shifted.addPoint(e); - } - } - - layer->paste(m_pane, shifted, 0, false); - } -} - -void -Analyser::deletePitches(Selection sel) -{ - Layer *pitchTrack = m_layers[PitchTrack]; - if (!pitchTrack) return; - - pitchTrack->deleteSelection(sel); -} - -void -Analyser::abandonReAnalysis(Selection sel) -{ - // A compound command is already in progress - - discardPitchCandidates(); - - Layer *myLayer = m_layers[PitchTrack]; - if (!myLayer) return; - myLayer->deleteSelection(sel); - myLayer->paste(m_pane, m_preAnalysis, 0, false); -} - -void -Analyser::clearReAnalysis() -{ - discardPitchCandidates(); -} - -void -Analyser::discardPitchCandidates() -{ - if (!m_reAnalysisCandidates.empty()) { - // We don't use a compound command here, because we may be - // already in one. Caller bears responsibility for doing that - foreach (Layer *layer, m_reAnalysisCandidates) { - // This will cause the layer to be deleted later (ownership is - // transferred to the remove command) - m_document->removeLayerFromView(m_pane, layer); - } - m_reAnalysisCandidates.clear(); - } - - m_currentCandidate = -1; - m_reAnalysingSelection = Selection(); - m_candidatesVisible = false; -} - -void -Analyser::layerAboutToBeDeleted(Layer *doomed) -{ - cerr << "Analyser::layerAboutToBeDeleted(" << doomed << ")" << endl; - - vector notDoomed; - - foreach (Layer *layer, m_reAnalysisCandidates) { - if (layer != doomed) { - notDoomed.push_back(layer); - } - } - - m_reAnalysisCandidates = notDoomed; -} - -void -Analyser::takePitchTrackFrom(Layer *otherLayer) -{ - Layer *myLayer = m_layers[PitchTrack]; - if (!myLayer || !otherLayer) return; - - auto myModel = ModelById::get(myLayer->getModel()); - auto otherModel = ModelById::get(otherLayer->getModel()); - if (!myModel || !otherModel) return; - - Clipboard clip; - - Selection sel = Selection(myModel->getStartFrame(), - myModel->getEndFrame()); - myLayer->deleteSelection(sel); - - sel = Selection(otherModel->getStartFrame(), - otherModel->getEndFrame()); - otherLayer->copy(m_pane, sel, clip); - - // Remove all pitches <= 0Hz -- we now save absent pitches as 0Hz - // values when exporting a pitch track, so we need to exclude them - // here when importing again - EventVector after; - int excl = 0; - for (const auto &p: clip.getPoints()) { - if (p.hasValue() && p.getValue() > 0.f) { - after.push_back(p); - } else { - ++excl; - } - } - clip.setPoints(after); - - myLayer->paste(m_pane, clip, 0, false); -} - -void -Analyser::getEnclosingSelectionScope(sv_frame_t f, sv_frame_t &f0, sv_frame_t &f1) -{ - FlexiNoteLayer *flexiNoteLayer = - qobject_cast(m_layers[Notes]); - - sv_frame_t f0i = f, f1i = f; - int res = 1; - - if (!flexiNoteLayer) { - f0 = f1 = f; - return; - } - - flexiNoteLayer->snapToFeatureFrame(m_pane, f0i, res, Layer::SnapLeft); - flexiNoteLayer->snapToFeatureFrame(m_pane, f1i, res, Layer::SnapRight); - - f0 = (f0i < 0 ? 0 : f0i); - f1 = (f1i < 0 ? 0 : f1i); -} - -void -Analyser::saveState(Component c) const -{ - bool v = isVisible(c); - bool a = isAudible(c); - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue(QString("visible-%1").arg(int(c)), v); - settings.setValue(QString("audible-%1").arg(int(c)), a); - settings.endGroup(); -} - -void -Analyser::loadState(Component c) -{ - QSettings settings; - settings.beginGroup("Analyser"); - bool deflt = (c == Spectrogram ? false : true); - bool v = settings.value(QString("visible-%1").arg(int(c)), deflt).toBool(); - bool a = settings.value(QString("audible-%1").arg(int(c)), true).toBool(); - settings.endGroup(); - setVisible(c, v); - setAudible(c, a); -} - -void -Analyser::setIntelligentActions(bool on) -{ - std::cerr << "toggle setIntelligentActions " << on << std::endl; - - FlexiNoteLayer *flexiNoteLayer = - qobject_cast(m_layers[Notes]); - if (flexiNoteLayer) { - flexiNoteLayer->setIntelligentActions(on); - } -} - -bool -Analyser::isVisible(Component c) const -{ - if (m_layers[c]) { - return !m_layers[c]->isLayerDormant(m_pane); - } else { - return false; - } -} - -void -Analyser::setVisible(Component c, bool v) -{ - if (m_layers[c]) { - m_layers[c]->setLayerDormant(m_pane, !v); - - if (v) { - if (c == Notes) { - m_paneStack->setCurrentLayer(m_pane, m_layers[c]); - } else if (c == PitchTrack) { - // raise the pitch track, then notes on top (if present) - m_paneStack->setCurrentLayer(m_pane, m_layers[c]); - if (m_layers[Notes] && - !m_layers[Notes]->isLayerDormant(m_pane)) { - m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); - } - } - } - - m_pane->layerParametersChanged(); - saveState(c); - } -} - -bool -Analyser::isAudible(Component c) const -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return false; - return params->isPlayAudible(); - } else { - return false; - } -} - -void -Analyser::setAudible(Component c, bool a) -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return; - params->setPlayAudible(a); - saveState(c); - } -} - -float -Analyser::getGain(Component c) const -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return 1.f; - return params->getPlayGain(); - } else { - return 1.f; - } -} - -void -Analyser::setGain(Component c, float gain) -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return; - params->setPlayGain(gain); - saveState(c); - } -} - -float -Analyser::getPan(Component c) const -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return 1.f; - return params->getPlayPan(); - } else { - return 1.f; - } -} - -void -Analyser::setPan(Component c, float pan) -{ - if (m_layers[c]) { - auto params = m_layers[c]->getPlayParameters(); - if (!params) return; - params->setPlayPan(pan); - saveState(c); - } -} - - - diff -r 47f96711069f -r f52766aa747b src/Analyser.h --- a/src/Analyser.h Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,266 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - 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. -*/ - -#ifndef ANALYSER_H -#define ANALYSER_H - -#include -#include -#include - -#include -#include - -#include "framework/Document.h" -#include "base/Selection.h" -#include "base/Clipboard.h" -#include "data/model/WaveFileModel.h" - -class Pane; -class PaneStack; -class Layer; -class TimeValueLayer; -class Layer; - -class Analyser : public QObject, - public Document::LayerCreationHandler -{ - Q_OBJECT - -public: - Analyser(); - virtual ~Analyser(); - - // Process new main model, add derived layers; return "" on - // success or error string on failure - QString newFileLoaded(Document *newDocument, - ModelId model, - PaneStack *paneStack, - Pane *pane); - - // Remove any derived layers, process the main model, add derived - // layers; return "" on success or error string on failure - QString analyseExistingFile(); - - // Discard any layers etc associated with the current document - void fileClosed(); - - void setIntelligentActions(bool); - - bool getDisplayFrequencyExtents(double &min, double &max); - bool setDisplayFrequencyExtents(double min, double max); - - // Return completion %age for initial analysis -- 100 means it's done - int getInitialAnalysisCompletion(); - - enum Component { - Audio = 0, - PitchTrack = 1, - Notes = 2, - Spectrogram = 3, - }; - - bool isVisible(Component c) const; - void setVisible(Component c, bool v); - void toggleVisible(Component c) { setVisible(c, !isVisible(c)); } - - bool isAudible(Component c) const; - void setAudible(Component c, bool v); - void toggleAudible(Component c) { setAudible(c, !isAudible(c)); } - - void cycleStatus(Component c) { - if (isVisible(c)) { - if (isAudible(c)) { - setVisible(c, false); - setAudible(c, false); - } else { - setAudible(c, true); - } - } else { - setVisible(c, true); - setAudible(c, false); - } - } - - ModelId getMainModelId() const { - return m_fileModel; - } - std::shared_ptr getMainModel() const { - return ModelById::getAs(m_fileModel); - } - - float getGain(Component c) const; - void setGain(Component c, float gain); - - float getPan(Component c) const; - void setPan(Component c, float pan); - - void getEnclosingSelectionScope(sv_frame_t f, sv_frame_t &f0, sv_frame_t &f1); - - struct FrequencyRange { - FrequencyRange() : min(0), max(0) { } - FrequencyRange(double min_, double max_) : min(min_), max(max_) { } - bool isConstrained() const { return min != max; } - double min; - double max; - bool operator==(const FrequencyRange &r) { - return min == r.min && max == r.max; - } - }; - - /** - * Analyse the selection and schedule asynchronous adds of - * candidate layers for the region it contains. Returns "" on - * success or a user-readable error string on failure. If the - * frequency range isConstrained(), analysis will be constrained - * to that range. - */ - QString reAnalyseSelection(Selection sel, FrequencyRange range); - - /** - * Return true if the analysed pitch candidates are currently - * visible (they are hidden from the call to reAnalyseSelection - * until they are requested through showPitchCandidates()). Note - * that this may return true even when no pitch candidate layers - * actually exist yet, because they are constructed - * asynchronously. If that is the case, then the layers will - * appear when they are created (otherwise they will remain hidden - * after creation). - */ - bool arePitchCandidatesShown() const; - - /** - * Show or hide the analysed pitch candidate layers. This is reset - * (to "hide") with each new call to reAnalyseSelection. Because - * the layers are created asynchronously, setting this to true - * does not guarantee that they appear immediately, only that they - * will appear once they have been created. - */ - void showPitchCandidates(bool shown); - - /** - * If a re-analysis has been activated, switch the selected area - * of the main pitch track to a different candidate from the - * analysis results. - */ - void switchPitchCandidate(Selection sel, bool up); - - /** - * Return true if it is possible to switch up to another pitch - * candidate. This may mean that the currently selected pitch - * candidate is not the highest, or it may mean that no alternate - * pitch candidate has been selected at all yet (but some are - * available). - */ - bool haveHigherPitchCandidate() const; - - /** - * Return true if it is possible to switch down to another pitch - * candidate. This may mean that the currently selected pitch - * candidate is not the lowest, or it may mean that no alternate - * pitch candidate has been selected at all yet (but some are - * available). - */ - bool haveLowerPitchCandidate() const; - - /** - * Delete the pitch estimates from the selected area of the main - * pitch track. - */ - void deletePitches(Selection sel); - - /** - * Move the main pitch track and any active analysis candidate - * tracks up or down an octave in the selected area. - */ - void shiftOctave(Selection sel, bool up); - - /** - * Remove any re-analysis layers and also reset the pitch track in - * the given selection to its state prior to the last re-analysis, - * abandoning any changes made since then. No re-analysis layers - * will be available until after the next call to - * reAnalyseSelection. - */ - void abandonReAnalysis(Selection sel); - - /** - * Remove any re-analysis layers, without any expectation of - * adding them later, unlike showPitchCandidates(false), and - * without changing the current pitch track, unlike - * abandonReAnalysis(). - */ - void clearReAnalysis(); - - /** - * Import the pitch track from the given layer into our - * pitch-track layer. - */ - void takePitchTrackFrom(Layer *layer); - - Pane *getPane() { - return m_pane; - } - - Layer *getLayer(Component type) { - return m_layers[type]; - } - -signals: - void layersChanged(); - void initialAnalysisCompleted(); - -protected slots: - void layerAboutToBeDeleted(Layer *); - void layerCompletionChanged(ModelId); - void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float); - void materialiseReAnalysis(); - -protected: - Document *m_document; - ModelId m_fileModel; - PaneStack *m_paneStack; - Pane *m_pane; - - mutable std::map m_layers; - - Clipboard m_preAnalysis; - Selection m_reAnalysingSelection; - FrequencyRange m_reAnalysingRange; - std::vector m_reAnalysisCandidates; - int m_currentCandidate; - bool m_candidatesVisible; - Document::LayerCreationAsyncHandle m_currentAsyncHandle; - QMutex m_asyncMutex; - - QString doAllAnalyses(bool withPitchTrack); - - QString addVisualisations(); - QString addWaveform(); - QString addAnalyses(); - - void discardPitchCandidates(); - - void stackLayers(); - - // Document::LayerCreationHandler method - void layersCreated(Document::LayerCreationAsyncHandle, - std::vector, std::vector); - - void saveState(Component c) const; - void loadState(Component c); -}; - -#endif diff -r 47f96711069f -r f52766aa747b src/MainWindow.cpp --- a/src/MainWindow.cpp Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3355 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - 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 "../version.h" - -#include "MainWindow.h" -#include "NetworkPermissionTester.h" -#include "Analyser.h" - -#include "framework/Document.h" -#include "framework/VersionTester.h" - -#include "view/Pane.h" -#include "view/PaneStack.h" -#include "data/model/WaveFileModel.h" -#include "data/model/NoteModel.h" -#include "layer/FlexiNoteLayer.h" -#include "view/ViewManager.h" -#include "base/Preferences.h" -#include "base/RecordDirectory.h" -#include "base/AudioLevel.h" -#include "layer/WaveformLayer.h" -#include "layer/TimeInstantLayer.h" -#include "layer/TimeValueLayer.h" -#include "layer/SpectrogramLayer.h" -#include "widgets/Fader.h" -#include "view/Overview.h" -#include "widgets/AudioDial.h" -#include "widgets/IconLoader.h" -#include "widgets/KeyReference.h" -#include "widgets/LevelPanToolButton.h" -#include "audio/AudioCallbackPlaySource.h" -#include "audio/AudioCallbackRecordTarget.h" -#include "audio/PlaySpeedRangeMapper.h" -#include "base/Profiler.h" -#include "base/UnitDatabase.h" -#include "layer/ColourDatabase.h" -#include "base/Selection.h" - -#include "rdf/RDFImporter.h" -#include "data/fileio/DataFileReaderFactory.h" -#include "data/fileio/CSVFormat.h" -#include "data/fileio/CSVFileWriter.h" -#include "data/fileio/MIDIFileWriter.h" -#include "rdf/RDFExporter.h" - -#include "widgets/RangeInputDialog.h" -#include "widgets/ActivityLog.h" - -// For version information -#include "vamp/vamp.h" -#include "vamp-sdk/PluginBase.h" -#include "plugin/api/ladspa.h" -#include "plugin/api/dssi.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using std::vector; - - -MainWindow::MainWindow(SoundOptions options, bool withSonification, bool withSpectrogram) : - MainWindowBase(options), - m_overview(0), - m_mainMenusCreated(false), - m_playbackMenu(0), - m_recentFilesMenu(0), - m_rightButtonMenu(0), - m_rightButtonPlaybackMenu(0), - m_deleteSelectedAction(0), - m_ffwdAction(0), - m_rwdAction(0), - m_intelligentActionOn(true), //GF: !!! temporary - m_activityLog(new ActivityLog()), - m_keyReference(new KeyReference()), - m_selectionAnchor(0), - m_withSonification(withSonification), - m_withSpectrogram(withSpectrogram) -{ - setWindowTitle(QApplication::applicationName()); - -#ifdef Q_OS_MAC -#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) - setUnifiedTitleAndToolBarOnMac(true); -#endif -#endif - - UnitDatabase *udb = UnitDatabase::getInstance(); - udb->registerUnit("Hz"); - udb->registerUnit("dB"); - udb->registerUnit("s"); - - ColourDatabase *cdb = ColourDatabase::getInstance(); - cdb->addColour(Qt::black, tr("Black")); - cdb->addColour(Qt::darkRed, tr("Red")); - cdb->addColour(Qt::darkBlue, tr("Blue")); - cdb->addColour(Qt::darkGreen, tr("Green")); - cdb->addColour(QColor(200, 50, 255), tr("Purple")); - cdb->addColour(QColor(255, 150, 50), tr("Orange")); - cdb->addColour(QColor(180, 180, 180), tr("Grey")); - cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); - cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); - cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); - cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); - cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); - cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); - - Preferences::getInstance()->setResampleOnLoad(true); - Preferences::getInstance()->setFixedSampleRate(44100); - Preferences::getInstance()->setSpectrogramSmoothing - (Preferences::SpectrogramInterpolated); - Preferences::getInstance()->setNormaliseAudio(true); - - QSettings settings; - - settings.beginGroup("MainWindow"); - settings.setValue("showstatusbar", false); - settings.endGroup(); - - settings.beginGroup("Transformer"); - settings.setValue("use-flexi-note-model", true); - settings.endGroup(); - - settings.beginGroup("LayerDefaults"); - settings.setValue("waveform", - QString("") - .arg(int(WaveformLayer::LinearScale)) - .arg(int(WaveformLayer::MixChannels))); - settings.endGroup(); - - m_viewManager->setAlignMode(false); - m_viewManager->setPlaySoloMode(false); - m_viewManager->setToolMode(ViewManager::NavigateMode); - m_viewManager->setZoomWheelsEnabled(false); - m_viewManager->setIlluminateLocalFeatures(true); - m_viewManager->setShowWorkTitle(false); - m_viewManager->setShowCentreLine(false); - m_viewManager->setShowDuration(false); - m_viewManager->setOverlayMode(ViewManager::GlobalOverlays); - - connect(m_viewManager, SIGNAL(selectionChangedByUser()), - this, SLOT(selectionChangedByUser())); - - QFrame *frame = new QFrame; - setCentralWidget(frame); - - QGridLayout *layout = new QGridLayout; - - QScrollArea *scroll = new QScrollArea(frame); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll->setFrameShape(QFrame::NoFrame); - - // We have a pane stack: it comes with the territory. However, we - // have a fixed and known number of panes in it -- it isn't - // variable - m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); - m_paneStack->setShowPaneAccessories(false); - connect(m_paneStack, SIGNAL(doubleClickSelectInvoked(sv_frame_t)), - this, SLOT(doubleClickSelectInvoked(sv_frame_t))); - scroll->setWidget(m_paneStack); - - m_overview = new Overview(frame); - m_overview->setPlaybackFollow(PlaybackScrollPage); - m_overview->setViewManager(m_viewManager); - m_overview->setFixedHeight(60); -#ifndef _WIN32 - // For some reason, the contents of the overview never appear if we - // make this setting on Windows. I have no inclination at the moment - // to track down the reason why. - m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); -#endif - connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), - this, SLOT(contextHelpChanged(const QString &))); - - m_panLayer = new WaveformLayer; - m_panLayer->setChannelMode(WaveformLayer::MergeChannels); - m_panLayer->setAggressiveCacheing(true); - m_panLayer->setGain(0.5); - m_overview->addLayer(m_panLayer); - - if (m_viewManager->getGlobalDarkBackground()) { - m_panLayer->setBaseColour - (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green"))); - } else { - m_panLayer->setBaseColour - (ColourDatabase::getInstance()->getColourIndex(tr("Blue"))); - } - - m_fader = new Fader(frame, false); - connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); - connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - - m_playSpeed = new AudioDial(frame); - m_playSpeed->setMeterColor(Qt::darkBlue); - m_playSpeed->setMinimum(0); - m_playSpeed->setMaximum(120); - m_playSpeed->setValue(60); - m_playSpeed->setFixedWidth(24); - m_playSpeed->setFixedHeight(24); - m_playSpeed->setNotchesVisible(true); - m_playSpeed->setPageStep(10); - m_playSpeed->setObjectName(tr("Playback Speed")); - m_playSpeed->setDefaultValue(60); - m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); - m_playSpeed->setShowToolTip(true); - connect(m_playSpeed, SIGNAL(valueChanged(int)), - this, SLOT(playSpeedChanged(int))); - connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); - connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); - - m_audioLPW = new LevelPanToolButton(frame); - m_audioLPW->setIncludeMute(false); - m_audioLPW->setObjectName(tr("Audio Track Level and Pan")); - connect(m_audioLPW, SIGNAL(levelChanged(float)), this, SLOT(audioGainChanged(float))); - connect(m_audioLPW, SIGNAL(panChanged(float)), this, SLOT(audioPanChanged(float))); - - if (m_withSonification) { - - m_pitchLPW = new LevelPanToolButton(frame); - m_pitchLPW->setIncludeMute(false); - m_pitchLPW->setObjectName(tr("Pitch Track Level and Pan")); - connect(m_pitchLPW, SIGNAL(levelChanged(float)), this, SLOT(pitchGainChanged(float))); - connect(m_pitchLPW, SIGNAL(panChanged(float)), this, SLOT(pitchPanChanged(float))); - - m_notesLPW = new LevelPanToolButton(frame); - m_notesLPW->setIncludeMute(false); - m_notesLPW->setObjectName(tr("Note Track Level and Pan")); - connect(m_notesLPW, SIGNAL(levelChanged(float)), this, SLOT(notesGainChanged(float))); - connect(m_notesLPW, SIGNAL(panChanged(float)), this, SLOT(notesPanChanged(float))); - } - - layout->setSpacing(4); - layout->addWidget(m_overview, 0, 1); - layout->addWidget(scroll, 1, 1); - - layout->setColumnStretch(1, 10); - - frame->setLayout(layout); - - m_analyser = new Analyser(); - connect(m_analyser, SIGNAL(layersChanged()), - this, SLOT(updateLayerStatuses())); - connect(m_analyser, SIGNAL(layersChanged()), - this, SLOT(updateMenuStates())); - - setupMenus(); - setupToolbars(); - setupHelpMenu(); - - statusBar(); - - finaliseMenus(); - - connect(m_viewManager, SIGNAL(activity(QString)), - m_activityLog, SLOT(activityHappened(QString))); - connect(m_playSource, SIGNAL(activity(QString)), - m_activityLog, SLOT(activityHappened(QString))); - connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), - m_activityLog, SLOT(activityHappened(QString))); - connect(this, SIGNAL(activity(QString)), - m_activityLog, SLOT(activityHappened(QString))); - connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); - connect(this, SIGNAL(sessionLoaded()), this, SLOT(analyseNewMainModel())); - connect(this, SIGNAL(audioFileLoaded()), this, SLOT(analyseNewMainModel())); - m_activityLog->hide(); - - setAudioRecordMode(RecordReplaceSession); - - newSession(); - - settings.beginGroup("MainWindow"); - settings.setValue("zoom-default", 512); - settings.endGroup(); - zoomDefault(); - - NetworkPermissionTester tester; - bool networkPermission = tester.havePermission(); - if (networkPermission) { - m_versionTester = new VersionTester - ("sonicvisualiser.org", "latest-tony-version.txt", TONY_VERSION); - connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), - this, SLOT(newerVersionAvailable(QString))); - } else { - m_versionTester = 0; - } -} - -MainWindow::~MainWindow() -{ - delete m_analyser; - delete m_keyReference; - Profiles::getInstance()->dump(); -} - -void -MainWindow::setupMenus() -{ - if (!m_mainMenusCreated) { - -#ifdef Q_OS_LINUX - // In Ubuntu 14.04 the window's menu bar goes missing entirely - // if the user is running any desktop environment other than Unity - // (in which the faux single-menubar appears). The user has a - // workaround, to remove the appmenu-qt5 package, but that is - // awkward and the problem is so severe that it merits disabling - // the system menubar integration altogether. Like this: - menuBar()->setNativeMenuBar(false); -#endif - - m_rightButtonMenu = new QMenu(); - } - - if (!m_mainMenusCreated) { - CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); - m_rightButtonMenu->addSeparator(); - } - - setupFileMenu(); - setupEditMenu(); - setupViewMenu(); - setupAnalysisMenu(); - - m_mainMenusCreated = true; -} - -void -MainWindow::setupFileMenu() -{ - if (m_mainMenusCreated) return; - - QMenu *menu = menuBar()->addMenu(tr("&File")); - menu->setTearOffEnabled(true); - QToolBar *toolbar = addToolBar(tr("File Toolbar")); - - m_keyReference->setCategory(tr("File and Session Management")); - - IconLoader il; - QIcon icon; - QAction *action; - - icon = il.load("fileopen"); - action = new QAction(icon, tr("&Open..."), this); - action->setShortcut(tr("Ctrl+O")); - action->setStatusTip(tr("Open a session or audio file")); - connect(action, SIGNAL(triggered()), this, SLOT(openFile())); - m_keyReference->registerShortcut(action); - menu->addAction(action); - toolbar->addAction(action); - - action = new QAction(tr("Open Lo&cation..."), this); - action->setShortcut(tr("Ctrl+Shift+O")); - action->setStatusTip(tr("Open a file from a remote URL")); - connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); - m_recentFilesMenu->setTearOffEnabled(true); - setupRecentFilesMenu(); - connect(&m_recentFiles, SIGNAL(recentChanged()), - this, SLOT(setupRecentFilesMenu())); - - menu->addSeparator(); - - icon = il.load("filesave"); - action = new QAction(icon, tr("&Save Session"), this); - action->setShortcut(tr("Ctrl+S")); - action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); - connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); - connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - toolbar->addAction(action); - - icon = il.load("filesaveas"); - action = new QAction(icon, tr("Save Session &As..."), this); - action->setShortcut(tr("Ctrl+Shift+S")); - action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); - connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); - connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - toolbar->addAction(action); - - action = new QAction(tr("Save Session to Audio File &Path"), this); - action->setShortcut(tr("Ctrl+Alt+S")); - action->setStatusTip(tr("Save the current session into a %1 session file with the same filename as the audio but a .ton extension.").arg(QApplication::applicationName())); - connect(action, SIGNAL(triggered()), this, SLOT(saveSessionInAudioPath())); - connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - - menu->addSeparator(); - - action = new QAction(tr("I&mport Pitch Track Data..."), this); - action->setStatusTip(tr("Import pitch-track data from a CSV, RDF, or layer XML file")); - connect(action, SIGNAL(triggered()), this, SLOT(importPitchLayer())); - connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - - action = new QAction(tr("E&xport Pitch Track Data..."), this); - action->setStatusTip(tr("Export pitch-track data to a CSV, RDF, or layer XML file")); - connect(action, SIGNAL(triggered()), this, SLOT(exportPitchLayer())); - connect(this, SIGNAL(canExportPitchTrack(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - - action = new QAction(tr("&Export Note Data..."), this); - action->setStatusTip(tr("Export note data to a CSV, RDF, layer XML, or MIDI file")); - connect(action, SIGNAL(triggered()), this, SLOT(exportNoteLayer())); - connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - - menu->addSeparator(); - - action = new QAction(tr("Browse Recorded Audio"), this); - action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); - connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); - menu->addAction(action); - - menu->addSeparator(); - - action = new QAction(il.load("exit"), tr("&Quit"), this); - action->setShortcut(tr("Ctrl+Q")); - action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); - connect(action, SIGNAL(triggered()), this, SLOT(close())); - m_keyReference->registerShortcut(action); - menu->addAction(action); -} - -void -MainWindow::setupEditMenu() -{ - if (m_mainMenusCreated) return; - - QMenu *menu = menuBar()->addMenu(tr("&Edit")); - menu->setTearOffEnabled(true); - CommandHistory::getInstance()->registerMenu(menu); - menu->addSeparator(); - - m_keyReference->setCategory - (tr("Selection Strip Mouse Actions")); - m_keyReference->registerShortcut - (tr("Jump"), tr("Left"), - tr("Click left button to move the playback position to a time")); - m_keyReference->registerShortcut - (tr("Select"), tr("Left"), - tr("Click left button and drag to select a region of time")); - m_keyReference->registerShortcut - (tr("Select Note Duration"), tr("Double-Click Left"), - tr("Double-click left button to select the region of time corresponding to a note")); - - QToolBar *toolbar = addToolBar(tr("Tools Toolbar")); - - CommandHistory::getInstance()->registerToolbar(toolbar); - - QActionGroup *group = new QActionGroup(this); - - IconLoader il; - - m_keyReference->setCategory(tr("Tool Selection")); - QAction *action = toolbar->addAction(il.load("navigate"), - tr("Navigate")); - action->setCheckable(true); - action->setChecked(true); - action->setShortcut(tr("1")); - action->setStatusTip(tr("Navigate")); - connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); - connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); - group->addAction(action); - menu->addAction(action); - m_keyReference->registerShortcut(action); - - m_keyReference->setCategory - (tr("Navigate Tool Mouse Actions")); - m_keyReference->registerShortcut - (tr("Navigate"), tr("Left"), - tr("Click left button and drag to move around")); - m_keyReference->registerShortcut - (tr("Re-Analyse Area"), tr("Shift+Left"), - tr("Shift-click left button and drag to define a specific pitch and time range to re-analyse")); - m_keyReference->registerShortcut - (tr("Edit"), tr("Double-Click Left"), - tr("Double-click left button on an item to edit it")); - - m_keyReference->setCategory(tr("Tool Selection")); - action = toolbar->addAction(il.load("move"), - tr("Edit")); - action->setCheckable(true); - action->setShortcut(tr("2")); - action->setStatusTip(tr("Edit with Note Intelligence")); - connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); - group->addAction(action); - menu->addAction(action); - m_keyReference->registerShortcut(action); - - m_keyReference->setCategory - (tr("Note Edit Tool Mouse Actions")); - m_keyReference->registerShortcut - (tr("Adjust Pitch"), tr("Left"), - tr("Click left button on the main part of a note and drag to move it up or down")); - m_keyReference->registerShortcut - (tr("Split"), tr("Left"), - tr("Click left button on the bottom edge of a note to split it at the click point")); - m_keyReference->registerShortcut - (tr("Resize"), tr("Left"), - tr("Click left button on the left or right edge of a note and drag to change the time or duration of the note")); - m_keyReference->registerShortcut - (tr("Erase"), tr("Shift+Left"), - tr("Shift-click left button on a note to remove it")); - - -/* Remove for now... - - m_keyReference->setCategory(tr("Tool Selection")); - action = toolbar->addAction(il.load("notes"), - tr("Free Edit")); - action->setCheckable(true); - action->setShortcut(tr("3")); - action->setStatusTip(tr("Free Edit")); - connect(action, SIGNAL(triggered()), this, SLOT(toolFreeEditSelected())); - group->addAction(action); - m_keyReference->registerShortcut(action); -*/ - - menu->addSeparator(); - - m_keyReference->setCategory(tr("Selection")); - - action = new QAction(tr("Select &All"), this); - action->setShortcut(tr("Ctrl+A")); - action->setStatusTip(tr("Select the whole duration of the current session")); - connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); - connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("C&lear Selection"), this); - action->setShortcuts(QList() - << QKeySequence(tr("Esc")) - << QKeySequence(tr("Ctrl+Esc"))); - action->setStatusTip(tr("Clear the selection and abandon any pending pitch choices in it")); - connect(action, SIGNAL(triggered()), this, SLOT(abandonSelection())); - connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - m_keyReference->registerAlternativeShortcut(action, QKeySequence(tr("Ctrl+Esc"))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - menu->addSeparator(); - m_rightButtonMenu->addSeparator(); - - m_keyReference->setCategory(tr("Pitch Track")); - - action = new QAction(tr("Choose Higher Pitch"), this); - action->setShortcut(tr("Ctrl+Up")); - action->setStatusTip(tr("Move pitches up an octave, or to the next higher pitch candidate")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(switchPitchUp())); - connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("Choose Lower Pitch"), this); - action->setShortcut(tr("Ctrl+Down")); - action->setStatusTip(tr("Move pitches down an octave, or to the next lower pitch candidate")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(switchPitchDown())); - connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - m_showCandidatesAction = new QAction(tr("Show Pitch Candidates"), this); - m_showCandidatesAction->setShortcut(tr("Ctrl+Return")); - m_showCandidatesAction->setStatusTip(tr("Toggle the display of alternative pitch candidates for the selected region")); - m_keyReference->registerShortcut(m_showCandidatesAction); - connect(m_showCandidatesAction, SIGNAL(triggered()), this, SLOT(togglePitchCandidates())); - connect(this, SIGNAL(canClearSelection(bool)), m_showCandidatesAction, SLOT(setEnabled(bool))); - menu->addAction(m_showCandidatesAction); - m_rightButtonMenu->addAction(m_showCandidatesAction); - - action = new QAction(tr("Remove Pitches"), this); - action->setShortcut(tr("Ctrl+Backspace")); - action->setStatusTip(tr("Remove all pitch estimates within the selected region, making it unvoiced")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(clearPitches())); - connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - menu->addSeparator(); - m_rightButtonMenu->addSeparator(); - - m_keyReference->setCategory(tr("Note Track")); - - action = new QAction(tr("Split Note"), this); - action->setShortcut(tr("/")); - action->setStatusTip(tr("Split the note at the current playback position into two")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(splitNote())); - connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("Merge Notes"), this); - action->setShortcut(tr("\\")); - action->setStatusTip(tr("Merge all notes within the selected region into a single note")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(mergeNotes())); - connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("Delete Notes"), this); - action->setShortcut(tr("Backspace")); - action->setStatusTip(tr("Delete all notes within the selected region")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(deleteNotes())); - connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("Form Note from Selection"), this); - action->setShortcut(tr("=")); - action->setStatusTip(tr("Form a note spanning the selected region, splitting any existing notes at its boundaries")); - m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(formNoteFromSelection())); - connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); - - action = new QAction(tr("Snap Notes to Pitch Track"), this); - action->setStatusTip(tr("Set notes within the selected region to the median frequency of their underlying pitches, or remove them if there are no underlying pitches")); - // m_keyReference->registerShortcut(action); - connect(action, SIGNAL(triggered()), this, SLOT(snapNotesToPitches())); - connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - m_rightButtonMenu->addAction(action); -} - -void -MainWindow::setupViewMenu() -{ - if (m_mainMenusCreated) return; - - IconLoader il; - - QAction *action = 0; - - m_keyReference->setCategory(tr("Panning and Navigation")); - - QMenu *menu = menuBar()->addMenu(tr("&View")); - menu->setTearOffEnabled(true); - action = new QAction(tr("Peek &Left"), this); - action->setShortcut(tr("Alt+Left")); - action->setStatusTip(tr("Scroll the current pane to the left without changing the play position")); - connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); - connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - action = new QAction(tr("Peek &Right"), this); - action->setShortcut(tr("Alt+Right")); - action->setStatusTip(tr("Scroll the current pane to the right without changing the play position")); - connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); - connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - menu->addSeparator(); - - m_keyReference->setCategory(tr("Zoom")); - - action = new QAction(il.load("zoom-in"), - tr("Zoom &In"), this); - action->setShortcut(tr("Up")); - action->setStatusTip(tr("Increase the zoom level")); - connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); - connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - action = new QAction(il.load("zoom-out"), - tr("Zoom &Out"), this); - action->setShortcut(tr("Down")); - action->setStatusTip(tr("Decrease the zoom level")); - connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); - connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - action = new QAction(tr("Restore &Default Zoom"), this); - action->setStatusTip(tr("Restore the zoom level to the default")); - connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); - connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); - menu->addAction(action); - - action = new QAction(il.load("zoom-fit"), - tr("Zoom to &Fit"), this); - action->setShortcut(tr("F")); - action->setStatusTip(tr("Zoom to show the whole file")); - connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); - connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - menu->addSeparator(); - - action = new QAction(tr("Set Displayed Fre&quency Range..."), this); - action->setStatusTip(tr("Set the minimum and maximum frequencies in the visible display")); - connect(action, SIGNAL(triggered()), this, SLOT(editDisplayExtents())); - menu->addAction(action); -} - -void -MainWindow::setupAnalysisMenu() -{ - if (m_mainMenusCreated) return; - - IconLoader il; - - QAction *action = 0; - - QMenu *menu = menuBar()->addMenu(tr("&Analysis")); - menu->setTearOffEnabled(true); - - m_autoAnalyse = new QAction(tr("Auto-Analyse &New Audio"), this); - m_autoAnalyse->setStatusTip(tr("Automatically trigger analysis upon opening of a new audio file.")); - m_autoAnalyse->setCheckable(true); - connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled())); - menu->addAction(m_autoAnalyse); - - action = new QAction(tr("&Analyse Now!"), this); - action->setStatusTip(tr("Trigger analysis of pitches and notes. (This will delete all existing pitches and notes.)")); - connect(action, SIGNAL(triggered()), this, SLOT(analyseNow())); - menu->addAction(action); - m_keyReference->registerShortcut(action); - - menu->addSeparator(); - - m_precise = new QAction(tr("&Unbiased Timing (slow)"), this); - m_precise->setStatusTip(tr("Use a symmetric window in YIN to remove frequency-dependent timing bias. (This is slow!)")); - m_precise->setCheckable(true); - connect(m_precise, SIGNAL(triggered()), this, SLOT(precisionAnalysisToggled())); - menu->addAction(m_precise); - - m_lowamp = new QAction(tr("&Penalise Soft Pitches"), this); - m_lowamp->setStatusTip(tr("Reduce the likelihood of detecting a pitch when the signal has low amplitude.")); - m_lowamp->setCheckable(true); - connect(m_lowamp, SIGNAL(triggered()), this, SLOT(lowampAnalysisToggled())); - menu->addAction(m_lowamp); - - m_onset = new QAction(tr("&High Onset Sensitivity"), this); - m_onset->setStatusTip(tr("Increase likelihood of separating notes, especially consecutive notes at the same pitch.")); - m_onset->setCheckable(true); - connect(m_onset, SIGNAL(triggered()), this, SLOT(onsetAnalysisToggled())); - menu->addAction(m_onset); - - m_prune = new QAction(tr("&Drop Short Notes"), this); - m_prune->setStatusTip(tr("Duration-based pruning: automatic note estimator will not output notes of less than 100ms duration.")); - m_prune->setCheckable(true); - connect(m_prune, SIGNAL(triggered()), this, SLOT(pruneAnalysisToggled())); - menu->addAction(m_prune); - - menu->addSeparator(); - - action = new QAction(tr("Reset Options to Defaults"), this); - action->setStatusTip(tr("Reset all of the Analyse menu options to their default settings.")); - connect(action, SIGNAL(triggered()), this, SLOT(resetAnalyseOptions())); - menu->addAction(action); - - updateAnalyseStates(); -} - -void -MainWindow::resetAnalyseOptions() -{ - //!!! oh no, we need to update the menu states as well... - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("auto-analysis", true); - settings.setValue("precision-analysis", false); - settings.setValue("lowamp-analysis", true); - settings.setValue("onset-analysis", true); - settings.setValue("prune-analysis", true); - settings.endGroup(); - updateAnalyseStates(); -} - -void -MainWindow::updateAnalyseStates() -{ - QSettings settings; - settings.beginGroup("Analyser"); - bool autoAnalyse = settings.value("auto-analysis", true).toBool(); - bool precise = settings.value("precision-analysis", false).toBool(); - bool lowamp = settings.value("lowamp-analysis", true).toBool(); - bool onset = settings.value("onset-analysis", true).toBool(); - bool prune = settings.value("prune-analysis", true).toBool(); - settings.endGroup(); - - m_autoAnalyse->setChecked(autoAnalyse); - m_precise->setChecked(precise); - m_lowamp->setChecked(lowamp); - m_onset->setChecked(onset); - m_prune->setChecked(prune); -} - -void -MainWindow::autoAnalysisToggled() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - - bool set = a->isChecked(); - - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("auto-analysis", set); - settings.endGroup(); -} - -void -MainWindow::precisionAnalysisToggled() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - - bool set = a->isChecked(); - - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("precision-analysis", set); - settings.endGroup(); - - // don't run analyseNow() automatically -- it's a destructive operation -} - -void -MainWindow::lowampAnalysisToggled() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - - bool set = a->isChecked(); - - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("lowamp-analysis", set); - settings.endGroup(); - - // don't run analyseNow() automatically -- it's a destructive operation -} - -void -MainWindow::onsetAnalysisToggled() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - - bool set = a->isChecked(); - - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("onset-analysis", set); - settings.endGroup(); - - // don't run analyseNow() automatically -- it's a destructive operation -} - -void -MainWindow::pruneAnalysisToggled() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - - bool set = a->isChecked(); - - QSettings settings; - settings.beginGroup("Analyser"); - settings.setValue("prune-analysis", set); - settings.endGroup(); - - // don't run analyseNow() automatically -- it's a destructive operation -} - -void -MainWindow::setupHelpMenu() -{ - QMenu *menu = menuBar()->addMenu(tr("&Help")); - menu->setTearOffEnabled(true); - - m_keyReference->setCategory(tr("Help")); - - IconLoader il; - - QString name = QApplication::applicationName(); - QAction *action; - - action = new QAction(tr("&Key and Mouse Reference"), this); - action->setShortcut(tr("F2")); - action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); - connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - action = new QAction(il.load("help"), - tr("&Help Reference"), this); - action->setShortcut(tr("F1")); - action->setStatusTip(tr("Open the %1 reference manual").arg(name)); - connect(action, SIGNAL(triggered()), this, SLOT(help())); - m_keyReference->registerShortcut(action); - menu->addAction(action); - - - action = new QAction(tr("%1 on the &Web").arg(name), this); - action->setStatusTip(tr("Open the %1 website").arg(name)); - connect(action, SIGNAL(triggered()), this, SLOT(website())); - menu->addAction(action); - - action = new QAction(tr("&About %1").arg(name), this); - action->setStatusTip(tr("Show information about %1").arg(name)); - connect(action, SIGNAL(triggered()), this, SLOT(about())); - menu->addAction(action); -} - -void -MainWindow::setupRecentFilesMenu() -{ - m_recentFilesMenu->clear(); - vector files = m_recentFiles.getRecent(); - for (size_t i = 0; i < files.size(); ++i) { - QAction *action = new QAction(files[i], this); - connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); - if (i == 0) { - action->setShortcut(tr("Ctrl+R")); - m_keyReference->registerShortcut - (tr("Re-open"), - action->shortcut().toString(), - tr("Re-open the current or most recently opened file")); - } - m_recentFilesMenu->addAction(action); - } -} - -void -MainWindow::setupToolbars() -{ - m_keyReference->setCategory(tr("Playback and Transport Controls")); - - IconLoader il; - - QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); - menu->setTearOffEnabled(true); - m_rightButtonMenu->addSeparator(); - m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); - - QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); - - QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"), - tr("Rewind to Start")); - rwdStartAction->setShortcut(tr("Home")); - rwdStartAction->setStatusTip(tr("Rewind to the start")); - connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); - connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool))); - - QAction *m_rwdAction = toolbar->addAction(il.load("rewind"), - tr("Rewind")); - m_rwdAction->setShortcut(tr("Left")); - m_rwdAction->setStatusTip(tr("Rewind to the previous one-second boundary")); - connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); - connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); - - setDefaultFfwdRwdStep(RealTime(1, 0)); - - QAction *playAction = toolbar->addAction(il.load("playpause"), - tr("Play / Pause")); - playAction->setCheckable(true); - playAction->setShortcut(tr("Space")); - playAction->setStatusTip(tr("Start or stop playback from the current position")); - connect(playAction, SIGNAL(triggered()), this, SLOT(play())); - connect(m_playSource, SIGNAL(playStatusChanged(bool)), - playAction, SLOT(setChecked(bool))); - connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool))); - - m_ffwdAction = toolbar->addAction(il.load("ffwd"), - tr("Fast Forward")); - m_ffwdAction->setShortcut(tr("Right")); - m_ffwdAction->setStatusTip(tr("Fast-forward to the next one-second boundary")); - connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); - connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); - - QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), - tr("Fast Forward to End")); - ffwdEndAction->setShortcut(tr("End")); - ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); - connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); - connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool))); - - QAction *recordAction = toolbar->addAction(il.load("record"), - tr("Record")); - recordAction->setCheckable(true); - recordAction->setShortcut(tr("Ctrl+Space")); - recordAction->setStatusTip(tr("Record a new audio file")); - connect(recordAction, SIGNAL(triggered()), this, SLOT(record())); - connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), - recordAction, SLOT(setChecked(bool))); - connect(m_recordTarget, SIGNAL(recordCompleted()), - this, SLOT(analyseNow())); - connect(this, SIGNAL(canRecord(bool)), - recordAction, SLOT(setEnabled(bool))); - - toolbar = addToolBar(tr("Play Mode Toolbar")); - - QAction *psAction = toolbar->addAction(il.load("playselection"), - tr("Constrain Playback to Selection")); - psAction->setCheckable(true); - psAction->setChecked(m_viewManager->getPlaySelectionMode()); - psAction->setShortcut(tr("s")); - psAction->setStatusTip(tr("Constrain playback to the selected regions")); - connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), - psAction, SLOT(setChecked(bool))); - connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); - connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool))); - - QAction *plAction = toolbar->addAction(il.load("playloop"), - tr("Loop Playback")); - plAction->setCheckable(true); - plAction->setChecked(m_viewManager->getPlayLoopMode()); - plAction->setShortcut(tr("l")); - plAction->setStatusTip(tr("Loop playback")); - connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), - plAction, SLOT(setChecked(bool))); - connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); - connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool))); - - QAction *oneLeftAction = new QAction(tr("&One Note Left"), this); - oneLeftAction->setShortcut(tr("Ctrl+Left")); - oneLeftAction->setStatusTip(tr("Move cursor to the preceding note (or silence) onset.")); - connect(oneLeftAction, SIGNAL(triggered()), this, SLOT(moveOneNoteLeft())); - connect(this, SIGNAL(canScroll(bool)), oneLeftAction, SLOT(setEnabled(bool))); - - QAction *oneRightAction = new QAction(tr("O&ne Note Right"), this); - oneRightAction->setShortcut(tr("Ctrl+Right")); - oneRightAction->setStatusTip(tr("Move cursor to the succeeding note (or silence).")); - connect(oneRightAction, SIGNAL(triggered()), this, SLOT(moveOneNoteRight())); - connect(this, SIGNAL(canScroll(bool)), oneRightAction, SLOT(setEnabled(bool))); - - QAction *selectOneLeftAction = new QAction(tr("&Select One Note Left"), this); - selectOneLeftAction->setShortcut(tr("Ctrl+Shift+Left")); - selectOneLeftAction->setStatusTip(tr("Select to the preceding note (or silence) onset.")); - connect(selectOneLeftAction, SIGNAL(triggered()), this, SLOT(selectOneNoteLeft())); - connect(this, SIGNAL(canScroll(bool)), selectOneLeftAction, SLOT(setEnabled(bool))); - - QAction *selectOneRightAction = new QAction(tr("S&elect One Note Right"), this); - selectOneRightAction->setShortcut(tr("Ctrl+Shift+Right")); - selectOneRightAction->setStatusTip(tr("Select to the succeeding note (or silence).")); - connect(selectOneRightAction, SIGNAL(triggered()), this, SLOT(selectOneNoteRight())); - connect(this, SIGNAL(canScroll(bool)), selectOneRightAction, SLOT(setEnabled(bool))); - - m_keyReference->registerShortcut(psAction); - m_keyReference->registerShortcut(plAction); - m_keyReference->registerShortcut(playAction); - m_keyReference->registerShortcut(recordAction); - m_keyReference->registerShortcut(m_rwdAction); - m_keyReference->registerShortcut(m_ffwdAction); - m_keyReference->registerShortcut(rwdStartAction); - m_keyReference->registerShortcut(ffwdEndAction); - m_keyReference->registerShortcut(recordAction); - m_keyReference->registerShortcut(oneLeftAction); - m_keyReference->registerShortcut(oneRightAction); - m_keyReference->registerShortcut(selectOneLeftAction); - m_keyReference->registerShortcut(selectOneRightAction); - - menu->addAction(playAction); - menu->addAction(psAction); - menu->addAction(plAction); - menu->addSeparator(); - menu->addAction(m_rwdAction); - menu->addAction(m_ffwdAction); - menu->addSeparator(); - menu->addAction(rwdStartAction); - menu->addAction(ffwdEndAction); - menu->addSeparator(); - menu->addAction(oneLeftAction); - menu->addAction(oneRightAction); - menu->addAction(selectOneLeftAction); - menu->addAction(selectOneRightAction); - menu->addSeparator(); - menu->addAction(recordAction); - menu->addSeparator(); - - m_rightButtonPlaybackMenu->addAction(playAction); - m_rightButtonPlaybackMenu->addAction(psAction); - m_rightButtonPlaybackMenu->addAction(plAction); - m_rightButtonPlaybackMenu->addSeparator(); - m_rightButtonPlaybackMenu->addAction(m_rwdAction); - m_rightButtonPlaybackMenu->addAction(m_ffwdAction); - m_rightButtonPlaybackMenu->addSeparator(); - m_rightButtonPlaybackMenu->addAction(rwdStartAction); - m_rightButtonPlaybackMenu->addAction(ffwdEndAction); - m_rightButtonPlaybackMenu->addSeparator(); - m_rightButtonPlaybackMenu->addAction(oneLeftAction); - m_rightButtonPlaybackMenu->addAction(oneRightAction); - m_rightButtonPlaybackMenu->addAction(selectOneLeftAction); - m_rightButtonPlaybackMenu->addAction(selectOneRightAction); - m_rightButtonPlaybackMenu->addSeparator(); - m_rightButtonPlaybackMenu->addAction(recordAction); - m_rightButtonPlaybackMenu->addSeparator(); - - QAction *fastAction = menu->addAction(tr("Speed Up")); - fastAction->setShortcut(tr("Ctrl+PgUp")); - fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); - connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); - connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); - - QAction *slowAction = menu->addAction(tr("Slow Down")); - slowAction->setShortcut(tr("Ctrl+PgDown")); - slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); - connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); - connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); - - QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); - normalAction->setShortcut(tr("Ctrl+Home")); - normalAction->setStatusTip(tr("Restore non-time-stretched playback")); - connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); - connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); - - m_keyReference->registerShortcut(fastAction); - m_keyReference->registerShortcut(slowAction); - m_keyReference->registerShortcut(normalAction); - - m_rightButtonPlaybackMenu->addAction(fastAction); - m_rightButtonPlaybackMenu->addAction(slowAction); - m_rightButtonPlaybackMenu->addAction(normalAction); - - toolbar = new QToolBar(tr("Playback Controls")); - addToolBar(Qt::BottomToolBarArea, toolbar); - - toolbar->addWidget(m_playSpeed); - toolbar->addWidget(m_fader); - - toolbar = addToolBar(tr("Show and Play")); - addToolBar(Qt::BottomToolBarArea, toolbar); - - m_showAudio = toolbar->addAction(il.load("waveform"), tr("Show Audio")); - m_showAudio->setCheckable(true); - connect(m_showAudio, SIGNAL(triggered()), this, SLOT(showAudioToggled())); - connect(this, SIGNAL(canPlay(bool)), m_showAudio, SLOT(setEnabled(bool))); - - m_playAudio = toolbar->addAction(il.load("speaker"), tr("Play Audio")); - m_playAudio->setCheckable(true); - connect(m_playAudio, SIGNAL(triggered()), this, SLOT(playAudioToggled())); - connect(this, SIGNAL(canPlayWaveform(bool)), m_playAudio, SLOT(setEnabled(bool))); - - int lpwSize, bigLpwSize; -#ifdef Q_OS_MAC - lpwSize = m_viewManager->scalePixelSize(32); // Mac toolbars are fatter - bigLpwSize = int(lpwSize * 2.2); -#else - lpwSize = m_viewManager->scalePixelSize(26); - bigLpwSize = int(lpwSize * 2.8); -#endif - - m_audioLPW->setImageSize(lpwSize); - m_audioLPW->setBigImageSize(bigLpwSize); - toolbar->addWidget(m_audioLPW); - - // Pitch (f0) - QLabel *spacer = new QLabel; // blank - spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); - toolbar->addWidget(spacer); - - m_showPitch = toolbar->addAction(il.load("values"), tr("Show Pitch Track")); - m_showPitch->setCheckable(true); - connect(m_showPitch, SIGNAL(triggered()), this, SLOT(showPitchToggled())); - connect(this, SIGNAL(canPlay(bool)), m_showPitch, SLOT(setEnabled(bool))); - - if (m_withSonification) { - m_playPitch = toolbar->addAction(il.load("speaker"), tr("Play Pitch Track")); - m_playPitch->setCheckable(true); - connect(m_playPitch, SIGNAL(triggered()), this, SLOT(playPitchToggled())); - connect(this, SIGNAL(canPlayPitch(bool)), m_playPitch, SLOT(setEnabled(bool))); - - m_pitchLPW->setImageSize(lpwSize); - m_pitchLPW->setBigImageSize(bigLpwSize); - toolbar->addWidget(m_pitchLPW); - } else { - m_playPitch = 0; - } - - // Notes - spacer = new QLabel; - spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); - toolbar->addWidget(spacer); - - m_showNotes = toolbar->addAction(il.load("notes"), tr("Show Notes")); - m_showNotes->setCheckable(true); - connect(m_showNotes, SIGNAL(triggered()), this, SLOT(showNotesToggled())); - connect(this, SIGNAL(canPlay(bool)), m_showNotes, SLOT(setEnabled(bool))); - - if (m_withSonification) { - m_playNotes = toolbar->addAction(il.load("speaker"), tr("Play Notes")); - m_playNotes->setCheckable(true); - connect(m_playNotes, SIGNAL(triggered()), this, SLOT(playNotesToggled())); - connect(this, SIGNAL(canPlayNotes(bool)), m_playNotes, SLOT(setEnabled(bool))); - - m_notesLPW->setImageSize(lpwSize); - m_notesLPW->setBigImageSize(bigLpwSize); - toolbar->addWidget(m_notesLPW); - } else { - m_playNotes = 0; - } - - // Spectrogram - spacer = new QLabel; - spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); - toolbar->addWidget(spacer); - - if (!m_withSpectrogram) - { - m_showSpect = new QAction(tr("Show Spectrogram"), this); - } else { - m_showSpect = toolbar->addAction(il.load("spectrogram"), tr("Show Spectrogram")); - } - m_showSpect->setCheckable(true); - connect(m_showSpect, SIGNAL(triggered()), this, SLOT(showSpectToggled())); - connect(this, SIGNAL(canPlay(bool)), m_showSpect, SLOT(setEnabled(bool))); - - Pane::registerShortcuts(*m_keyReference); - - updateLayerStatuses(); -} - - -void -MainWindow::moveOneNoteRight() -{ - // cerr << "MainWindow::moveOneNoteRight" << endl; - moveByOneNote(true, false); -} - -void -MainWindow::moveOneNoteLeft() -{ - // cerr << "MainWindow::moveOneNoteLeft" << endl; - moveByOneNote(false, false); -} - -void -MainWindow::selectOneNoteRight() -{ - moveByOneNote(true, true); -} - -void -MainWindow::selectOneNoteLeft() -{ - moveByOneNote(false, true); -} - - -void -MainWindow::moveByOneNote(bool right, bool doSelect) -{ - sv_frame_t frame = m_viewManager->getPlaybackFrame(); - cerr << "MainWindow::moveByOneNote startframe: " << frame << endl; - - bool isAtSelectionBoundary = false; - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - if (!selections.empty()) { - Selection sel = *selections.begin(); - isAtSelectionBoundary = (frame == sel.getStartFrame()) || (frame == sel.getEndFrame()); - } - if (!doSelect || !isAtSelectionBoundary) { - m_selectionAnchor = frame; - } - - Layer *layer = m_analyser->getLayer(Analyser::Notes); - if (!layer) return; - - auto model = ModelById::getAs(layer->getModel()); - if (!model) return; - - //!!! This seems like a strange and inefficient way to do this - - //!!! there is almost certainly a better way making use of - //!!! EventSeries api - - EventVector points = model->getAllEvents(); - if (points.empty()) return; - - EventVector::iterator i = points.begin(); - std::set snapFrames; - snapFrames.insert(0); - while (i != points.end()) { - snapFrames.insert(i->getFrame()); - snapFrames.insert(i->getFrame() + i->getDuration() + 1); - ++i; - } - std::set::iterator i2; - if (snapFrames.find(frame) == snapFrames.end()) { - // we're not on an existing snap point, so go to previous - snapFrames.insert(frame); - } - i2 = snapFrames.find(frame); - if (right) { - i2++; - if (i2 == snapFrames.end()) i2--; - } else { - if (i2 != snapFrames.begin()) i2--; - } - frame = *i2; - m_viewManager->setPlaybackFrame(frame); - if (doSelect) { - Selection sel; - if (frame > m_selectionAnchor) { - sel = Selection(m_selectionAnchor, frame); - } else { - sel = Selection(frame, m_selectionAnchor); - } - m_viewManager->setSelection(sel); - } - cerr << "MainWindow::moveByOneNote endframe: " << frame << endl; -} - -void -MainWindow::toolNavigateSelected() -{ - m_viewManager->setToolMode(ViewManager::NavigateMode); - m_intelligentActionOn = true; -} - -void -MainWindow::toolEditSelected() -{ - cerr << "MainWindow::toolEditSelected" << endl; - m_viewManager->setToolMode(ViewManager::NoteEditMode); - m_intelligentActionOn = true; - m_analyser->setIntelligentActions(m_intelligentActionOn); -} - -void -MainWindow::toolFreeEditSelected() -{ - m_viewManager->setToolMode(ViewManager::NoteEditMode); - m_intelligentActionOn = false; - m_analyser->setIntelligentActions(m_intelligentActionOn); -} - -void -MainWindow::updateMenuStates() -{ - MainWindowBase::updateMenuStates(); - - Pane *currentPane = 0; - Layer *currentLayer = 0; - - if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); - if (currentPane) currentLayer = currentPane->getSelectedLayer(); - - bool haveMainModel = - (getMainModel() != 0); - bool havePlayTarget = - (m_playTarget != 0 || m_audioIO != 0); - bool haveCurrentPane = - (currentPane != 0); - bool haveCurrentLayer = - (haveCurrentPane && - (currentLayer != 0)); - bool haveSelection = - (m_viewManager && - !m_viewManager->getSelections().empty()); - bool haveCurrentTimeInstantsLayer = - (haveCurrentLayer && - qobject_cast(currentLayer)); - bool haveCurrentTimeValueLayer = - (haveCurrentLayer && - qobject_cast(currentLayer)); - bool pitchCandidatesVisible = - m_analyser->arePitchCandidatesShown(); - - emit canChangePlaybackSpeed(true); - int v = m_playSpeed->value(); - emit canSpeedUpPlayback(v < m_playSpeed->maximum()); - emit canSlowDownPlayback(v > m_playSpeed->minimum()); - - bool haveWaveform = - m_analyser->isVisible(Analyser::Audio) && - m_analyser->getLayer(Analyser::Audio); - - bool havePitchTrack = - m_analyser->isVisible(Analyser::PitchTrack) && - m_analyser->getLayer(Analyser::PitchTrack); - - bool haveNotes = - m_analyser->isVisible(Analyser::Notes) && - m_analyser->getLayer(Analyser::Notes); - - emit canExportPitchTrack(havePitchTrack); - emit canExportNotes(haveNotes); - emit canSnapNotes(haveSelection && haveNotes); - - emit canPlayWaveform(haveWaveform && haveMainModel && havePlayTarget); - emit canPlayPitch(havePitchTrack && haveMainModel && havePlayTarget); - emit canPlayNotes(haveNotes && haveMainModel && havePlayTarget); - - if (pitchCandidatesVisible) { - m_showCandidatesAction->setText(tr("Hide Pitch Candidates")); - m_showCandidatesAction->setStatusTip(tr("Remove the display of alternate pitch candidates for the selected region")); - } else { - m_showCandidatesAction->setText(tr("Show Pitch Candidates")); - m_showCandidatesAction->setStatusTip(tr("Show alternate pitch candidates for the selected region")); - } - - if (m_ffwdAction && m_rwdAction) { - if (haveCurrentTimeInstantsLayer) { - m_ffwdAction->setText(tr("Fast Forward to Next Instant")); - m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); - m_rwdAction->setText(tr("Rewind to Previous Instant")); - m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); - } else if (haveCurrentTimeValueLayer) { - m_ffwdAction->setText(tr("Fast Forward to Next Point")); - m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); - m_rwdAction->setText(tr("Rewind to Previous Point")); - m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); - } else { - m_ffwdAction->setText(tr("Fast Forward")); - m_ffwdAction->setStatusTip(tr("Fast forward")); - m_rwdAction->setText(tr("Rewind")); - m_rwdAction->setStatusTip(tr("Rewind")); - } - } -} - -void -MainWindow::showAudioToggled() -{ - m_analyser->toggleVisible(Analyser::Audio); - - QSettings settings; - settings.beginGroup("MainWindow"); - - bool playOn = false; - if (m_analyser->isVisible(Analyser::Audio)) { - // just switched layer on; check whether playback was also on previously - playOn = settings.value("playaudiowas", true).toBool(); - } else { - settings.setValue("playaudiowas", m_playAudio->isChecked()); - } - m_analyser->setAudible(Analyser::Audio, playOn); - - settings.endGroup(); - - updateMenuStates(); - updateLayerStatuses(); -} - -void -MainWindow::showPitchToggled() -{ - m_analyser->toggleVisible(Analyser::PitchTrack); - - QSettings settings; - settings.beginGroup("MainWindow"); - - bool playOn = false; - if (m_analyser->isVisible(Analyser::PitchTrack)) { - // just switched layer on; check whether playback was also on previously - playOn = settings.value("playpitchwas", true).toBool(); - } else { - settings.setValue("playpitchwas", m_playPitch->isChecked()); - } - m_analyser->setAudible(Analyser::PitchTrack, playOn); - - settings.endGroup(); - - updateMenuStates(); - updateLayerStatuses(); -} - -void -MainWindow::showSpectToggled() -{ - m_analyser->toggleVisible(Analyser::Spectrogram); -} - -void -MainWindow::showNotesToggled() -{ - m_analyser->toggleVisible(Analyser::Notes); - - QSettings settings; - settings.beginGroup("MainWindow"); - - bool playOn = false; - if (m_analyser->isVisible(Analyser::Notes)) { - // just switched layer on; check whether playback was also on previously - playOn = settings.value("playnoteswas", true).toBool(); - } else { - settings.setValue("playnoteswas", m_playNotes->isChecked()); - } - m_analyser->setAudible(Analyser::Notes, playOn); - - settings.endGroup(); - - updateMenuStates(); - updateLayerStatuses(); -} - -void -MainWindow::playAudioToggled() -{ - m_analyser->toggleAudible(Analyser::Audio); - updateLayerStatuses(); -} - -void -MainWindow::playPitchToggled() -{ - m_analyser->toggleAudible(Analyser::PitchTrack); - updateLayerStatuses(); -} - -void -MainWindow::playNotesToggled() -{ - m_analyser->toggleAudible(Analyser::Notes); - updateLayerStatuses(); -} - -void -MainWindow::updateLayerStatuses() -{ - m_showAudio->setChecked(m_analyser->isVisible(Analyser::Audio)); - m_playAudio->setChecked(m_analyser->isAudible(Analyser::Audio)); - m_audioLPW->setEnabled(m_analyser->isAudible(Analyser::Audio)); - m_audioLPW->setLevel(m_analyser->getGain(Analyser::Audio)); - m_audioLPW->setPan(m_analyser->getPan(Analyser::Audio)); - - m_showPitch->setChecked(m_analyser->isVisible(Analyser::PitchTrack)); - m_playPitch->setChecked(m_analyser->isAudible(Analyser::PitchTrack)); - m_pitchLPW->setEnabled(m_analyser->isAudible(Analyser::PitchTrack)); - m_pitchLPW->setLevel(m_analyser->getGain(Analyser::PitchTrack)); - m_pitchLPW->setPan(m_analyser->getPan(Analyser::PitchTrack)); - - m_showNotes->setChecked(m_analyser->isVisible(Analyser::Notes)); - m_playNotes->setChecked(m_analyser->isAudible(Analyser::Notes)); - m_notesLPW->setEnabled(m_analyser->isAudible(Analyser::Notes)); - m_notesLPW->setLevel(m_analyser->getGain(Analyser::Notes)); - m_notesLPW->setPan(m_analyser->getPan(Analyser::Notes)); - - m_showSpect->setChecked(m_analyser->isVisible(Analyser::Spectrogram)); -} - -void -MainWindow::editDisplayExtents() -{ - double min, max; - double vmin = 0; - double vmax = getMainModel()->getSampleRate() /2; - - if (!m_analyser->getDisplayFrequencyExtents(min, max)) { - //!!! - return; - } - - RangeInputDialog dialog(tr("Set frequency range"), - tr("Enter new frequency range, from %1 to %2 Hz.\nThese values will be rounded to the nearest spectrogram bin.") - .arg(vmin).arg(vmax), - "Hz", float(vmin), float(vmax), this); - dialog.setRange(float(min), float(max)); - - if (dialog.exec() == QDialog::Accepted) { - float fmin, fmax; - dialog.getRange(fmin, fmax); - min = fmin; - max = fmax; - if (min > max) { - double tmp = max; - max = min; - min = tmp; - } - m_analyser->setDisplayFrequencyExtents(min, max); - } -} - -void -MainWindow::updateDescriptionLabel() -{ - // Nothing, we don't have one -} - -void -MainWindow::documentModified() -{ - MainWindowBase::documentModified(); -} - -void -MainWindow::documentRestored() -{ - MainWindowBase::documentRestored(); -} - -void -MainWindow::newSession() -{ - if (!checkSaveModified()) return; - - closeSession(); - createDocument(); - m_document->setAutoAlignment(true); - - Pane *pane = m_paneStack->addPane(); - pane->setPlaybackFollow(PlaybackScrollPage); - - m_viewManager->setGlobalCentreFrame - (pane->getFrameForX(width() / 2)); - - connect(pane, SIGNAL(contextHelpChanged(const QString &)), - this, SLOT(contextHelpChanged(const QString &))); - -// Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); -// m_document->addLayerToView(pane, waveform); - - m_overview->registerView(pane); - - CommandHistory::getInstance()->clear(); - CommandHistory::getInstance()->documentSaved(); - documentRestored(); - updateMenuStates(); -} - -void -MainWindow::documentReplaced() -{ - if (m_document) { - connect(m_document, SIGNAL(activity(QString)), - m_activityLog, SLOT(activityHappened(QString))); - } -} - -void -MainWindow::closeSession() -{ - if (!checkSaveModified()) return; - - m_analyser->fileClosed(); - - while (m_paneStack->getPaneCount() > 0) { - - Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); - - while (pane->getLayerCount() > 0) { - m_document->removeLayerFromView - (pane, pane->getLayer(pane->getLayerCount() - 1)); - } - - m_overview->unregisterView(pane); - m_paneStack->deletePane(pane); - } - - while (m_paneStack->getHiddenPaneCount() > 0) { - - Pane *pane = m_paneStack->getHiddenPane - (m_paneStack->getHiddenPaneCount() - 1); - - while (pane->getLayerCount() > 0) { - m_document->removeLayerFromView - (pane, pane->getLayer(pane->getLayerCount() - 1)); - } - - m_overview->unregisterView(pane); - m_paneStack->deletePane(pane); - } - - delete m_document; - m_document = 0; - m_viewManager->clearSelections(); - m_timeRulerLayer = 0; // document owned this - - m_sessionFile = ""; - - CommandHistory::getInstance()->clear(); - CommandHistory::getInstance()->documentSaved(); - documentRestored(); -} - -void -MainWindow::openFile() -{ - QString orig = m_audioFile; - if (orig == "") orig = "."; - else orig = QFileInfo(orig).absoluteDir().canonicalPath(); - - QString path = getOpenFileName(FileFinder::AnyFile); - - if (path.isEmpty()) return; - - FileOpenStatus status = openPath(path, ReplaceSession); - - if (status == FileOpenFailed) { - QMessageBox::critical(this, tr("Failed to open file"), - tr("File open failed

File \"%1\" could not be opened").arg(path)); - } else if (status == FileOpenWrongMode) { - QMessageBox::critical(this, tr("Failed to open file"), - tr("Audio required

Please load at least one audio file before importing annotation data")); - } -} - -void -MainWindow::openLocation() -{ - QSettings settings; - settings.beginGroup("MainWindow"); - QString lastLocation = settings.value("lastremote", "").toString(); - - bool ok = false; - QString text = QInputDialog::getText - (this, tr("Open Location"), - tr("Please enter the URL of the location to open:"), - QLineEdit::Normal, lastLocation, &ok); - - if (!ok) return; - - settings.setValue("lastremote", text); - - if (text.isEmpty()) return; - - FileOpenStatus status = openPath(text, ReplaceSession); - - if (status == FileOpenFailed) { - QMessageBox::critical(this, tr("Failed to open location"), - tr("Open failed

URL \"%1\" could not be opened").arg(text)); - } else if (status == FileOpenWrongMode) { - QMessageBox::critical(this, tr("Failed to open location"), - tr("Audio required

Please load at least one audio file before importing annotation data")); - } -} - -void -MainWindow::openRecentFile() -{ - QObject *obj = sender(); - QAction *action = qobject_cast(obj); - - if (!action) { - cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" - << endl; - return; - } - - QString path = action->text(); - if (path == "") return; - - FileOpenStatus status = openPath(path, ReplaceSession); - - if (status == FileOpenFailed) { - QMessageBox::critical(this, tr("Failed to open location"), - tr("Open failed

File or URL \"%1\" could not be opened").arg(path)); - } else if (status == FileOpenWrongMode) { - QMessageBox::critical(this, tr("Failed to open location"), - tr("Audio required

Please load at least one audio file before importing annotation data")); - } -} - -void -MainWindow::paneAdded(Pane *pane) -{ - pane->setPlaybackFollow(PlaybackScrollPage); - m_paneStack->sizePanesEqually(); - if (m_overview) m_overview->registerView(pane); -} - -void -MainWindow::paneHidden(Pane *pane) -{ - if (m_overview) m_overview->unregisterView(pane); -} - -void -MainWindow::paneAboutToBeDeleted(Pane *pane) -{ - if (m_overview) m_overview->unregisterView(pane); -} - -void -MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) -{ - if (pane) m_paneStack->setCurrentPane(pane); - - for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { - - FileOpenStatus status = openPath(*i, ReplaceSession); - - if (status == FileOpenFailed) { - QMessageBox::critical(this, tr("Failed to open dropped URL"), - tr("Open failed

Dropped URL \"%1\" could not be opened").arg(*i)); - } else if (status == FileOpenWrongMode) { - QMessageBox::critical(this, tr("Failed to open dropped URL"), - tr("Audio required

Please load at least one audio file before importing annotation data")); - } - } -} - -void -MainWindow::paneDropAccepted(Pane *pane, QString text) -{ - if (pane) m_paneStack->setCurrentPane(pane); - - QUrl testUrl(text); - if (testUrl.scheme() == "file" || - testUrl.scheme() == "http" || - testUrl.scheme() == "ftp") { - QStringList list; - list.push_back(text); - paneDropAccepted(pane, list); - return; - } - - //!!! open as text -- but by importing as if a CSV, or just adding - //to a text layer? -} - -void -MainWindow::closeEvent(QCloseEvent *e) -{ -// cerr << "MainWindow::closeEvent" << endl; - - if (m_openingAudioFile) { -// cerr << "Busy - ignoring close event" << endl; - e->ignore(); - return; - } - - if (!m_abandoning && !checkSaveModified()) { -// cerr << "Ignoring close event" << endl; - e->ignore(); - return; - } - - QSettings settings; - settings.beginGroup("MainWindow"); - settings.setValue("size", size()); - settings.setValue("position", pos()); - settings.endGroup(); - - delete m_keyReference; - m_keyReference = 0; - - closeSession(); - - e->accept(); - return; -} - -bool -MainWindow::commitData(bool mayAskUser) -{ - if (mayAskUser) { - bool rv = checkSaveModified(); - return rv; - } else { - if (!m_documentModified) return true; - - // If we can't check with the user first, then we can't save - // to the original session file (even if we have it) -- have - // to use a temporary file - - QString svDirBase = ".sv1"; - QString svDir = QDir::home().filePath(svDirBase); - - if (!QFileInfo(svDir).exists()) { - if (!QDir::home().mkdir(svDirBase)) return false; - } else { - if (!QFileInfo(svDir).isDir()) return false; - } - - // This name doesn't have to be unguessable -#ifndef _WIN32 - QString fname = QString("tmp-%1-%2.sv") - .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) - .arg(QProcess().pid()); -#else - QString fname = QString("tmp-%1.sv") - .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); -#endif - QString fpath = QDir(svDir).filePath(fname); - if (saveSessionFile(fpath)) { - m_recentFiles.addFile(fpath); - return true; - } else { - return false; - } - } -} - -bool -MainWindow::checkSaveModified() -{ - // Called before some destructive operation (e.g. new session, - // exit program). Return true if we can safely proceed, false to - // cancel. - - if (!m_documentModified) return true; - - int button = - QMessageBox::warning(this, - tr("Session modified"), - tr("The current session has been modified.\nDo you want to save it?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Yes); - - if (button == QMessageBox::Yes) { - saveSession(); - if (m_documentModified) { // save failed -- don't proceed! - return false; - } else { - return true; // saved, so it's safe to continue now - } - } else if (button == QMessageBox::No) { - m_documentModified = false; // so we know to abandon it - return true; - } - - // else cancel - return false; -} - -bool -MainWindow::waitForInitialAnalysis() -{ - // Called before saving a session. We can't safely save while the - // initial analysis is happening, because then we end up with an - // incomplete session on reload. There are certainly theoretically - // better ways to handle this... - - QSettings settings; - settings.beginGroup("Analyser"); - bool autoAnalyse = settings.value("auto-analysis", true).toBool(); - settings.endGroup(); - - if (!autoAnalyse) { - return true; - } - - if (!m_analyser || m_analyser->getInitialAnalysisCompletion() >= 100) { - return true; - } - - QMessageBox mb(QMessageBox::Information, - tr("Waiting for analysis"), - tr("Waiting for initial analysis to finish before loading or saving..."), - QMessageBox::Cancel, - this); - - connect(m_analyser, SIGNAL(initialAnalysisCompleted()), - &mb, SLOT(accept())); - - if (mb.exec() == QDialog::Accepted) { - return true; - } else { - return false; - } -} - -void -MainWindow::saveSession() -{ - // We do not want to save mid-analysis regions -- that would cause - // confusion on reloading - m_analyser->clearReAnalysis(); - clearSelection(); - - if (m_sessionFile != "") { - if (!saveSessionFile(m_sessionFile)) { - QMessageBox::critical - (this, tr("Failed to save file"), - tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); - } else { - CommandHistory::getInstance()->documentSaved(); - documentRestored(); - } - } else { - saveSessionAs(); - } -} - -void -MainWindow::saveSessionInAudioPath() -{ - if (m_audioFile == "") return; - - if (!waitForInitialAnalysis()) return; - - // We do not want to save mid-analysis regions -- that would cause - // confusion on reloading - m_analyser->clearReAnalysis(); - clearSelection(); - - QString filepath = QFileInfo(m_audioFile).absoluteDir().canonicalPath(); - QString basename = QFileInfo(m_audioFile).completeBaseName(); - - QString path = QDir(filepath).filePath(basename + ".ton"); - - cerr << path << endl; - - // We don't want to overwrite an existing .ton file unless we put - // it there in the first place - bool shouldVerify = true; - if (m_sessionFile == path) { - shouldVerify = false; - } - - if (shouldVerify && QFileInfo(path).exists()) { - if (QMessageBox::question(0, tr("File exists"), - tr("File exists

The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), - QMessageBox::Ok, - QMessageBox::Cancel) != QMessageBox::Ok) { - return; - } - } - - if (!waitForInitialAnalysis()) { - QMessageBox::warning(this, tr("File not saved"), - tr("Wait cancelled: the session has not been saved.")); - } - - if (!saveSessionFile(path)) { - QMessageBox::critical(this, tr("Failed to save file"), - tr("Session file \"%1\" could not be saved.").arg(path)); - } else { - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(QFileInfo(path).fileName())); - m_sessionFile = path; - CommandHistory::getInstance()->documentSaved(); - documentRestored(); - m_recentFiles.addFile(path); - } -} - -void -MainWindow::saveSessionAs() -{ - // We do not want to save mid-analysis regions -- that would cause - // confusion on reloading - m_analyser->clearReAnalysis(); - clearSelection(); - - QString path = getSaveFileName(FileFinder::SessionFile); - - if (path == "") { - return; - } - - if (!waitForInitialAnalysis()) { - QMessageBox::warning(this, tr("File not saved"), - tr("Wait cancelled: the session has not been saved.")); - return; - } - - if (!saveSessionFile(path)) { - QMessageBox::critical(this, tr("Failed to save file"), - tr("Session file \"%1\" could not be saved.").arg(path)); - } else { - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(QFileInfo(path).fileName())); - m_sessionFile = path; - CommandHistory::getInstance()->documentSaved(); - documentRestored(); - m_recentFiles.addFile(path); - } -} - -QString -MainWindow::exportToSVL(QString path, Layer *layer) -{ - auto model = ModelById::get(layer->getModel()); - if (!model) return "Internal error: No model in layer"; - - QFile file(path); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - return tr("Failed to open file %1 for writing").arg(path); - } else { - QTextStream out(&file); - out << "\n" - << "\n" - << "\n" - << " \n"; - - model->toXml(out, " "); - - out << " \n" - << " \n"; - - layer->toXml(out, " "); - - out << " \n" - << "\n"; - - return ""; - } -} - -void -MainWindow::importPitchLayer() -{ - QString path = getOpenFileName(FileFinder::LayerFileNoMidiNonSV); - if (path == "") return; - - FileOpenStatus status = importPitchLayer(path); - - if (status == FileOpenFailed) { - emit hideSplash(); - QMessageBox::critical(this, tr("Failed to open file"), - tr("File open failed

Layer file %1 could not be opened.").arg(path)); - return; - } else if (status == FileOpenWrongMode) { - emit hideSplash(); - QMessageBox::critical(this, tr("Failed to open file"), - tr("Audio required

Unable to load layer data from \"%1\" without an audio file.
Please load at least one audio file before importing annotations.").arg(path)); - } -} - -MainWindow::FileOpenStatus -MainWindow::importPitchLayer(FileSource source) -{ - if (!source.isAvailable()) return FileOpenFailed; - source.waitForData(); - - if (!waitForInitialAnalysis()) return FileOpenCancelled; - - QString path = source.getLocalFilename(); - - RDFImporter::RDFDocumentType rdfType = - RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); - - if (rdfType != RDFImporter::NotRDF) { - - //!!! - return FileOpenFailed; - - } else if (source.getExtension().toLower() == "svl" || - (source.getExtension().toLower() == "xml" && - (SVFileReader::identifyXmlFile(source.getLocalFilename()) - == SVFileReader::SVLayerFile))) { - - //!!! - return FileOpenFailed; - - } else { - - try { - - CSVFormat format(path); - format.setSampleRate(getMainModel()->getSampleRate()); - - if (format.getModelType() != CSVFormat::TwoDimensionalModel) { - //!!! error report - return FileOpenFailed; - } - - Model *model = DataFileReaderFactory::loadCSV - (path, format, getMainModel()->getSampleRate()); - - if (model) { - - SVDEBUG << "MainWindow::importPitchLayer: Have model" << endl; - - ModelId modelId = ModelById::add - (std::shared_ptr(model)); - - CommandHistory::getInstance()->startCompoundOperation - (tr("Import Pitch Track"), true); - - Layer *newLayer = m_document->createImportedLayer(modelId); - - m_analyser->takePitchTrackFrom(newLayer); - - m_document->deleteLayer(newLayer); - - CommandHistory::getInstance()->endCompoundOperation(); - - if (!source.isRemote()) { - registerLastOpenedFilePath - (FileFinder::LayerFile, - path); // for file dialog - } - - return FileOpenSucceeded; - } - } catch (DataFileReaderFactory::Exception e) { - if (e == DataFileReaderFactory::ImportCancelled) { - return FileOpenCancelled; - } - } - } - - return FileOpenFailed; -} - -void -MainWindow::exportPitchLayer() -{ - Layer *layer = m_analyser->getLayer(Analyser::PitchTrack); - if (!layer) return; - - auto model = ModelById::getAs(layer->getModel()); - if (!model) return; - - FileFinder::FileType type = FileFinder::LayerFileNoMidiNonSV; - - QString path = getSaveFileName(type); - - if (path == "") return; - - if (!waitForInitialAnalysis()) return; - - if (QFileInfo(path).suffix() == "") path += ".svl"; - - QString suffix = QFileInfo(path).suffix().toLower(); - - QString error; - - if (suffix == "xml" || suffix == "svl") { - - error = exportToSVL(path, layer); - - } else if (suffix == "ttl" || suffix == "n3") { - - RDFExporter exporter(path, model.get()); - exporter.write(); - if (!exporter.isOK()) { - error = exporter.getError(); - } - - } else { - - DataExportOptions options = DataExportFillGaps; - - CSVFileWriter writer(path, model.get(), - ((suffix == "csv") ? "," : "\t"), - options); - writer.write(); - - if (!writer.isOK()) { - error = writer.getError(); - } - } - - if (error != "") { - QMessageBox::critical(this, tr("Failed to write file"), error); - } else { - emit activity(tr("Export layer to \"%1\"").arg(path)); - } -} - -void -MainWindow::exportNoteLayer() -{ - Layer *layer = m_analyser->getLayer(Analyser::Notes); - if (!layer) return; - - auto model = ModelById::getAs(layer->getModel()); - if (!model) return; - - FileFinder::FileType type = FileFinder::LayerFileNonSV; - - QString path = getSaveFileName(type); - - if (path == "") return; - - if (QFileInfo(path).suffix() == "") path += ".svl"; - - QString suffix = QFileInfo(path).suffix().toLower(); - - QString error; - - if (suffix == "xml" || suffix == "svl") { - - error = exportToSVL(path, layer); - - } else if (suffix == "mid" || suffix == "midi") { - - MIDIFileWriter writer(path, model.get(), model->getSampleRate()); - writer.write(); - if (!writer.isOK()) { - error = writer.getError(); - } - - } else if (suffix == "ttl" || suffix == "n3") { - - RDFExporter exporter(path, model.get()); - exporter.write(); - if (!exporter.isOK()) { - error = exporter.getError(); - } - - } else { - - DataExportOptions options = DataExportOmitLevels; - - CSVFileWriter writer(path, model.get(), - ((suffix == "csv") ? "," : "\t"), - options); - writer.write(); - - if (!writer.isOK()) { - error = writer.getError(); - } - } - - if (error != "") { - QMessageBox::critical(this, tr("Failed to write file"), error); - } else { - emit activity(tr("Export layer to \"%1\"").arg(path)); - } -} - -void -MainWindow::browseRecordedAudio() -{ - if (!m_recordTarget) return; - - QString path = RecordDirectory::getRecordContainerDirectory(); - if (path == "") path = RecordDirectory::getRecordDirectory(); - if (path == "") return; - - openLocalFolder(path); -} - -void -MainWindow::doubleClickSelectInvoked(sv_frame_t frame) -{ - sv_frame_t f0, f1; - m_analyser->getEnclosingSelectionScope(frame, f0, f1); - - cerr << "MainWindow::doubleClickSelectInvoked(" << frame << "): [" << f0 << "," << f1 << "]" << endl; - - Selection sel(f0, f1); - m_viewManager->setSelection(sel); -} - -void -MainWindow::abandonSelection() -{ - // Named abandonSelection rather than clearSelection to indicate - // that this is an active operation -- it restores the original - // content of the pitch track in the selected region rather than - // simply un-selecting. - - cerr << "MainWindow::abandonSelection()" << endl; - - CommandHistory::getInstance()->startCompoundOperation(tr("Abandon Selection"), true); - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - if (!selections.empty()) { - Selection sel = *selections.begin(); - m_analyser->abandonReAnalysis(sel); - auxSnapNotes(sel); - } - - MainWindowBase::clearSelection(); - - CommandHistory::getInstance()->endCompoundOperation(); -} - -void -MainWindow::selectionChangedByUser() -{ - if (!m_document) { - // we're exiting, most likely - return; - } - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - cerr << "MainWindow::selectionChangedByUser" << endl; - - m_analyser->showPitchCandidates(m_pendingConstraint.isConstrained()); - - if (!selections.empty()) { - Selection sel = *selections.begin(); - cerr << "MainWindow::selectionChangedByUser: have selection" << endl; - QString error = m_analyser->reAnalyseSelection - (sel, m_pendingConstraint); - if (error != "") { - QMessageBox::critical - (this, tr("Failed to analyse selection"), - tr("Analysis failed

%2

").arg(error)); - } - } - - m_pendingConstraint = Analyser::FrequencyRange(); -} - -void -MainWindow::regionOutlined(QRect r) -{ - cerr << "MainWindow::regionOutlined(" << r.x() << "," << r.y() << "," << r.width() << "," << r.height() << ")" << endl; - - Pane *pane = qobject_cast(sender()); - if (!pane) { - cerr << "MainWindow::regionOutlined: not sent by pane, ignoring" << endl; - return; - } - - if (!m_analyser) { - cerr << "MainWindow::regionOutlined: no analyser, ignoring" << endl; - return; - } - - SpectrogramLayer *spectrogram = qobject_cast - (m_analyser->getLayer(Analyser::Spectrogram)); - if (!spectrogram) { - cerr << "MainWindow::regionOutlined: no spectrogram layer, ignoring" << endl; - return; - } - - sv_frame_t f0 = pane->getFrameForX(r.x()); - sv_frame_t f1 = pane->getFrameForX(r.x() + r.width()); - - double v0 = spectrogram->getFrequencyForY(pane, r.y() + r.height()); - double v1 = spectrogram->getFrequencyForY(pane, r.y()); - - cerr << "MainWindow::regionOutlined: frame " << f0 << " -> " << f1 - << ", frequency " << v0 << " -> " << v1 << endl; - - m_pendingConstraint = Analyser::FrequencyRange(v0, v1); - - Selection sel(f0, f1); - m_viewManager->setSelection(sel); -} - -void -MainWindow::clearPitches() -{ - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - CommandHistory::getInstance()->startCompoundOperation(tr("Clear Pitches"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - m_analyser->deletePitches(*k); - auxSnapNotes(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); -} - -void -MainWindow::octaveShift(bool up) -{ - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - CommandHistory::getInstance()->startCompoundOperation - (up ? tr("Choose Higher Octave") : tr("Choose Lower Octave"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - - m_analyser->shiftOctave(*k, up); - auxSnapNotes(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); -} - -void -MainWindow::togglePitchCandidates() -{ - CommandHistory::getInstance()->startCompoundOperation(tr("Toggle Pitch Candidates"), true); - - m_analyser->showPitchCandidates(!m_analyser->arePitchCandidatesShown()); - - CommandHistory::getInstance()->endCompoundOperation(); - - updateMenuStates(); -} - -void -MainWindow::switchPitchUp() -{ - if (m_analyser->arePitchCandidatesShown()) { - if (m_analyser->haveHigherPitchCandidate()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Choose Higher Pitch Candidate"), true); - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - m_analyser->switchPitchCandidate(*k, true); - auxSnapNotes(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } - } else { - octaveShift(true); - } -} - -void -MainWindow::switchPitchDown() -{ - if (m_analyser->arePitchCandidatesShown()) { - if (m_analyser->haveLowerPitchCandidate()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Choose Lower Pitch Candidate"), true); - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - m_analyser->switchPitchCandidate(*k, false); - auxSnapNotes(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } - } else { - octaveShift(false); - } -} - -void -MainWindow::snapNotesToPitches() -{ - cerr << "in snapNotesToPitches" << endl; - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - if (!selections.empty()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Snap Notes to Pitches"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - auxSnapNotes(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } -} - -void -MainWindow::auxSnapNotes(Selection s) -{ - cerr << "in auxSnapNotes" << endl; - FlexiNoteLayer *layer = - qobject_cast(m_analyser->getLayer(Analyser::Notes)); - if (!layer) return; - - layer->snapSelectedNotesToPitchTrack(m_analyser->getPane(), s); -} - -void -MainWindow::splitNote() -{ - FlexiNoteLayer *layer = - qobject_cast(m_analyser->getLayer(Analyser::Notes)); - if (!layer) return; - - layer->splitNotesAt(m_analyser->getPane(), m_viewManager->getPlaybackFrame()); -} - -void -MainWindow::mergeNotes() -{ - FlexiNoteLayer *layer = - qobject_cast(m_analyser->getLayer(Analyser::Notes)); - if (!layer) return; - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - if (!selections.empty()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Merge Notes"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - layer->mergeNotes(m_analyser->getPane(), *k, true); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } -} - -void -MainWindow::deleteNotes() -{ - FlexiNoteLayer *layer = - qobject_cast(m_analyser->getLayer(Analyser::Notes)); - if (!layer) return; - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - if (!selections.empty()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Delete Notes"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - layer->deleteSelectionInclusive(*k); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } -} - - -void -MainWindow::formNoteFromSelection() -{ - Pane *pane = m_analyser->getPane(); - Layer *layer0 = m_analyser->getLayer(Analyser::Notes); - auto model = ModelById::getAs(layer0->getModel()); - FlexiNoteLayer *layer = qobject_cast(layer0); - if (!layer || !model) return; - - MultiSelection::SelectionList selections = m_viewManager->getSelections(); - - if (!selections.empty()) { - - CommandHistory::getInstance()->startCompoundOperation - (tr("Form Note from Selection"), true); - - for (MultiSelection::SelectionList::iterator k = selections.begin(); - k != selections.end(); ++k) { - - // Chop existing events at start and end frames; remember - // the first starting pitch, to use as default for new - // note; delete existing events; create new note; ask - // layer to merge, just in order to adapt the note to the - // existing pitch track if possible. This way we should - // handle all the possible cases of existing notes that - // may or may not overlap the start or end times - - sv_frame_t start = k->getStartFrame(); - sv_frame_t end = k->getEndFrame(); - - EventVector existing = - model->getEventsStartingWithin(start, end - start); - - int defaultPitch = 100; - if (!existing.empty()) { - defaultPitch = int(roundf(existing.begin()->getValue())); - } - - layer->splitNotesAt(pane, start); - layer->splitNotesAt(pane, end); - layer->deleteSelection(*k); - - layer->addNoteOn(start, defaultPitch, 100); - layer->addNoteOff(end, defaultPitch); - - layer->mergeNotes(pane, *k, false); - } - - CommandHistory::getInstance()->endCompoundOperation(); - } -} - -void -MainWindow::playSpeedChanged(int position) -{ - PlaySpeedRangeMapper mapper; - - double percent = m_playSpeed->mappedValue(); - double factor = mapper.getFactorForValue(percent); - - int centre = m_playSpeed->defaultValue(); - - // Percentage is shown to 0dp if >100, to 1dp if <100; factor is - // shown to 3sf - - char pcbuf[30]; - char facbuf[30]; - - if (position == centre) { - contextHelpChanged(tr("Playback speed: Normal")); - } else if (position < centre) { - sprintf(pcbuf, "%.1f", percent); - sprintf(facbuf, "%.3g", 1.0 / factor); - contextHelpChanged(tr("Playback speed: %1% (%2x slower)") - .arg(pcbuf) - .arg(facbuf)); - } else { - sprintf(pcbuf, "%.0f", percent); - sprintf(facbuf, "%.3g", factor); - contextHelpChanged(tr("Playback speed: %1% (%2x faster)") - .arg(pcbuf) - .arg(facbuf)); - } - - m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup - - updateMenuStates(); -} - -void -MainWindow::playSharpenToggled() -{ - QSettings settings; - settings.beginGroup("MainWindow"); - settings.setValue("playsharpen", m_playSharpen->isChecked()); - settings.endGroup(); - - playSpeedChanged(m_playSpeed->value()); - // TODO: pitch gain? -} - -void -MainWindow::playMonoToggled() -{ - QSettings settings; - settings.beginGroup("MainWindow"); - settings.setValue("playmono", m_playMono->isChecked()); - settings.endGroup(); - - playSpeedChanged(m_playSpeed->value()); - // TODO: pitch gain? -} - -void -MainWindow::speedUpPlayback() -{ - int value = m_playSpeed->value(); - value = value + m_playSpeed->pageStep(); - if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); - m_playSpeed->setValue(value); -} - -void -MainWindow::slowDownPlayback() -{ - int value = m_playSpeed->value(); - value = value - m_playSpeed->pageStep(); - if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); - m_playSpeed->setValue(value); -} - -void -MainWindow::restoreNormalPlayback() -{ - m_playSpeed->setValue(m_playSpeed->defaultValue()); -} - -void -MainWindow::audioGainChanged(float gain) -{ - double db = AudioLevel::multiplier_to_dB(gain); - cerr << "gain = " << gain << " (" << db << " dB)" << endl; - contextHelpChanged(tr("Audio Gain: %1 dB").arg(db)); - if (gain == 0.f) { - m_analyser->setAudible(Analyser::Audio, false); - } else { - m_analyser->setAudible(Analyser::Audio, true); - m_analyser->setGain(Analyser::Audio, gain); - } - updateMenuStates(); -} - -void -MainWindow::pitchGainChanged(float gain) -{ - double db = AudioLevel::multiplier_to_dB(gain); - cerr << "gain = " << gain << " (" << db << " dB)" << endl; - contextHelpChanged(tr("Pitch Gain: %1 dB").arg(db)); - if (gain == 0.f) { - m_analyser->setAudible(Analyser::PitchTrack, false); - } else { - m_analyser->setAudible(Analyser::PitchTrack, true); - m_analyser->setGain(Analyser::PitchTrack, gain); - } - updateMenuStates(); -} - -void -MainWindow::notesGainChanged(float gain) -{ - double db = AudioLevel::multiplier_to_dB(gain); - cerr << "gain = " << gain << " (" << db << " dB)" << endl; - contextHelpChanged(tr("Notes Gain: %1 dB").arg(db)); - if (gain == 0.f) { - m_analyser->setAudible(Analyser::Notes, false); - } else { - m_analyser->setAudible(Analyser::Notes, true); - m_analyser->setGain(Analyser::Notes, gain); - } - updateMenuStates(); -} - -void -MainWindow::audioPanChanged(float pan) -{ - contextHelpChanged(tr("Audio Pan: %1").arg(pan)); - m_analyser->setPan(Analyser::Audio, pan); - updateMenuStates(); -} - -void -MainWindow::pitchPanChanged(float pan) -{ - contextHelpChanged(tr("Pitch Pan: %1").arg(pan)); - m_analyser->setPan(Analyser::PitchTrack, pan); - updateMenuStates(); -} - -void -MainWindow::notesPanChanged(float pan) -{ - contextHelpChanged(tr("Notes Pan: %1").arg(pan)); - m_analyser->setPan(Analyser::Notes, pan); - updateMenuStates(); -} - -void -MainWindow::updateVisibleRangeDisplay(Pane *p) const -{ - if (!getMainModel() || !p) { - return; - } - - bool haveSelection = false; - sv_frame_t startFrame = 0, endFrame = 0; - - if (m_viewManager && m_viewManager->haveInProgressSelection()) { - - bool exclusive = false; - Selection s = m_viewManager->getInProgressSelection(exclusive); - - if (!s.isEmpty()) { - haveSelection = true; - startFrame = s.getStartFrame(); - endFrame = s.getEndFrame(); - } - } - - if (!haveSelection) { - startFrame = p->getFirstVisibleFrame(); - endFrame = p->getLastVisibleFrame(); - } - - RealTime start = RealTime::frame2RealTime - (startFrame, getMainModel()->getSampleRate()); - - RealTime end = RealTime::frame2RealTime - (endFrame, getMainModel()->getSampleRate()); - - RealTime duration = end - start; - - QString startStr, endStr, durationStr; - startStr = start.toText(true).c_str(); - endStr = end.toText(true).c_str(); - durationStr = duration.toText(true).c_str(); - - if (haveSelection) { - m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") - .arg(startStr).arg(endStr).arg(durationStr); - } else { - m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") - .arg(startStr).arg(endStr).arg(durationStr); - } - - getStatusLabel()->setText(m_myStatusMessage); -} - -void -MainWindow::updatePositionStatusDisplays() const -{ - if (!statusBar()->isVisible()) return; - -} - -void -MainWindow::monitoringLevelsChanged(float left, float right) -{ - m_fader->setPeakLeft(left); - m_fader->setPeakRight(right); -} - -void -MainWindow::sampleRateMismatch(sv_samplerate_t , - sv_samplerate_t , - bool ) -{ - updateDescriptionLabel(); -} - -void -MainWindow::audioOverloadPluginDisabled() -{ - QMessageBox::information - (this, tr("Audio processing overload"), - tr("Overloaded

Audio effects plugin auditioning has been disabled due to a processing overload.")); -} - -void -MainWindow::audioTimeStretchMultiChannelDisabled() -{ - static bool shownOnce = false; - if (shownOnce) return; - QMessageBox::information - (this, tr("Audio processing overload"), - tr("Overloaded

Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); - shownOnce = true; -} - -void -MainWindow::layerRemoved(Layer *layer) -{ - MainWindowBase::layerRemoved(layer); -} - -void -MainWindow::layerInAView(Layer *layer, bool inAView) -{ - MainWindowBase::layerInAView(layer, inAView); -} - -void -MainWindow::modelAdded(ModelId model) -{ - MainWindowBase::modelAdded(model); - auto dtvm = ModelById::getAs(model); - if (dtvm) { - cerr << "A dense time-value model (such as an audio file) has been loaded" << endl; - } -} - -void -MainWindow::mainModelChanged(ModelId model) -{ - m_panLayer->setModel(model); - - MainWindowBase::mainModelChanged(model); - - if (m_playTarget || m_audioIO) { - connect(m_fader, SIGNAL(valueChanged(float)), - this, SLOT(mainModelGainChanged(float))); - } -} - -void -MainWindow::mainModelGainChanged(float gain) -{ - if (m_playTarget) { - m_playTarget->setOutputGain(gain); - } else if (m_audioIO) { - m_audioIO->setOutputGain(gain); - } -} - -void -MainWindow::analyseNow() -{ - cerr << "analyseNow called" << endl; - if (!m_analyser) return; - - CommandHistory::getInstance()->startCompoundOperation - (tr("Analyse Audio"), true); - - QString error = m_analyser->analyseExistingFile(); - - CommandHistory::getInstance()->endCompoundOperation(); - - if (error != "") { - QMessageBox::warning - (this, - tr("Failed to analyse audio"), - tr("Analysis failed

%1

").arg(error), - QMessageBox::Ok); - } -} - -void -MainWindow::analyseNewMainModel() -{ - auto model = getMainModel(); - - cerr << "MainWindow::analyseNewMainModel: main model is " << model << endl; - - cerr << "(document is " << m_document << ", it says main model is " << m_document->getMainModel() << ")" << endl; - - if (!model) { - cerr << "no main model!" << endl; - return; - } - - if (!m_paneStack) { - cerr << "no pane stack!" << endl; - return; - } - - int pc = m_paneStack->getPaneCount(); - Pane *pane = 0; - Pane *selectionStrip = 0; - - if (pc < 2) { - pane = m_paneStack->addPane(); - selectionStrip = m_paneStack->addPane(); - m_document->addLayerToView - (selectionStrip, - m_document->createMainModelLayer(LayerFactory::TimeRuler)); - } else { - pane = m_paneStack->getPane(0); - selectionStrip = m_paneStack->getPane(1); - } - - pane->setPlaybackFollow(PlaybackScrollPage); - - if (selectionStrip) { - selectionStrip->setPlaybackFollow(PlaybackScrollPage); - selectionStrip->setFixedHeight(26); - m_paneStack->sizePanesEqually(); - m_viewManager->clearToolModeOverrides(); - m_viewManager->setToolModeFor(selectionStrip, - ViewManager::SelectMode); - } - - if (pane) { - - disconnect(pane, SIGNAL(regionOutlined(QRect)), - pane, SLOT(zoomToRegion(QRect))); - connect(pane, SIGNAL(regionOutlined(QRect)), - this, SLOT(regionOutlined(QRect))); - - QString error = m_analyser->newFileLoaded - (m_document, getMainModelId(), m_paneStack, pane); - if (error != "") { - QMessageBox::warning - (this, - tr("Failed to analyse audio"), - tr("Analysis failed

%1

").arg(error), - QMessageBox::Ok); - } - } - - if (!m_withSpectrogram) { - m_analyser->setVisible(Analyser::Spectrogram, false); - } - - if (!m_withSonification) { - m_analyser->setAudible(Analyser::PitchTrack, false); - m_analyser->setAudible(Analyser::Notes, false); - } - - updateLayerStatuses(); - documentRestored(); -} - -void -MainWindow::modelGenerationFailed(QString transformName, QString message) -{ - if (message != "") { - - QMessageBox::warning - (this, - tr("Failed to generate layer"), - tr("Layer generation failed

Failed to generate derived layer.

The layer transform \"%1\" failed:

%2") - .arg(transformName).arg(message), - QMessageBox::Ok); - } else { - QMessageBox::warning - (this, - tr("Failed to generate layer"), - tr("Layer generation failed

Failed to generate a derived layer.

The layer transform \"%1\" failed.

No error information is available.") - .arg(transformName), - QMessageBox::Ok); - } -} - -void -MainWindow::modelGenerationWarning(QString /* transformName */, QString message) -{ - QMessageBox::warning - (this, tr("Warning"), message, QMessageBox::Ok); -} - -void -MainWindow::modelRegenerationFailed(QString layerName, - QString transformName, - QString message) -{ - if (message != "") { - - QMessageBox::warning - (this, - tr("Failed to regenerate layer"), - tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed:

%3") - .arg(layerName).arg(transformName).arg(message), - QMessageBox::Ok); - } else { - QMessageBox::warning - (this, - tr("Failed to regenerate layer"), - tr("Layer generation failed

Failed to regenerate derived layer \"%1\" using new data model as input.

The layer transform \"%2\" failed.

No error information is available.") - .arg(layerName).arg(transformName), - QMessageBox::Ok); - } -} - -void -MainWindow::modelRegenerationWarning(QString layerName, - QString /* transformName */, - QString message) -{ - QMessageBox::warning - (this, tr("Warning"), tr("Warning when regenerating layer

When regenerating the derived layer \"%1\" using new data model as input:

%2").arg(layerName).arg(message), QMessageBox::Ok); -} - -void -MainWindow::alignmentFailed(QString message) -{ - QMessageBox::warning - (this, - tr("Failed to calculate alignment"), - tr("Alignment calculation failed

Failed to calculate an audio alignment:

%1") - .arg(message), - QMessageBox::Ok); -} - -void -MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) -{ -// cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl; - m_paneStack->setCurrentPane(pane); - m_rightButtonMenu->popup(position); -} - -void -MainWindow::handleOSCMessage(const OSCMessage &) -{ - cerr << "MainWindow::handleOSCMessage: Not implemented" << endl; -} - -void -MainWindow::mouseEnteredWidget() -{ - QWidget *w = qobject_cast(sender()); - if (!w) return; - - if (w == m_fader) { - contextHelpChanged(tr("Adjust the master playback level")); - } else if (w == m_playSpeed) { - contextHelpChanged(tr("Adjust the master playback speed")); - } else if (w == m_playSharpen && w->isEnabled()) { - contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); - } else if (w == m_playMono && w->isEnabled()) { - contextHelpChanged(tr("Toggle mono mode for playback time scaling")); - } -} - -void -MainWindow::mouseLeftWidget() -{ - contextHelpChanged(""); -} - -void -MainWindow::website() -{ - //!!! todo: URL! - openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/")); -} - -void -MainWindow::help() -{ - //!!! todo: help URL! - openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/wiki/Reference")); -} - -void -MainWindow::about() -{ - bool debug = false; - QString version = "(unknown version)"; - -#ifdef BUILD_DEBUG - debug = true; -#endif - version = tr("Release %1").arg(TONY_VERSION); - - QString aboutText; - - aboutText += tr("

About Tony

"); - aboutText += tr("

Tony is a program for interactive note and pitch analysis and annotation.

"); - aboutText += tr("

%1 : %2 configuration

") - .arg(version) - .arg(debug ? tr("Debug") : tr("Release")); - aboutText += tr("

Using Qt framework version %1.

") - .arg(QT_VERSION_STR); - - aboutText += - "

Copyright © 2005–2015 Chris Cannam, Queen Mary University of London, and the Tony project authors: Matthias Mauch, George Fazekas, Justin Salamon, and Rachel Bittner.

" - "

pYIN analysis plugin written by Matthias Mauch.

" - "

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.

"; - - QMessageBox::about(this, tr("About %1").arg(QApplication::applicationName()), aboutText); -} - -void -MainWindow::keyReference() -{ - m_keyReference->show(); -} - -void -MainWindow::newerVersionAvailable(QString version) -{ - //!!! nicer URL would be nicer - QSettings settings; - settings.beginGroup("NewerVersionWarning"); - QString tag = QString("version-%1-available-show").arg(version); - if (settings.value(tag, true).toBool()) { - QString title(tr("Newer version available")); - QString text(tr("

Newer version available

You are using version %1 of Tony, but version %2 is now available.

Please see the Tony website for more information.

").arg(TONY_VERSION).arg(version)); - QMessageBox::information(this, title, text); - settings.setValue(tag, false); - } - settings.endGroup(); -} - -void -MainWindow::ffwd() -{ - if (!getMainModel()) return; - - sv_frame_t frame = m_viewManager->getPlaybackFrame(); - ++frame; - - sv_samplerate_t sr = getMainModel()->getSampleRate(); - - // The step is supposed to scale and be as wide as a step of - // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 - - ZoomLevel zoom = m_viewManager->getGlobalZoom(); - double framesPerPixel = 1.0; - if (zoom.zone == ZoomLevel::FramesPerPixel) { - framesPerPixel = zoom.level; - } else { - framesPerPixel = 1.0 / zoom.level; - } - double defaultFramesPerPixel = (720 * 44100) / sr; - double scaler = framesPerPixel / defaultFramesPerPixel; - RealTime step = m_defaultFfwdRwdStep * scaler; - - frame = RealTime::realTime2Frame - (RealTime::frame2RealTime(frame, sr) + step, sr); - - if (frame > getMainModel()->getEndFrame()) { - frame = getMainModel()->getEndFrame(); - } - - if (frame < 0) frame = 0; - - if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(frame); - } - - m_viewManager->setPlaybackFrame(frame); - - if (frame == getMainModel()->getEndFrame() && - m_playSource && - m_playSource->isPlaying() && - !m_viewManager->getPlayLoopMode()) { - stop(); - } -} - -void -MainWindow::rewind() -{ - if (!getMainModel()) return; - - sv_frame_t frame = m_viewManager->getPlaybackFrame(); - if (frame > 0) --frame; - - sv_samplerate_t sr = getMainModel()->getSampleRate(); - - // The step is supposed to scale and be as wide as a step of - // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 - - ZoomLevel zoom = m_viewManager->getGlobalZoom(); - double framesPerPixel = 1.0; - if (zoom.zone == ZoomLevel::FramesPerPixel) { - framesPerPixel = zoom.level; - } else { - framesPerPixel = 1.0 / zoom.level; - } - double defaultFramesPerPixel = (720 * 44100) / sr; - double scaler = framesPerPixel / defaultFramesPerPixel; - RealTime step = m_defaultFfwdRwdStep * scaler; - - frame = RealTime::realTime2Frame - (RealTime::frame2RealTime(frame, sr) - step, sr); - - if (frame < getMainModel()->getStartFrame()) { - frame = getMainModel()->getStartFrame(); - } - - if (frame < 0) frame = 0; - - if (m_viewManager->getPlaySelectionMode()) { - frame = m_viewManager->constrainFrameToSelection(frame); - } - - m_viewManager->setPlaybackFrame(frame); -} diff -r 47f96711069f -r f52766aa747b src/MainWindow.h --- a/src/MainWindow.h Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,254 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - 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. -*/ - -#ifndef _MAIN_WINDOW_H_ -#define _MAIN_WINDOW_H_ - -#include "framework/MainWindowBase.h" -#include "Analyser.h" - -class VersionTester; -class ActivityLog; -class LevelPanToolButton; - -class MainWindow : public MainWindowBase -{ - Q_OBJECT - -public: - MainWindow(SoundOptions options, bool withSonification = true, bool withSpectrogram = true); - virtual ~MainWindow(); - -signals: - void canExportPitchTrack(bool); - void canExportNotes(bool); - void canSnapNotes(bool); - void canPlayWaveform(bool); - void canPlayPitch(bool); - void canPlayNotes(bool); - -public slots: - virtual bool commitData(bool mayAskUser); // on session shutdown - -protected slots: - virtual void openFile(); - virtual void openLocation(); - virtual void openRecentFile(); - virtual void saveSession(); - virtual void saveSessionInAudioPath(); - virtual void saveSessionAs(); - virtual void exportPitchLayer(); - virtual void exportNoteLayer(); - virtual void importPitchLayer(); - virtual void browseRecordedAudio(); - virtual void newSession(); - virtual void closeSession(); - - virtual void toolNavigateSelected(); - virtual void toolEditSelected(); - virtual void toolFreeEditSelected(); - - virtual void clearPitches(); - virtual void togglePitchCandidates(); - virtual void switchPitchUp(); - virtual void switchPitchDown(); - - virtual void snapNotesToPitches(); - virtual void splitNote(); - virtual void mergeNotes(); - virtual void deleteNotes(); - virtual void formNoteFromSelection(); - - virtual void showAudioToggled(); - virtual void showSpectToggled(); - virtual void showPitchToggled(); - virtual void showNotesToggled(); - - virtual void playAudioToggled(); - virtual void playPitchToggled(); - virtual void playNotesToggled(); - - virtual void editDisplayExtents(); - - virtual void analyseNow(); - virtual void resetAnalyseOptions(); - virtual void autoAnalysisToggled(); - virtual void precisionAnalysisToggled(); - virtual void lowampAnalysisToggled(); - virtual void onsetAnalysisToggled(); - virtual void pruneAnalysisToggled(); - virtual void updateAnalyseStates(); - - virtual void doubleClickSelectInvoked(sv_frame_t); - virtual void abandonSelection(); - - virtual void paneAdded(Pane *); - virtual void paneHidden(Pane *); - virtual void paneAboutToBeDeleted(Pane *); - - virtual void paneDropAccepted(Pane *, QStringList); - virtual void paneDropAccepted(Pane *, QString); - - virtual void playSpeedChanged(int); - virtual void playSharpenToggled(); - virtual void playMonoToggled(); - - virtual void speedUpPlayback(); - virtual void slowDownPlayback(); - virtual void restoreNormalPlayback(); - - virtual void monitoringLevelsChanged(float, float); - - virtual void audioGainChanged(float); - virtual void pitchGainChanged(float); - virtual void notesGainChanged(float); - - virtual void audioPanChanged(float); - virtual void pitchPanChanged(float); - virtual void notesPanChanged(float); - - virtual void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool); - virtual void audioOverloadPluginDisabled(); - virtual void audioTimeStretchMultiChannelDisabled(); - - virtual void documentModified(); - virtual void documentRestored(); - virtual void documentReplaced(); - - virtual void updateMenuStates(); - virtual void updateDescriptionLabel(); - virtual void updateLayerStatuses(); - - virtual void layerRemoved(Layer *); - virtual void layerInAView(Layer *, bool); - - virtual void mainModelChanged(ModelId); - virtual void mainModelGainChanged(float); - virtual void modelAdded(ModelId); - - virtual void modelGenerationFailed(QString, QString); - virtual void modelGenerationWarning(QString, QString); - virtual void modelRegenerationFailed(QString, QString, QString); - virtual void modelRegenerationWarning(QString, QString, QString); - virtual void alignmentFailed(QString); - - virtual void rightButtonMenuRequested(Pane *, QPoint point); - - virtual void setupRecentFilesMenu(); - - virtual void handleOSCMessage(const OSCMessage &); - - virtual void mouseEnteredWidget(); - virtual void mouseLeftWidget(); - - virtual void website(); - virtual void help(); - virtual void about(); - virtual void keyReference(); - - virtual void newerVersionAvailable(QString); - - virtual void selectionChangedByUser(); - virtual void regionOutlined(QRect); - - virtual void analyseNewMainModel(); - - void moveOneNoteRight(); - void moveOneNoteLeft(); - void selectOneNoteRight(); - void selectOneNoteLeft(); - - void ffwd(); - void rewind(); - -protected: - Analyser *m_analyser; - - Overview *m_overview; - Fader *m_fader; - AudioDial *m_playSpeed; - QPushButton *m_playSharpen; - QPushButton *m_playMono; - WaveformLayer *m_panLayer; - - bool m_mainMenusCreated; - QMenu *m_playbackMenu; - QMenu *m_recentFilesMenu; - QMenu *m_rightButtonMenu; - QMenu *m_rightButtonPlaybackMenu; - - QAction *m_deleteSelectedAction; - QAction *m_ffwdAction; - QAction *m_rwdAction; - QAction *m_editSelectAction; - QAction *m_showCandidatesAction; - QAction *m_toggleIntelligenceAction; - bool m_intelligentActionOn; // GF: !!! temporary - - QAction *m_autoAnalyse; - QAction *m_precise; - QAction *m_lowamp; - QAction *m_onset; - QAction *m_prune; - - QAction *m_showAudio; - QAction *m_showSpect; - QAction *m_showPitch; - QAction *m_showNotes; - QAction *m_playAudio; - QAction *m_playPitch; - QAction *m_playNotes; - LevelPanToolButton *m_audioLPW; - LevelPanToolButton *m_pitchLPW; - LevelPanToolButton *m_notesLPW; - - ActivityLog *m_activityLog; - KeyReference *m_keyReference; - VersionTester *m_versionTester; - - sv_frame_t m_selectionAnchor; - - bool m_withSonification; - bool m_withSpectrogram; - - Analyser::FrequencyRange m_pendingConstraint; - - QString exportToSVL(QString path, Layer *layer); - FileOpenStatus importPitchLayer(FileSource source); - - virtual void setupMenus(); - virtual void setupFileMenu(); - virtual void setupEditMenu(); - virtual void setupViewMenu(); - virtual void setupAnalysisMenu(); - virtual void setupHelpMenu(); - virtual void setupToolbars(); - - virtual void octaveShift(bool up); - - virtual void auxSnapNotes(Selection s); - - virtual void closeEvent(QCloseEvent *e); - bool checkSaveModified(); - bool waitForInitialAnalysis(); - - virtual void updateVisibleRangeDisplay(Pane *p) const; - virtual void updatePositionStatusDisplays() const; - - void moveByOneNote(bool right, bool doSelect); -}; - - -#endif diff -r 47f96711069f -r f52766aa747b src/NetworkPermissionTester.cpp --- a/src/NetworkPermissionTester.cpp Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - 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 "NetworkPermissionTester.h" - -#include "../version.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool -NetworkPermissionTester::havePermission() -{ - QSettings settings; - settings.beginGroup("Preferences"); - - QString tag = QString("network-permission-%1").arg(TONY_VERSION); - - bool permish = false; - - if (settings.contains(tag)) { - permish = settings.value(tag, false).toBool(); - } else { - - QDialog d; - d.setWindowTitle(QCoreApplication::translate - ("NetworkPermissionTester", - "Tony: a tool for melody annotation")); - - QGridLayout *layout = new QGridLayout; - d.setLayout(layout); - - QLabel *label = new QLabel; - label->setWordWrap(true); - label->setText - (QCoreApplication::translate - ("NetworkPermissionTester", - "

Tony: a tool for melody annotation

" - "

Tony is a program for computer-assisted pitch and note annotation of unaccompanied melody.

" - "

Developed in the Centre for Digital Music at Queen Mary, University of London, Tony is provided free as open source software under the GNU General Public License.

" - "


" - "

Before we go on...

" - "

Tony needs to make occasional network requests to our servers.

" - "

This is to:

" - "
  • tell you when updates are available.
" - "

No personal information will be sent, no tracking is carried out, and all requests happen in the background without interrupting your work.

" - "

We recommend that you allow this. But if you do not wish to do so, please un-check the box below.

")); - layout->addWidget(label, 0, 0); - - QCheckBox *cb = new QCheckBox(QCoreApplication::translate("NetworkPermissionTester", "Allow this")); - cb->setChecked(true); - layout->addWidget(cb, 1, 0); - - QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); - QObject::connect(bb, SIGNAL(accepted()), &d, SLOT(accept())); - layout->addWidget(bb, 2, 0); - - d.exec(); - - bool permish = cb->isChecked(); - settings.setValue(tag, permish); - } - - settings.endGroup(); - - return permish; -} - - - diff -r 47f96711069f -r f52766aa747b src/NetworkPermissionTester.h --- a/src/NetworkPermissionTester.h Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - 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. -*/ - -#ifndef NETWORK_PERMISSION_TESTER_H -#define NETWORK_PERMISSION_TESTER_H - -class NetworkPermissionTester -{ -public: - NetworkPermissionTester() { } - bool havePermission(); -}; - -#endif - - diff -r 47f96711069f -r f52766aa747b src/main.cpp --- a/src/main.cpp Wed Aug 14 11:55:35 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,406 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Tony - An intonation analysis and annotation tool - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - 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 "MainWindow.h" - -#include "system/System.h" -#include "system/Init.h" -#include "base/TempDirectory.h" -#include "base/PropertyContainer.h" -#include "base/Preferences.h" -#include "widgets/TipDialog.h" -#include "widgets/InteractiveFileFinder.h" -#include "transform/TransformFactory.h" -#include "svcore/plugin/PluginScan.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -static QMutex cleanupMutex; -static bool cleanedUp = false; - -static void -signalHandler(int /* signal */) -{ - // Avoid this happening more than once across threads - - cerr << "signalHandler: cleaning up and exiting" << endl; - cleanupMutex.lock(); - if (!cleanedUp) { - TempDirectory::getInstance()->cleanup(); - cleanedUp = true; - } - cleanupMutex.unlock(); - exit(0); -} - -class TonyApplication : public QApplication -{ -public: - TonyApplication(int &argc, char **argv) : - QApplication(argc, argv), - m_mainWindow(0), - m_readyForFiles(false) - { - // tidier without, I reckon - setAttribute(Qt::AA_DontShowIconsInMenus); - } - virtual ~TonyApplication() { - } - - void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } - void releaseMainWindow() { m_mainWindow = 0; } - - virtual void commitData(QSessionManager &manager) { - if (!m_mainWindow) return; - bool mayAskUser = manager.allowsInteraction(); - bool success = m_mainWindow->commitData(mayAskUser); - manager.release(); - if (!success) manager.cancel(); - } - - void readyForFiles() { - m_readyForFiles = true; - } - - void handleFilepathArgument(QString path, QSplashScreen *splash); - - void handleQueuedPaths(QSplashScreen *splash) { - foreach (QString f, m_filepathQueue) { - handleFilepathArgument(f, splash); - } - } - -protected: - MainWindow *m_mainWindow; - - bool m_readyForFiles; - QStringList m_filepathQueue; - - virtual bool event(QEvent *event) { - - if (event->type() == QEvent::FileOpen) { - QString path = static_cast(event)->file(); - if (m_readyForFiles) { - handleFilepathArgument(path, NULL); - } else { - m_filepathQueue.append(path); - } - return true; - } else { - return QApplication::event(event); - } - } -}; - -static QString -getEnvQStr(QString variable) -{ -#ifdef Q_OS_WIN32 - std::wstring wvar = variable.toStdWString(); - wchar_t *value = _wgetenv(wvar.c_str()); - if (!value) return QString(); - else return QString::fromStdWString(std::wstring(value)); -#else - std::string var = variable.toStdString(); - return QString::fromUtf8(qgetenv(var.c_str())); -#endif -} - -static void -putEnvQStr(QString assignment) -{ -#ifdef Q_OS_WIN32 - std::wstring wassignment = assignment.toStdWString(); - _wputenv(_wcsdup(wassignment.c_str())); -#else - putenv(strdup(assignment.toUtf8().data())); -#endif -} - -static void -setupTonyVampPath() -{ - QString tonyVampPath = getEnvQStr("TONY_VAMP_PATH"); - -#ifdef Q_OS_WIN32 - QChar sep(';'); -#else - QChar sep(':'); -#endif - - if (tonyVampPath == "") { - tonyVampPath = QApplication::applicationDirPath(); - -#ifdef Q_OS_WIN32 - QString programFiles = getEnvQStr("ProgramFiles"); - if (programFiles == "") programFiles = "C:\\Program Files"; - QString defaultTonyPath(programFiles + "\\Tony"); - tonyVampPath = tonyVampPath + sep + defaultTonyPath; -#else -#ifdef Q_OS_MAC - tonyVampPath = tonyVampPath + "/../Resources:" + tonyVampPath; -#else - QString defaultTonyPath("/usr/local/lib/tony:/usr/lib/tony"); - tonyVampPath = tonyVampPath + sep + defaultTonyPath; -#endif -#endif - } - - std::vector vampPathList = - Vamp::PluginHostAdapter::getPluginPath(); - - for (auto p: vampPathList) { - tonyVampPath = tonyVampPath + sep + QString::fromUtf8(p.c_str()); - } - - SVCERR << "Setting VAMP_PATH to " << tonyVampPath - << " for Tony plugins" << endl; - - QString env = "VAMP_PATH=" + tonyVampPath; - - // Windows lacks setenv, must use putenv (different arg convention) - putEnvQStr(env); -} - -int -main(int argc, char **argv) -{ - svSystemSpecificInitialisation(); - -#ifdef Q_OS_MAC - if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { - // Fix for OS/X 10.9 font problem - QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); - } -#endif - - TonyApplication application(argc, argv); - - QApplication::setOrganizationName("sonic-visualiser"); - QApplication::setOrganizationDomain("sonicvisualiser.org"); - QApplication::setApplicationName("Tony"); - - setupTonyVampPath(); - - QStringList args = application.arguments(); - - signal(SIGINT, signalHandler); - signal(SIGTERM, signalHandler); - -#ifndef Q_OS_WIN32 - signal(SIGHUP, signalHandler); - signal(SIGQUIT, signalHandler); -#endif - - bool audioOutput = true; - bool sonification = true; - bool spectrogram = true; - - if (args.contains("--help") || args.contains("-h") || args.contains("-?")) { - std::cerr << QApplication::tr( - "\nTony is a program for interactive note and pitch analysis and annotation.\n\nUsage:\n\n %1 [--no-audio] [--no-sonification] [--no-spectrogram] [ ...]\n\n --no-audio: Do not attempt to open an audio output device\n --no-sonification: Disable sonification of pitch tracks and notes and hide their toggles.\n --no-spectrogram: Disable spectrogram.\n : One or more Tony (.ton) and audio files may be provided.").arg(argv[0]).toStdString() << std::endl; - exit(2); - } - - if (args.contains("--no-audio")) audioOutput = false; - - if (args.contains("--no-sonification")) sonification = false; - - if (args.contains("--no-spectrogram")) spectrogram = false; - - InteractiveFileFinder::getInstance()->setApplicationSessionExtension("ton"); - - QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); - - QSplashScreen *splash = 0; - // If we had a splash screen, we would show it here - - QIcon icon; - int sizes[] = { 16, 22, 24, 32, 48, 64, 128 }; - for (size_t i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) { - icon.addFile(QString(":icons/tony-%1x%2.png").arg(sizes[i]).arg(sizes[i])); - } - QApplication::setWindowIcon(icon); - - QString language = QLocale::system().name(); - - QTranslator qtTranslator; - QString qtTrName = QString("qt_%1").arg(language); - std::cerr << "Loading " << qtTrName.toStdString() << "..." << std::endl; - bool success = false; - if (!(success = qtTranslator.load(qtTrName))) { - QString qtDir = getenv("QTDIR"); - if (qtDir != "") { - success = qtTranslator.load - (qtTrName, QDir(qtDir).filePath("translations")); - } - } - if (!success) { - std::cerr << "Failed to load Qt translation for locale" << std::endl; - } - application.installTranslator(&qtTranslator); - - StoreStartupLocale(); - - // Permit size_t and PropertyName to be used as args in queued signal calls - qRegisterMetaType("size_t"); - qRegisterMetaType("PropertyContainer::PropertyName"); - - MainWindow::SoundOptions options = MainWindow::WithEverything; - if (!audioOutput) options = 0; - - MainWindow *gui = new MainWindow(options, sonification, spectrogram); - application.setMainWindow(gui); - if (splash) { - QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide())); - } - - QScreen *screen = QApplication::primaryScreen(); - QRect available = screen->availableGeometry(); - - int width = (available.width() * 2) / 3; - int height = available.height() / 2; - if (height < 450) height = (available.height() * 2) / 3; - if (width > height * 2) width = height * 2; - - QSettings settings; - settings.beginGroup("MainWindow"); - QSize size = settings.value("size", QSize(width, height)).toSize(); - gui->resizeConstrained(size); - if (settings.contains("position")) { - QRect prevrect(settings.value("position").toPoint(), size); - if (!(available & prevrect).isEmpty()) { - gui->move(prevrect.topLeft()); - } - } - settings.endGroup(); - - gui->show(); - - application.readyForFiles(); - - for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { - - if (i == args.begin()) continue; - if (i->startsWith('-')) continue; - - QString path = *i; - - application.handleFilepathArgument(path, splash); - } - - application.handleQueuedPaths(splash); - - if (splash) splash->finish(gui); - delete splash; - - int rv = application.exec(); - - gui->hide(); - - cleanupMutex.lock(); - - if (!cleanedUp) { - TransformFactory::deleteInstance(); - TempDirectory::getInstance()->cleanup(); - cleanedUp = true; - } - - application.releaseMainWindow(); - - delete gui; - - cleanupMutex.unlock(); - - return rv; -} - -/** Application-global handler for filepaths passed in, e.g. as - * command-line arguments or apple events */ - -void TonyApplication::handleFilepathArgument(QString path, - QSplashScreen *splash) -{ - static bool haveSession = false; - static bool haveMainModel = false; - static bool havePriorCommandLineModel = false; - - if (!m_mainWindow) return; - - MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; - -#ifdef Q_OS_WIN32 - path.replace("\\", "/"); -#endif - - if (path.endsWith("ton")) { - if (!haveSession) { - status = m_mainWindow->openSessionPath(path); - if (status == MainWindow::FileOpenSucceeded) { - haveSession = true; - haveMainModel = true; - } - } else { - std::cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << std::endl; - status = MainWindow::FileOpenSucceeded; - } - } - if (status != MainWindow::FileOpenSucceeded) { - if (!haveMainModel) { - status = m_mainWindow->openPath(path, MainWindow::ReplaceSession); - if (status == MainWindow::FileOpenSucceeded) { - haveMainModel = true; - } - } else { - if (haveSession && !havePriorCommandLineModel) { - status = m_mainWindow->openPath(path, MainWindow::AskUser); - if (status == MainWindow::FileOpenSucceeded) { - havePriorCommandLineModel = true; - } - } else { - status = m_mainWindow->openPath(path, MainWindow::CreateAdditionalModel); - } - } - } - if (status == MainWindow::FileOpenFailed) { - if (splash) splash->hide(); - QMessageBox::critical - (m_mainWindow, QMessageBox::tr("Failed to open file"), - QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path)); - } else if (status == MainWindow::FileOpenWrongMode) { - if (splash) splash->hide(); - QMessageBox::critical - (m_mainWindow, QMessageBox::tr("Failed to open file"), - QMessageBox::tr("Audio required

Please load at least one audio file before importing annotation data")); - } -} - diff -r 47f96711069f -r f52766aa747b tonyapp.pro --- a/tonyapp.pro Wed Aug 14 11:55:35 2019 +0100 +++ b/tonyapp.pro Wed Aug 14 11:57:06 2019 +0100 @@ -36,14 +36,14 @@ for (file, SVGUI_HEADERS) { HEADERS += $$sprintf("svgui/%1", $$file) } for (file, SVAPP_HEADERS) { HEADERS += $$sprintf("svapp/%1", $$file) } -HEADERS += src/MainWindow.h \ - src/NetworkPermissionTester.h \ - src/Analyser.h +HEADERS += main/MainWindow.h \ + main/NetworkPermissionTester.h \ + main/Analyser.h -SOURCES += src/main.cpp \ - src/Analyser.cpp \ - src/NetworkPermissionTester.cpp \ - src/MainWindow.cpp +SOURCES += main/main.cpp \ + main/Analyser.cpp \ + main/NetworkPermissionTester.cpp \ + main/MainWindow.cpp