Chris@6: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@6: Chris@6: /* Chris@6: Tony Chris@6: An intonation analysis and annotation tool Chris@6: Centre for Digital Music, Queen Mary, University of London. Chris@6: This file copyright 2006-2012 Chris Cannam and QMUL. Chris@6: Chris@6: This program is free software; you can redistribute it and/or Chris@6: modify it under the terms of the GNU General Public License as Chris@6: published by the Free Software Foundation; either version 2 of the Chris@6: License, or (at your option) any later version. See the file Chris@6: COPYING included with this distribution for more information. Chris@6: */ Chris@6: Chris@6: #include "Analyser.h" Chris@6: Chris@6: #include "transform/TransformFactory.h" Chris@6: #include "transform/ModelTransformer.h" gyorgyf@14: #include "transform/FeatureExtractionModelTransformer.h" Chris@6: #include "framework/Document.h" Chris@6: #include "data/model/WaveFileModel.h" Chris@6: #include "view/Pane.h" Chris@6: #include "view/PaneStack.h" Chris@6: #include "layer/Layer.h" Chris@6: #include "layer/TimeValueLayer.h" matthiasm@13: #include "layer/NoteLayer.h" matthiasm@11: #include "layer/FlexiNoteLayer.h" Chris@120: #include "layer/WaveformLayer.h" Chris@6: #include "layer/ColourDatabase.h" Chris@145: #include "layer/ColourMapper.h" gyorgyf@16: #include "layer/LayerFactory.h" Chris@145: #include "layer/SpectrogramLayer.h" Chris@161: #include "layer/Colour3DPlotLayer.h" Chris@199: #include "layer/ShowLayerCommand.h" Chris@6: Chris@83: #include Chris@341: #include Chris@83: Chris@163: using std::vector; Chris@163: Chris@6: Analyser::Analyser() : Chris@6: m_document(0), Chris@6: m_fileModel(0), Chris@133: m_paneStack(0), Chris@188: m_pane(0), Chris@188: m_currentCandidate(-1), Chris@341: m_candidatesVisible(false), Chris@341: m_currentAsyncHandle(0) Chris@6: { Chris@83: QSettings settings; Chris@83: settings.beginGroup("LayerDefaults"); Chris@83: settings.setValue Chris@83: ("timevalues", Chris@83: QString("") Chris@145: .arg(int(TimeValueLayer::AutoAlignScale)) matthiasm@182: .arg(int(TimeValueLayer::PlotPoints)) Chris@83: .arg(27.5f).arg(880.f)); // temporary values: better get the real extents of the data from the model Chris@83: settings.setValue Chris@83: ("flexinotes", Chris@83: QString("") Chris@83: .arg(int(FlexiNoteLayer::AutoAlignScale))); Chris@83: settings.endGroup(); Chris@6: } Chris@6: Chris@6: Analyser::~Analyser() Chris@6: { Chris@6: } Chris@6: Chris@140: QString Chris@6: Analyser::newFileLoaded(Document *doc, WaveFileModel *model, Chris@6: PaneStack *paneStack, Pane *pane) Chris@6: { Chris@6: m_document = doc; Chris@6: m_fileModel = model; Chris@133: m_paneStack = paneStack; Chris@6: m_pane = pane; Chris@6: Chris@242: connect(doc, SIGNAL(layerAboutToBeDeleted(Layer *)), Chris@242: this, SLOT(layerAboutToBeDeleted(Layer *))); Chris@242: Chris@326: QSettings settings; Chris@326: settings.beginGroup("Analyser"); Chris@326: bool autoAnalyse = settings.value("auto-analysis", true).toBool(); Chris@326: settings.endGroup(); Chris@326: Chris@326: return doAllAnalyses(autoAnalyse); Chris@325: } Chris@325: Chris@325: QString Chris@325: Analyser::analyseExistingFile() Chris@325: { Chris@325: if (!m_document) return "Internal error: Analyser::analyseExistingFile() called with no document present"; Chris@325: Chris@325: if (!m_pane) return "Internal error: Analyser::analyseExistingFile() called with no pane present"; Chris@325: Chris@325: if (m_layers[PitchTrack]) { Chris@325: m_document->removeLayerFromView(m_pane, m_layers[PitchTrack]); Chris@325: m_layers[PitchTrack] = 0; Chris@325: } Chris@325: if (m_layers[Notes]) { Chris@325: m_document->removeLayerFromView(m_pane, m_layers[Notes]); Chris@325: m_layers[Notes] = 0; Chris@325: } Chris@325: Chris@326: return doAllAnalyses(true); Chris@325: } Chris@325: Chris@325: QString Chris@326: Analyser::doAllAnalyses(bool withPitchTrack) Chris@325: { Chris@165: m_reAnalysingSelection = Selection(); Chris@165: m_reAnalysisCandidates.clear(); Chris@167: m_currentCandidate = -1; Chris@184: m_candidatesVisible = false; Chris@165: Chris@161: // Note that we need at least one main-model layer (time ruler, Chris@161: // waveform or what have you). It could be hidden if we don't want Chris@161: // to see it but it must exist. Chris@6: Chris@161: QString warning, error; Chris@161: Chris@260: cerr << "Analyser::newFileLoaded: about to check visualisations etc" << endl; Chris@260: Chris@161: // This isn't fatal -- we can proceed without Chris@161: // visualisations. Other failures are fatal though. Chris@161: warning = addVisualisations(); Chris@161: Chris@161: error = addWaveform(); Chris@161: if (error != "") return error; Chris@161: Chris@326: if (withPitchTrack) { Chris@326: error = addAnalyses(); Chris@326: if (error != "") return error; Chris@326: } Chris@161: Chris@161: loadState(Audio); Chris@161: loadState(PitchTrack); Chris@161: loadState(Notes); Chris@161: loadState(Spectrogram); Chris@161: Chris@260: stackLayers(); Chris@260: Chris@161: emit layersChanged(); Chris@161: Chris@161: return warning; Chris@161: } Chris@161: Chris@226: void Chris@226: Analyser::fileClosed() Chris@226: { Chris@226: cerr << "Analyser::fileClosed" << endl; Chris@226: m_layers.clear(); Chris@226: m_reAnalysisCandidates.clear(); Chris@226: m_currentCandidate = -1; Chris@226: m_reAnalysingSelection = Selection(); Chris@226: } Chris@226: Chris@227: bool Chris@227: Analyser::getDisplayFrequencyExtents(float &min, float &max) Chris@227: { Chris@227: if (!m_layers[Spectrogram]) return false; Chris@227: return m_layers[Spectrogram]->getDisplayExtents(min, max); Chris@227: } Chris@227: Chris@227: bool Chris@227: Analyser::setDisplayFrequencyExtents(float min, float max) Chris@227: { Chris@227: if (!m_layers[Spectrogram]) return false; Chris@227: m_layers[Spectrogram]->setDisplayExtents(min, max); Chris@242: return true; Chris@227: } Chris@227: Chris@314: int Chris@314: Analyser::getInitialAnalysisCompletion() Chris@314: { Chris@314: int completion = 0; Chris@314: Chris@314: if (m_layers[PitchTrack]) { Chris@314: completion = m_layers[PitchTrack]->getCompletion(m_pane); Chris@314: } Chris@314: Chris@314: if (m_layers[Notes]) { Chris@314: int c = m_layers[Notes]->getCompletion(m_pane); Chris@314: if (c < completion) completion = c; Chris@314: } Chris@314: Chris@314: return completion; Chris@314: } Chris@314: Chris@314: void Chris@314: Analyser::layerCompletionChanged() Chris@314: { Chris@314: if (getInitialAnalysisCompletion() == 100) { Chris@314: emit initialAnalysisCompleted(); Chris@314: } Chris@314: } Chris@314: Chris@161: QString Chris@161: Analyser::addVisualisations() Chris@161: { Chris@227: // A spectrogram, off by default. Must go at the back because it's Chris@227: // opaque Chris@227: Chris@227: /* This is roughly what we'd do for a constant-Q spectrogram, but it Chris@227: currently has issues with y-axis alignment Chris@227: Chris@161: TransformFactory *tf = TransformFactory::getInstance(); Chris@161: Chris@161: QString name = "Constant-Q"; Chris@161: QString base = "vamp:cqvamp:cqvamp:"; Chris@161: QString out = "constantq"; Chris@6: Chris@161: QString notFound = tr("Transform \"%1\" not found, spectrogram will not be enabled.

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

