Mercurial > hg > tony
view main/Analyser.cpp @ 586:1dfdbd017cf5
Suppress property boxes. They aren't visible anyway, but updates to their invisible state seems to be causing problems with certain Qt builds on Windows.
author | Chris Cannam |
---|---|
date | Wed, 04 Sep 2019 14:19:57 +0100 |
parents | af488f78d029 |
children | 327177b6bd3a |
line wrap: on
line source
/* -*- 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 <QSettings> #include <QMutexLocker> 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("<layer verticalScale=\"%1\" plotStyle=\"%2\" " "scaleMinimum=\"%3\" scaleMaximum=\"%4\"/>") .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("<layer verticalScale=\"%1\"/>") .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<WaveFileModel>(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<SparseTimeValueModel> (m_layers[PitchTrack]->getModel()); if (model) { model->extendEndFrame(endFrame); } } if (m_layers[Notes]) { auto model = ModelById::getAs<NoteModel> (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.<br><br>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<Colour3DPlotLayer *> (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<SpectrogramLayer *> (m_pane->getLayer(i)); if (existing) { cerr << "recording existing spectrogram layer" << endl; m_layers[Spectrogram] = existing; return ""; } } SpectrogramLayer *spectrogram = qobject_cast<SpectrogramLayer *> (m_document->createMainModelLayer(LayerFactory::MelodicRangeSpectrogram)); spectrogram->setColourMap((int)ColourMapper::BlackOnWhite); spectrogram->setNormalization(ColumnNormalization::Hybrid); // This magical scale factor happens to get us a similar display // to Tony v1.0 spectrogram->setGain(0.25f); 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<WaveformLayer *> (m_pane->getLayer(i)); if (existing) { cerr << "recording existing waveform layer" << endl; m_layers[Audio] = existing; return ""; } } WaveformLayer *waveform = qobject_cast<WaveformLayer *> (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<WaveFileModel>(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<TimeValueLayer *>(m_pane->getLayer(i)); } if (!existingNotes) { existingNotes = qobject_cast<FlexiNoteLayer *>(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<TimeValueLayer *> (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.<br><br>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<Layer *> layers = m_document->createDerivedLayers(transforms, m_fileModel); for (int i = 0; i < (int)layers.size(); ++i) { FlexiNoteLayer *f = qobject_cast<FlexiNoteLayer *>(layers[i]); TimeValueLayer *t = qobject_cast<TimeValueLayer *>(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<TimeValueLayer *>(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<FlexiNoteLayer *>(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<WaveFileModel>(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.<br><br>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<Layer *> primary, vector<Layer *> 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<Layer *> 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<TimeValueLayer *>(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<Layer *> 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<Layer *> 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<FlexiNoteLayer *>(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<FlexiNoteLayer *>(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); } }