Is the %2 Vamp plugin correctly installed?"); Chris@140: if (!tf->haveTransform(base + f0out)) { Chris@140: return notFound.arg(base + f0out).arg(plugname); Chris@140: } Chris@140: if (!tf->haveTransform(base + noteout)) { Chris@140: return notFound.arg(base + noteout).arg(plugname); Chris@6: } Chris@6: Chris@326: QSettings settings; Chris@326: settings.beginGroup("Analyser"); Chris@326: bool precise = settings.value("precision-analysis", false).toBool(); Chris@326: settings.endGroup(); Chris@326: matthiasm@345: settings.beginGroup("Analyser"); matthiasm@345: bool lowamp = settings.value("lowamp-analysis", false).toBool(); matthiasm@345: settings.endGroup(); matthiasm@345: Chris@83: Transform t = tf->getDefaultTransformFor Chris@83: (base + f0out, m_fileModel->getSampleRate()); Chris@83: t.setStepSize(256); Chris@83: t.setBlockSize(2048); Chris@6: Chris@326: if (precise) { Chris@326: cerr << "setting parameters for precise mode" << endl; Chris@326: t.setParameter("precisetime", 1); Chris@326: } else { Chris@326: cerr << "setting parameters for vague mode" << endl; Chris@326: t.setParameter("precisetime", 0); Chris@326: } Chris@326: matthiasm@345: if (lowamp) { matthiasm@345: cerr << "setting parameters for lowamp suppression" << endl; matthiasm@354: t.setParameter("lowampsuppression", 0.1f); matthiasm@345: } else { matthiasm@345: cerr << "setting parameters for no lowamp suppression" << endl; matthiasm@345: t.setParameter("lowampsuppression", 0.0f); matthiasm@345: } matthiasm@345: Chris@83: transforms.push_back(t); Chris@83: Chris@83: t.setOutput(noteout); Chris@83: Chris@83: transforms.push_back(t); Chris@83: Chris@83: std::vector layers = Chris@83: m_document->createDerivedLayers(transforms, m_fileModel); Chris@83: Chris@162: for (int i = 0; i < (int)layers.size(); ++i) { Chris@83: Chris@162: FlexiNoteLayer *f = qobject_cast(layers[i]); Chris@162: TimeValueLayer *t = qobject_cast(layers[i]); Chris@162: Chris@162: if (f) m_layers[Notes] = f; Chris@162: if (t) m_layers[PitchTrack] = t; Chris@162: Chris@162: m_document->addLayerToView(m_pane, layers[i]); Chris@6: } Chris@161: Chris@162: ColourDatabase *cdb = ColourDatabase::getInstance(); Chris@162: Chris@162: TimeValueLayer *pitchLayer = Chris@162: qobject_cast(m_layers[PitchTrack]); Chris@162: if (pitchLayer) { Chris@162: pitchLayer->setBaseColour(cdb->getColourIndex(tr("Black"))); Chris@162: PlayParameters *params = pitchLayer->getPlayParameters(); Chris@162: if (params) params->setPlayPan(1); Chris@162: } Chris@314: connect(pitchLayer, SIGNAL(modelCompletionChanged()), Chris@314: this, SLOT(layerCompletionChanged())); Chris@162: Chris@162: FlexiNoteLayer *flexiNoteLayer = Chris@162: qobject_cast(m_layers[Notes]); Chris@162: if (flexiNoteLayer) { Chris@162: flexiNoteLayer->setBaseColour(cdb->getColourIndex(tr("Bright Blue"))); Chris@162: PlayParameters *params = flexiNoteLayer->getPlayParameters(); Chris@162: if (params) params->setPlayPan(1); Chris@162: } Chris@314: connect(flexiNoteLayer, SIGNAL(modelCompletionChanged()), Chris@314: this, SLOT(layerCompletionChanged())); Chris@162: Chris@162: return ""; Chris@162: } Chris@162: Chris@162: QString Chris@192: Analyser::reAnalyseSelection(Selection sel, FrequencyRange range) Chris@162: { Chris@341: QMutexLocker locker(&m_asyncMutex); Chris@341: Chris@200: if (sel == m_reAnalysingSelection || sel.isEmpty()) return ""; Chris@165: Chris@341: if (m_currentAsyncHandle) { Chris@341: m_document->cancelAsyncLayerCreation(m_currentAsyncHandle); Chris@341: } Chris@341: Chris@252: if (!m_reAnalysisCandidates.empty()) { Chris@252: CommandHistory::getInstance()->startCompoundOperation Chris@252: (tr("Discard Previous Candidates"), true); Chris@252: discardPitchCandidates(); Chris@252: CommandHistory::getInstance()->endCompoundOperation(); Chris@252: } Chris@199: Chris@194: m_reAnalysingSelection = sel; Chris@167: Chris@194: m_preAnalysis = Clipboard(); Chris@194: Layer *myLayer = m_layers[PitchTrack]; Chris@194: if (myLayer) { Chris@194: myLayer->copy(m_pane, sel, m_preAnalysis); Chris@194: } Chris@165: Chris@162: TransformFactory *tf = TransformFactory::getInstance(); Chris@162: Chris@223: QString plugname1 = "pYIN"; Chris@223: QString plugname2 = "CHP"; Chris@223: Chris@162: QString base = "vamp:pyin:localcandidatepyin:"; Chris@162: QString out = "pitchtrackcandidates"; Chris@162: Chris@192: if (range.isConstrained()) { Chris@223: base = "vamp:chp:constrainedharmonicpeak:"; Chris@223: out = "peak"; Chris@192: } Chris@192: Chris@162: Transforms transforms; Chris@162: Chris@223: QString notFound = tr("Transform \"%1\" not found. Unable to perform interactive analysis.

Are the %2 and %3 Vamp plugins correctly installed?"); Chris@162: if (!tf->haveTransform(base + out)) { Chris@223: return notFound.arg(base + out).arg(plugname1).arg(plugname2); Chris@162: } Chris@162: Chris@162: Transform t = tf->getDefaultTransformFor Chris@162: (base + out, m_fileModel->getSampleRate()); Chris@162: t.setStepSize(256); Chris@162: t.setBlockSize(2048); Chris@162: Chris@192: if (range.isConstrained()) { Chris@192: t.setParameter("minfreq", range.min); Chris@192: t.setParameter("maxfreq", range.max); Chris@223: t.setBlockSize(4096); Chris@192: } Chris@192: matthiasm@273: // get time stamps that align with the 256-sample grid of the original extraction matthiasm@273: int startSample = ceil(sel.getStartFrame()*1.0/256) * 256; matthiasm@273: int endSample = ceil(sel.getEndFrame()*1.0/256) * 256; matthiasm@273: if (!range.isConstrained()) { matthiasm@273: startSample -= 4*256; // 4*256 is for 4 frames offset due to timestamp shift matthiasm@273: endSample -= 4*256; matthiasm@273: } else { matthiasm@273: endSample -= 9*256; // MM says: not sure what the CHP plugin does there matthiasm@273: } matthiasm@273: RealTime start = RealTime::frame2RealTime(startSample, m_fileModel->getSampleRate()); matthiasm@273: RealTime end = RealTime::frame2RealTime(endSample, m_fileModel->getSampleRate()); Chris@164: Chris@164: RealTime duration; Chris@164: Chris@164: if (sel.getEndFrame() > sel.getStartFrame()) { Chris@164: duration = end - start; Chris@164: } Chris@164: Chris@200: cerr << "Analyser::reAnalyseSelection: start " << start << " end " << end << " original selection start " << sel.getStartFrame() << " end " << sel.getEndFrame() << " duration " << duration << endl; Chris@200: Chris@200: if (duration <= RealTime::zeroTime) { Chris@200: cerr << "Analyser::reAnalyseSelection: duration <= 0, not analysing" << endl; Chris@200: return ""; Chris@200: } Chris@200: Chris@164: t.setStartTime(start); Chris@164: t.setDuration(duration); Chris@162: Chris@162: transforms.push_back(t); Chris@341: Chris@341: m_currentAsyncHandle = Chris@341: m_document->createDerivedLayersAsync(transforms, m_fileModel, this); Chris@162: Chris@163: return ""; Chris@163: } Chris@162: Chris@184: bool Chris@184: Analyser::arePitchCandidatesShown() const Chris@184: { Chris@184: return m_candidatesVisible; Chris@184: } Chris@184: Chris@184: void Chris@184: Analyser::showPitchCandidates(bool shown) Chris@184: { Chris@184: if (m_candidatesVisible == shown) return; Chris@184: Chris@184: foreach (Layer *layer, m_reAnalysisCandidates) { Chris@184: if (shown) { Chris@199: CommandHistory::getInstance()->addCommand Chris@199: (new ShowLayerCommand(m_pane, layer, true, Chris@199: tr("Show Pitch Candidates"))); Chris@184: } else { Chris@199: CommandHistory::getInstance()->addCommand Chris@199: (new ShowLayerCommand(m_pane, layer, false, Chris@199: tr("Hide Pitch Candidates"))); Chris@184: } Chris@184: } Chris@184: Chris@184: m_candidatesVisible = shown; Chris@184: } Chris@184: Chris@163: void Chris@341: Analyser::layersCreated(Document::LayerCreationAsyncHandle handle, Chris@341: vector primary, Chris@163: vector additional) Chris@163: { Chris@349: { Chris@349: QMutexLocker locker(&m_asyncMutex); Chris@165: Chris@349: if (handle != m_currentAsyncHandle || Chris@349: m_reAnalysingSelection == Selection()) { Chris@349: // We don't want these! Chris@349: for (int i = 0; i < (int)primary.size(); ++i) { Chris@349: m_document->deleteLayer(primary[i]); Chris@349: } Chris@349: for (int i = 0; i < (int)additional.size(); ++i) { Chris@349: m_document->deleteLayer(additional[i]); Chris@349: } Chris@349: return; Chris@349: } Chris@349: m_currentAsyncHandle = 0; Chris@349: Chris@349: CommandHistory::getInstance()->startCompoundOperation Chris@349: (tr("Re-Analyse Selection"), true); Chris@349: Chris@349: m_reAnalysisCandidates.clear(); Chris@349: Chris@349: vector all; Chris@226: for (int i = 0; i < (int)primary.size(); ++i) { Chris@349: all.push_back(primary[i]); Chris@226: } Chris@226: for (int i = 0; i < (int)additional.size(); ++i) { Chris@349: all.push_back(additional[i]); Chris@226: } Chris@349: Chris@349: for (int i = 0; i < (int)all.size(); ++i) { Chris@349: TimeValueLayer *t = qobject_cast(all[i]); Chris@349: if (t) { Chris@349: PlayParameters *params = t->getPlayParameters(); Chris@349: if (params) { Chris@349: params->setPlayAudible(false); Chris@349: } Chris@349: t->setBaseColour Chris@349: (ColourDatabase::getInstance()->getColourIndex(tr("Bright Orange"))); Chris@349: t->setPresentationName("candidate"); Chris@349: m_document->addLayerToView(m_pane, t); Chris@349: m_reAnalysisCandidates.push_back(t); Chris@349: } Chris@349: } Chris@349: Chris@349: if (!all.empty()) { Chris@349: bool show = m_candidatesVisible; Chris@349: m_candidatesVisible = !show; // to ensure the following takes effect Chris@349: showPitchCandidates(show); Chris@349: } Chris@349: Chris@349: CommandHistory::getInstance()->endCompoundOperation(); Chris@226: } Chris@199: Chris@187: emit layersChanged(); Chris@6: } Chris@6: Chris@198: bool Chris@198: Analyser::haveHigherPitchCandidate() const Chris@198: { Chris@198: if (m_reAnalysisCandidates.empty()) return false; Chris@198: return (m_currentCandidate < 0 || Chris@198: (m_currentCandidate + 1 < (int)m_reAnalysisCandidates.size())); Chris@198: } Chris@198: Chris@198: bool Chris@198: Analyser::haveLowerPitchCandidate() const Chris@198: { Chris@198: if (m_reAnalysisCandidates.empty()) return false; Chris@198: return (m_currentCandidate < 0 || m_currentCandidate >= 1); Chris@198: } Chris@198: gyorgyf@45: void Chris@167: Analyser::switchPitchCandidate(Selection sel, bool up) Chris@167: { Chris@167: if (m_reAnalysisCandidates.empty()) return; Chris@167: Chris@167: if (up) { Chris@167: m_currentCandidate = m_currentCandidate + 1; Chris@168: if (m_currentCandidate >= (int)m_reAnalysisCandidates.size()) { Chris@167: m_currentCandidate = 0; Chris@167: } Chris@167: } else { Chris@167: m_currentCandidate = m_currentCandidate - 1; Chris@167: if (m_currentCandidate < 0) { Chris@168: m_currentCandidate = (int)m_reAnalysisCandidates.size() - 1; Chris@167: } Chris@167: } Chris@167: Chris@167: Layer *pitchTrack = m_layers[PitchTrack]; Chris@167: if (!pitchTrack) return; Chris@167: Chris@167: Clipboard clip; Chris@167: pitchTrack->deleteSelection(sel); Chris@167: m_reAnalysisCandidates[m_currentCandidate]->copy(m_pane, sel, clip); Chris@167: pitchTrack->paste(m_pane, clip, 0, false); Chris@264: Chris@264: stackLayers(); Chris@260: } Chris@167: Chris@260: void Chris@260: Analyser::stackLayers() Chris@260: { Chris@167: // raise the pitch track, then notes on top (if present) Chris@260: if (m_layers[PitchTrack]) { Chris@260: m_paneStack->setCurrentLayer(m_pane, m_layers[PitchTrack]); Chris@260: } Chris@167: if (m_layers[Notes] && !m_layers[Notes]->isLayerDormant(m_pane)) { Chris@167: m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); Chris@167: } Chris@167: } Chris@167: Chris@167: void Chris@168: Analyser::shiftOctave(Selection sel, bool up) Chris@168: { Chris@168: float factor = (up ? 2.f : 0.5f); Chris@168: Chris@168: vector actOn; Chris@168: Chris@168: Layer *pitchTrack = m_layers[PitchTrack]; Chris@168: if (pitchTrack) actOn.push_back(pitchTrack); Chris@168: Chris@168: foreach (Layer *layer, actOn) { Chris@168: Chris@168: Clipboard clip; Chris@168: layer->copy(m_pane, sel, clip); Chris@168: layer->deleteSelection(sel); Chris@168: Chris@168: Clipboard shifted; Chris@168: foreach (Clipboard::Point p, clip.getPoints()) { Chris@168: if (p.haveValue()) { Chris@168: Clipboard::Point sp = p.withValue(p.getValue() * factor); Chris@168: shifted.addPoint(sp); Chris@168: } else { Chris@168: shifted.addPoint(p); Chris@168: } Chris@168: } Chris@168: Chris@168: layer->paste(m_pane, shifted, 0, false); Chris@168: } Chris@168: } Chris@168: Chris@168: void Chris@184: Analyser::deletePitches(Selection sel) Chris@168: { Chris@168: Layer *pitchTrack = m_layers[PitchTrack]; Chris@168: if (!pitchTrack) return; Chris@168: Chris@168: pitchTrack->deleteSelection(sel); Chris@168: } Chris@168: Chris@168: void Chris@199: Analyser::abandonReAnalysis(Selection sel) Chris@167: { Chris@252: // A compound command is already in progress Chris@252: Chris@199: discardPitchCandidates(); Chris@194: Chris@194: Layer *myLayer = m_layers[PitchTrack]; Chris@194: if (!myLayer) return; Chris@194: myLayer->deleteSelection(sel); Chris@194: myLayer->paste(m_pane, m_preAnalysis, 0, false); Chris@167: } Chris@167: Chris@167: void Chris@269: Analyser::clearReAnalysis() Chris@269: { Chris@269: discardPitchCandidates(); Chris@269: } Chris@269: Chris@269: void Chris@199: Analyser::discardPitchCandidates() Chris@199: { Chris@242: if (!m_reAnalysisCandidates.empty()) { Chris@252: // We don't use a compound command here, because we may be Chris@252: // already in one. Caller bears responsibility for doing that Chris@242: foreach (Layer *layer, m_reAnalysisCandidates) { Chris@242: // This will cause the layer to be deleted later (ownership is Chris@242: // transferred to the remove command) Chris@242: m_document->removeLayerFromView(m_pane, layer); Chris@242: } Chris@243: m_reAnalysisCandidates.clear(); Chris@199: } Chris@199: Chris@199: m_currentCandidate = -1; Chris@199: m_reAnalysingSelection = Selection(); Chris@199: m_candidatesVisible = false; Chris@199: } Chris@199: Chris@199: void Chris@242: Analyser::layerAboutToBeDeleted(Layer *doomed) Chris@242: { Chris@242: cerr << "Analyser::layerAboutToBeDeleted(" << doomed << ")" << endl; Chris@242: Chris@242: vector notDoomed; Chris@242: Chris@242: foreach (Layer *layer, m_reAnalysisCandidates) { Chris@242: if (layer != doomed) { Chris@242: notDoomed.push_back(layer); Chris@242: } Chris@242: } Chris@242: Chris@242: m_reAnalysisCandidates = notDoomed; Chris@242: } Chris@242: Chris@242: void Chris@174: Analyser::takePitchTrackFrom(Layer *otherLayer) Chris@174: { Chris@174: Layer *myLayer = m_layers[PitchTrack]; Chris@174: if (!myLayer) return; Chris@174: Chris@174: Clipboard clip; Chris@174: Chris@174: Selection sel = Selection(myLayer->getModel()->getStartFrame(), Chris@174: myLayer->getModel()->getEndFrame()); Chris@174: myLayer->deleteSelection(sel); Chris@174: Chris@174: sel = Selection(otherLayer->getModel()->getStartFrame(), Chris@174: otherLayer->getModel()->getEndFrame()); Chris@174: otherLayer->copy(m_pane, sel, clip); Chris@174: Chris@174: myLayer->paste(m_pane, clip, 0, false); Chris@174: } Chris@174: Chris@174: void Chris@355: Analyser::getEnclosingSelectionScope(int f, int &f0, int &f1) Chris@139: { Chris@139: FlexiNoteLayer *flexiNoteLayer = Chris@139: qobject_cast(m_layers[Notes]); Chris@139: Chris@139: int f0i = f, f1i = f; Chris@355: int res = 1; Chris@139: Chris@139: if (!flexiNoteLayer) { Chris@139: f0 = f1 = f; Chris@139: return; Chris@139: } Chris@139: Chris@139: flexiNoteLayer->snapToFeatureFrame(m_pane, f0i, res, Layer::SnapLeft); Chris@139: flexiNoteLayer->snapToFeatureFrame(m_pane, f1i, res, Layer::SnapRight); Chris@139: Chris@139: f0 = (f0i < 0 ? 0 : f0i); Chris@139: f1 = (f1i < 0 ? 0 : f1i); Chris@139: } Chris@139: Chris@139: void Chris@132: Analyser::saveState(Component c) const Chris@132: { Chris@132: bool v = isVisible(c); Chris@132: bool a = isAudible(c); Chris@132: QSettings settings; Chris@132: settings.beginGroup("Analyser"); Chris@145: settings.setValue(QString("visible-%1").arg(int(c)), v); Chris@145: settings.setValue(QString("audible-%1").arg(int(c)), a); Chris@132: settings.endGroup(); Chris@132: } Chris@132: Chris@132: void Chris@132: Analyser::loadState(Component c) Chris@132: { Chris@132: QSettings settings; Chris@132: settings.beginGroup("Analyser"); Chris@145: bool deflt = (c == Spectrogram ? false : true); Chris@145: bool v = settings.value(QString("visible-%1").arg(int(c)), deflt).toBool(); Chris@145: bool a = settings.value(QString("audible-%1").arg(int(c)), true).toBool(); Chris@132: settings.endGroup(); Chris@132: setVisible(c, v); Chris@132: setAudible(c, a); Chris@132: } Chris@132: Chris@132: void gyorgyf@45: Analyser::setIntelligentActions(bool on) gyorgyf@45: { gyorgyf@45: std::cerr << "toggle setIntelligentActions " << on << std::endl; Chris@128: Chris@128: FlexiNoteLayer *flexiNoteLayer = Chris@128: qobject_cast(m_layers[Notes]); Chris@128: if (flexiNoteLayer) { Chris@128: flexiNoteLayer->setIntelligentActions(on); Chris@70: } gyorgyf@45: } Chris@128: Chris@128: bool Chris@128: Analyser::isVisible(Component c) const Chris@128: { Chris@128: if (m_layers[c]) { Chris@128: return !m_layers[c]->isLayerDormant(m_pane); Chris@128: } else { Chris@128: return false; Chris@128: } Chris@128: } Chris@128: Chris@128: void Chris@128: Analyser::setVisible(Component c, bool v) Chris@128: { Chris@128: if (m_layers[c]) { Chris@128: m_layers[c]->setLayerDormant(m_pane, !v); Chris@133: Chris@167: if (v) { Chris@167: if (c == Notes) { Chris@167: m_paneStack->setCurrentLayer(m_pane, m_layers[c]); Chris@167: } else if (c == PitchTrack) { Chris@167: // raise the pitch track, then notes on top (if present) Chris@167: m_paneStack->setCurrentLayer(m_pane, m_layers[c]); Chris@167: if (m_layers[Notes] && Chris@167: !m_layers[Notes]->isLayerDormant(m_pane)) { Chris@167: m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]); Chris@167: } Chris@167: } Chris@133: } Chris@133: Chris@128: m_pane->layerParametersChanged(); Chris@132: saveState(c); Chris@128: } Chris@128: } Chris@128: Chris@128: bool Chris@128: Analyser::isAudible(Component c) const Chris@128: { Chris@128: if (m_layers[c]) { Chris@128: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@128: if (!params) return false; Chris@128: return params->isPlayAudible(); Chris@128: } else { Chris@128: return false; Chris@128: } Chris@128: } Chris@128: Chris@128: void Chris@128: Analyser::setAudible(Component c, bool a) Chris@128: { Chris@128: if (m_layers[c]) { Chris@128: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@128: if (!params) return; Chris@128: params->setPlayAudible(a); Chris@132: saveState(c); Chris@128: } Chris@128: } Chris@128: Chris@158: float Chris@158: Analyser::getGain(Component c) const Chris@158: { Chris@158: if (m_layers[c]) { Chris@158: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@158: if (!params) return 1.f; Chris@158: return params->getPlayGain(); Chris@158: } else { Chris@158: return 1.f; Chris@158: } Chris@158: } Chris@158: Chris@158: void Chris@158: Analyser::setGain(Component c, float gain) Chris@158: { Chris@158: if (m_layers[c]) { Chris@158: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@158: if (!params) return; Chris@158: params->setPlayGain(gain); Chris@158: saveState(c); Chris@158: } Chris@158: } Chris@158: Chris@158: float Chris@158: Analyser::getPan(Component c) const Chris@158: { Chris@158: if (m_layers[c]) { Chris@158: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@158: if (!params) return 1.f; Chris@158: return params->getPlayPan(); Chris@158: } else { Chris@158: return 1.f; Chris@158: } Chris@158: } Chris@158: Chris@158: void Chris@158: Analyser::setPan(Component c, float pan) Chris@158: { Chris@158: if (m_layers[c]) { Chris@158: PlayParameters *params = m_layers[c]->getPlayParameters(); Chris@158: if (!params) return; Chris@158: params->setPlayPan(pan); Chris@158: saveState(c); Chris@158: } Chris@158: } Chris@158: Chris@158: Chris@158: