annotate main/Analyser.cpp @ 698:ee97c742d184 tip

Default branch is now named default on git as well as hg, in case we ever want to switch to mirroring in the other direction
author Chris Cannam
date Thu, 27 Aug 2020 15:58:43 +0100
parents 90ee5448c205
children
rev   line source
Chris@6 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@6 2
Chris@6 3 /*
Chris@6 4 Tony
Chris@6 5 An intonation analysis and annotation tool
Chris@6 6 Centre for Digital Music, Queen Mary, University of London.
Chris@6 7 This file copyright 2006-2012 Chris Cannam and QMUL.
Chris@6 8
Chris@6 9 This program is free software; you can redistribute it and/or
Chris@6 10 modify it under the terms of the GNU General Public License as
Chris@6 11 published by the Free Software Foundation; either version 2 of the
Chris@6 12 License, or (at your option) any later version. See the file
Chris@6 13 COPYING included with this distribution for more information.
Chris@6 14 */
Chris@6 15
Chris@6 16 #include "Analyser.h"
Chris@6 17
Chris@6 18 #include "transform/TransformFactory.h"
Chris@6 19 #include "transform/ModelTransformer.h"
gyorgyf@14 20 #include "transform/FeatureExtractionModelTransformer.h"
Chris@6 21 #include "framework/Document.h"
Chris@6 22 #include "data/model/WaveFileModel.h"
Chris@6 23 #include "view/Pane.h"
Chris@6 24 #include "view/PaneStack.h"
Chris@6 25 #include "layer/Layer.h"
Chris@6 26 #include "layer/TimeValueLayer.h"
matthiasm@13 27 #include "layer/NoteLayer.h"
matthiasm@11 28 #include "layer/FlexiNoteLayer.h"
Chris@120 29 #include "layer/WaveformLayer.h"
Chris@6 30 #include "layer/ColourDatabase.h"
Chris@145 31 #include "layer/ColourMapper.h"
gyorgyf@16 32 #include "layer/LayerFactory.h"
Chris@145 33 #include "layer/SpectrogramLayer.h"
Chris@161 34 #include "layer/Colour3DPlotLayer.h"
Chris@199 35 #include "layer/ShowLayerCommand.h"
Chris@6 36
Chris@83 37 #include <QSettings>
Chris@341 38 #include <QMutexLocker>
Chris@83 39
Chris@163 40 using std::vector;
Chris@163 41
Chris@6 42 Analyser::Analyser() :
Chris@6 43 m_document(0),
Chris@133 44 m_paneStack(0),
Chris@188 45 m_pane(0),
Chris@188 46 m_currentCandidate(-1),
Chris@341 47 m_candidatesVisible(false),
Chris@341 48 m_currentAsyncHandle(0)
Chris@6 49 {
Chris@83 50 QSettings settings;
Chris@83 51 settings.beginGroup("LayerDefaults");
Chris@83 52 settings.setValue
Chris@83 53 ("timevalues",
Chris@83 54 QString("<layer verticalScale=\"%1\" plotStyle=\"%2\" "
Chris@83 55 "scaleMinimum=\"%3\" scaleMaximum=\"%4\"/>")
Chris@145 56 .arg(int(TimeValueLayer::AutoAlignScale))
matthiasm@182 57 .arg(int(TimeValueLayer::PlotPoints))
Chris@83 58 .arg(27.5f).arg(880.f)); // temporary values: better get the real extents of the data from the model
Chris@83 59 settings.setValue
Chris@83 60 ("flexinotes",
Chris@83 61 QString("<layer verticalScale=\"%1\"/>")
Chris@83 62 .arg(int(FlexiNoteLayer::AutoAlignScale)));
Chris@83 63 settings.endGroup();
Chris@6 64 }
Chris@6 65
Chris@6 66 Analyser::~Analyser()
Chris@6 67 {
Chris@6 68 }
Chris@6 69
Chris@673 70 std::map<QString, QVariant>
Chris@673 71 Analyser::getAnalysisSettings()
Chris@673 72 {
Chris@673 73 return { { "precision-analysis", false },
Chris@673 74 { "lowamp-analysis", true },
Chris@673 75 { "onset-analysis", true },
Chris@673 76 { "prune-analysis", true }
Chris@673 77 };
Chris@673 78 }
Chris@673 79
Chris@140 80 QString
Chris@572 81 Analyser::newFileLoaded(Document *doc, ModelId model,
Chris@6 82 PaneStack *paneStack, Pane *pane)
Chris@6 83 {
Chris@6 84 m_document = doc;
Chris@6 85 m_fileModel = model;
Chris@133 86 m_paneStack = paneStack;
Chris@6 87 m_pane = pane;
Chris@6 88
Chris@572 89 if (!ModelById::isa<WaveFileModel>(m_fileModel)) {
Chris@572 90 return "Internal error: Analyser::newFileLoaded() called with no model, or a non-WaveFileModel";
Chris@572 91 }
Chris@446 92
Chris@242 93 connect(doc, SIGNAL(layerAboutToBeDeleted(Layer *)),
Chris@242 94 this, SLOT(layerAboutToBeDeleted(Layer *)));
Chris@242 95
Chris@326 96 QSettings settings;
Chris@326 97 settings.beginGroup("Analyser");
Chris@326 98 bool autoAnalyse = settings.value("auto-analysis", true).toBool();
Chris@326 99 settings.endGroup();
Chris@326 100
Chris@326 101 return doAllAnalyses(autoAnalyse);
Chris@325 102 }
Chris@325 103
Chris@325 104 QString
Chris@325 105 Analyser::analyseExistingFile()
Chris@325 106 {
Chris@325 107 if (!m_document) return "Internal error: Analyser::analyseExistingFile() called with no document present";
Chris@325 108
Chris@325 109 if (!m_pane) return "Internal error: Analyser::analyseExistingFile() called with no pane present";
Chris@325 110
Chris@572 111 if (m_fileModel.isNone()) return "Internal error: Analyser::analyseExistingFile() called with no model present";
Chris@446 112
Chris@325 113 if (m_layers[PitchTrack]) {
Chris@325 114 m_document->removeLayerFromView(m_pane, m_layers[PitchTrack]);
Chris@325 115 m_layers[PitchTrack] = 0;
Chris@325 116 }
Chris@325 117 if (m_layers[Notes]) {
Chris@325 118 m_document->removeLayerFromView(m_pane, m_layers[Notes]);
Chris@325 119 m_layers[Notes] = 0;
Chris@325 120 }
Chris@325 121
Chris@326 122 return doAllAnalyses(true);
Chris@325 123 }
Chris@325 124
Chris@325 125 QString
Chris@326 126 Analyser::doAllAnalyses(bool withPitchTrack)
Chris@325 127 {
Chris@165 128 m_reAnalysingSelection = Selection();
Chris@165 129 m_reAnalysisCandidates.clear();
Chris@167 130 m_currentCandidate = -1;
Chris@184 131 m_candidatesVisible = false;
Chris@165 132
Chris@161 133 // Note that we need at least one main-model layer (time ruler,
Chris@161 134 // waveform or what have you). It could be hidden if we don't want
Chris@161 135 // to see it but it must exist.
Chris@6 136
Chris@161 137 QString warning, error;
Chris@161 138
Chris@260 139 cerr << "Analyser::newFileLoaded: about to check visualisations etc" << endl;
Chris@260 140
Chris@161 141 // This isn't fatal -- we can proceed without
Chris@161 142 // visualisations. Other failures are fatal though.
Chris@161 143 warning = addVisualisations();
Chris@161 144
Chris@161 145 error = addWaveform();
Chris@161 146 if (error != "") return error;
Chris@161 147
Chris@326 148 if (withPitchTrack) {
Chris@326 149 error = addAnalyses();
Chris@326 150 if (error != "") return error;
Chris@326 151 }
Chris@161 152
Chris@161 153 loadState(Audio);
Chris@161 154 loadState(PitchTrack);
Chris@161 155 loadState(Notes);
Chris@161 156 loadState(Spectrogram);
Chris@161 157
Chris@260 158 stackLayers();
Chris@260 159
Chris@161 160 emit layersChanged();
Chris@161 161
Chris@161 162 return warning;
Chris@161 163 }
Chris@161 164
Chris@226 165 void
Chris@226 166 Analyser::fileClosed()
Chris@226 167 {
Chris@226 168 cerr << "Analyser::fileClosed" << endl;
Chris@226 169 m_layers.clear();
Chris@226 170 m_reAnalysisCandidates.clear();
Chris@226 171 m_currentCandidate = -1;
Chris@226 172 m_reAnalysingSelection = Selection();
Chris@226 173 }
Chris@226 174
Chris@227 175 bool
Chris@399 176 Analyser::getDisplayFrequencyExtents(double &min, double &max)
Chris@227 177 {
Chris@227 178 if (!m_layers[Spectrogram]) return false;
Chris@227 179 return m_layers[Spectrogram]->getDisplayExtents(min, max);
Chris@227 180 }
Chris@227 181
Chris@227 182 bool
Chris@399 183 Analyser::setDisplayFrequencyExtents(double min, double max)
Chris@227 184 {
Chris@227 185 if (!m_layers[Spectrogram]) return false;
Chris@227 186 m_layers[Spectrogram]->setDisplayExtents(min, max);
Chris@242 187 return true;
Chris@227 188 }
Chris@227 189
Chris@314 190 int
Chris@314 191 Analyser::getInitialAnalysisCompletion()
Chris@314 192 {
Chris@314 193 int completion = 0;
Chris@314 194
Chris@314 195 if (m_layers[PitchTrack]) {
Chris@314 196 completion = m_layers[PitchTrack]->getCompletion(m_pane);
Chris@314 197 }
Chris@314 198
Chris@314 199 if (m_layers[Notes]) {
Chris@314 200 int c = m_layers[Notes]->getCompletion(m_pane);
Chris@314 201 if (c < completion) completion = c;
Chris@314 202 }
Chris@314 203
Chris@314 204 return completion;
Chris@314 205 }
Chris@314 206
Chris@314 207 void
Chris@576 208 Analyser::layerCompletionChanged(ModelId)
Chris@314 209 {
Chris@573 210 if (getInitialAnalysisCompletion() < 100) {
Chris@573 211 return;
Chris@573 212 }
Chris@432 213
Chris@573 214 emit initialAnalysisCompleted();
Chris@432 215
Chris@573 216 if (!m_layers[Audio]) {
Chris@573 217 return;
Chris@573 218 }
Chris@432 219
Chris@573 220 // Extend pitch-track and note layers so as to nominally end at
Chris@573 221 // the same time as the audio. This affects any time-filling done
Chris@573 222 // on export etc.
Chris@573 223
Chris@573 224 auto audioModel = ModelById::get(m_layers[Audio]->getModel());
Chris@573 225 sv_frame_t endFrame = audioModel->getEndFrame();
Chris@432 226
Chris@573 227 if (m_layers[PitchTrack]) {
Chris@573 228 auto model = ModelById::getAs<SparseTimeValueModel>
Chris@573 229 (m_layers[PitchTrack]->getModel());
Chris@573 230 if (model) {
Chris@573 231 model->extendEndFrame(endFrame);
Chris@573 232 }
Chris@573 233 }
Chris@573 234
Chris@573 235 if (m_layers[Notes]) {
Chris@573 236 auto model = ModelById::getAs<NoteModel>
Chris@573 237 (m_layers[Notes]->getModel());
Chris@573 238 if (model) {
Chris@573 239 model->extendEndFrame(endFrame);
Chris@432 240 }
Chris@314 241 }
Chris@314 242 }
Chris@314 243
Chris@161 244 QString
Chris@161 245 Analyser::addVisualisations()
Chris@161 246 {
Chris@572 247 if (m_fileModel.isNone()) return "Internal error: Analyser::addVisualisations() called with no model present";
Chris@446 248
Chris@227 249 // A spectrogram, off by default. Must go at the back because it's
Chris@227 250 // opaque
Chris@227 251
Chris@227 252 /* This is roughly what we'd do for a constant-Q spectrogram, but it
Chris@227 253 currently has issues with y-axis alignment
Chris@227 254
Chris@161 255 TransformFactory *tf = TransformFactory::getInstance();
Chris@161 256
Chris@161 257 QString name = "Constant-Q";
Chris@161 258 QString base = "vamp:cqvamp:cqvamp:";
Chris@161 259 QString out = "constantq";
Chris@6 260
Chris@161 261 QString notFound = tr("Transform \"%1\" not found, spectrogram will not be enabled.<br><br>Is the %2 Vamp plugin correctly installed?");
Chris@161 262 if (!tf->haveTransform(base + out)) {
Chris@161 263 return notFound.arg(base + out).arg(name);
Chris@161 264 }
Chris@161 265
Chris@161 266 Transform transform = tf->getDefaultTransformFor
Chris@161 267 (base + out, m_fileModel->getSampleRate());
Chris@162 268 transform.setParameter("bpo", 36);
Chris@161 269
Chris@161 270 Colour3DPlotLayer *spectrogram = qobject_cast<Colour3DPlotLayer *>
Chris@161 271 (m_document->createDerivedLayer(transform, m_fileModel));
Chris@161 272
Chris@161 273 if (!spectrogram) return tr("Transform \"%1\" did not run correctly (no layer or wrong layer type returned)").arg(base + out);
Chris@165 274 */
Chris@165 275
Chris@260 276 // As with all the visualisation layers, if we already have one in
Chris@260 277 // the pane we do not create another, just record its
Chris@260 278 // existence. (We create a new one when loading a new audio file,
Chris@260 279 // but just note the existing one when loading a complete session.)
Chris@260 280
Chris@260 281 for (int i = 0; i < m_pane->getLayerCount(); ++i) {
Chris@260 282 SpectrogramLayer *existing = qobject_cast<SpectrogramLayer *>
Chris@260 283 (m_pane->getLayer(i));
Chris@260 284 if (existing) {
Chris@260 285 cerr << "recording existing spectrogram layer" << endl;
Chris@260 286 m_layers[Spectrogram] = existing;
Chris@260 287 return "";
Chris@260 288 }
Chris@260 289 }
Chris@260 290
Chris@145 291 SpectrogramLayer *spectrogram = qobject_cast<SpectrogramLayer *>
Chris@145 292 (m_document->createMainModelLayer(LayerFactory::MelodicRangeSpectrogram));
Chris@165 293
Chris@145 294 spectrogram->setColourMap((int)ColourMapper::BlackOnWhite);
Chris@520 295 spectrogram->setNormalization(ColumnNormalization::Hybrid);
Chris@584 296 // This magical scale factor happens to get us a similar display
Chris@584 297 // to Tony v1.0
Chris@584 298 spectrogram->setGain(0.25f);
Chris@145 299 m_document->addLayerToView(m_pane, spectrogram);
Chris@145 300 spectrogram->setLayerDormant(m_pane, true);
Chris@145 301
Chris@145 302 m_layers[Spectrogram] = spectrogram;
Chris@145 303
Chris@161 304 return "";
Chris@161 305 }
Chris@161 306
Chris@161 307 QString
Chris@161 308 Analyser::addWaveform()
Chris@161 309 {
Chris@131 310 // Our waveform layer is just a shadow, light grey and taking up
Chris@260 311 // little space at the bottom.
Chris@260 312
Chris@260 313 // As with the spectrogram above, if one exists already we just
Chris@260 314 // use it
Chris@260 315 for (int i = 0; i < m_pane->getLayerCount(); ++i) {
Chris@260 316 WaveformLayer *existing = qobject_cast<WaveformLayer *>
Chris@260 317 (m_pane->getLayer(i));
Chris@260 318 if (existing) {
Chris@260 319 cerr << "recording existing waveform layer" << endl;
Chris@260 320 m_layers[Audio] = existing;
Chris@260 321 return "";
Chris@260 322 }
Chris@260 323 }
Chris@120 324
Chris@120 325 WaveformLayer *waveform = qobject_cast<WaveformLayer *>
Chris@120 326 (m_document->createMainModelLayer(LayerFactory::Waveform));
Chris@120 327
Chris@120 328 waveform->setMiddleLineHeight(0.9);
Chris@120 329 waveform->setShowMeans(false); // too small & pale for this
Chris@120 330 waveform->setBaseColour
Chris@120 331 (ColourDatabase::getInstance()->getColourIndex(tr("Grey")));
Chris@573 332 auto params = waveform->getPlayParameters();
Chris@433 333 if (params) {
Chris@433 334 params->setPlayPan(-1);
Chris@433 335 params->setPlayGain(1);
Chris@433 336 }
Chris@128 337
Chris@128 338 m_document->addLayerToView(m_pane, waveform);
Chris@120 339
Chris@128 340 m_layers[Audio] = waveform;
Chris@161 341 return "";
Chris@161 342 }
Chris@161 343
Chris@161 344 QString
Chris@161 345 Analyser::addAnalyses()
Chris@161 346 {
Chris@572 347 auto waveFileModel = ModelById::getAs<WaveFileModel>(m_fileModel);
Chris@572 348 if (!waveFileModel) {
Chris@572 349 return "Internal error: Analyser::addAnalyses() called with no model present";
Chris@572 350 }
Chris@572 351
Chris@260 352 // As with the spectrogram above, if these layers exist we use
Chris@260 353 // them
Chris@260 354 TimeValueLayer *existingPitch = 0;
Chris@260 355 FlexiNoteLayer *existingNotes = 0;
Chris@260 356 for (int i = 0; i < m_pane->getLayerCount(); ++i) {
Chris@260 357 if (!existingPitch) {
Chris@260 358 existingPitch = qobject_cast<TimeValueLayer *>(m_pane->getLayer(i));
Chris@260 359 }
Chris@260 360 if (!existingNotes) {
Chris@260 361 existingNotes = qobject_cast<FlexiNoteLayer *>(m_pane->getLayer(i));
Chris@260 362 }
Chris@260 363 }
Chris@260 364 if (existingPitch && existingNotes) {
Chris@260 365 cerr << "recording existing pitch and notes layers" << endl;
Chris@260 366 m_layers[PitchTrack] = existingPitch;
Chris@260 367 m_layers[Notes] = existingNotes;
Chris@260 368 return "";
Chris@326 369 } else {
Chris@326 370 if (existingPitch) {
Chris@326 371 m_document->removeLayerFromView(m_pane, existingPitch);
Chris@326 372 m_layers[PitchTrack] = 0;
Chris@326 373 }
Chris@326 374 if (existingNotes) {
Chris@326 375 m_document->removeLayerFromView(m_pane, existingNotes);
Chris@326 376 m_layers[Notes] = 0;
Chris@326 377 }
Chris@260 378 }
Chris@260 379
Chris@161 380 TransformFactory *tf = TransformFactory::getInstance();
Chris@161 381
Chris@161 382 QString plugname = "pYIN";
Chris@161 383 QString base = "vamp:pyin:pyin:";
Chris@161 384 QString f0out = "smoothedpitchtrack";
Chris@161 385 QString noteout = "notes";
Chris@120 386
Chris@83 387 Transforms transforms;
Chris@138 388
Chris@138 389 /*!!! we could have more than one pitch track...
Chris@138 390 QString cx = "vamp:cepstral-pitchtracker:cepstral-pitchtracker:f0";
Chris@138 391 if (tf->haveTransform(cx)) {
Chris@138 392 Transform tx = tf->getDefaultTransformFor(cx);
Chris@138 393 TimeValueLayer *lx = qobject_cast<TimeValueLayer *>
Chris@138 394 (m_document->createDerivedLayer(tx, m_fileModel));
Chris@138 395 lx->setVerticalScale(TimeValueLayer::AutoAlignScale);
Chris@138 396 lx->setBaseColour(ColourDatabase::getInstance()->getColourIndex(tr("Bright Red")));
Chris@138 397 m_document->addLayerToView(m_pane, lx);
Chris@138 398 }
Chris@138 399 */
Chris@138 400
Chris@140 401 QString notFound = tr("Transform \"%1\" not found. Unable to analyse audio file.<br><br>Is the %2 Vamp plugin correctly installed?");
Chris@140 402 if (!tf->haveTransform(base + f0out)) {
Chris@140 403 return notFound.arg(base + f0out).arg(plugname);
Chris@140 404 }
Chris@140 405 if (!tf->haveTransform(base + noteout)) {
Chris@140 406 return notFound.arg(base + noteout).arg(plugname);
Chris@6 407 }
Chris@6 408
Chris@326 409 QSettings settings;
Chris@326 410 settings.beginGroup("Analyser");
Chris@673 411
Chris@673 412 bool precise = false, lowamp = true, onset = true, prune = true;
Chris@673 413
Chris@673 414 std::map<QString, bool &> flags {
Chris@673 415 { "precision-analysis", precise },
Chris@673 416 { "lowamp-analysis", lowamp },
Chris@673 417 { "onset-analysis", onset },
Chris@673 418 { "prune-analysis", prune }
Chris@673 419 };
Chris@673 420
Chris@673 421 auto keyMap = getAnalysisSettings();
Chris@673 422
Chris@673 423 for (auto p: flags) {
Chris@673 424 auto ki = keyMap.find(p.first);
Chris@673 425 if (ki != keyMap.end()) {
Chris@673 426 p.second = settings.value(ki->first, ki->second).toBool();
Chris@673 427 } else {
Chris@673 428 throw std::logic_error("Internal error: One or more analysis settings keys not found in map: check addAnalyses and getAnalysisSettings");
Chris@673 429 }
Chris@673 430 }
Chris@673 431
matthiasm@345 432 settings.endGroup();
matthiasm@345 433
Chris@83 434 Transform t = tf->getDefaultTransformFor
Chris@572 435 (base + f0out, waveFileModel->getSampleRate());
Chris@83 436 t.setStepSize(256);
Chris@83 437 t.setBlockSize(2048);
Chris@6 438
Chris@326 439 if (precise) {
Chris@326 440 cerr << "setting parameters for precise mode" << endl;
Chris@326 441 t.setParameter("precisetime", 1);
Chris@326 442 } else {
Chris@326 443 cerr << "setting parameters for vague mode" << endl;
Chris@326 444 t.setParameter("precisetime", 0);
Chris@326 445 }
Chris@326 446
matthiasm@345 447 if (lowamp) {
matthiasm@345 448 cerr << "setting parameters for lowamp suppression" << endl;
matthiasm@422 449 t.setParameter("lowampsuppression", 0.2f);
matthiasm@345 450 } else {
matthiasm@345 451 cerr << "setting parameters for no lowamp suppression" << endl;
matthiasm@345 452 t.setParameter("lowampsuppression", 0.0f);
matthiasm@345 453 }
matthiasm@345 454
matthiasm@423 455 if (onset) {
matthiasm@423 456 cerr << "setting parameters for increased onset sensitivity" << endl;
matthiasm@423 457 t.setParameter("onsetsensitivity", 0.7f);
matthiasm@423 458 } else {
matthiasm@423 459 cerr << "setting parameters for non-increased onset sensitivity" << endl;
matthiasm@423 460 t.setParameter("onsetsensitivity", 0.0f);
matthiasm@423 461 }
matthiasm@423 462
matthiasm@423 463 if (prune) {
matthiasm@423 464 cerr << "setting parameters for duration pruning" << endl;
matthiasm@423 465 t.setParameter("prunethresh", 0.1f);
matthiasm@423 466 } else {
matthiasm@423 467 cerr << "setting parameters for no duration pruning" << endl;
matthiasm@423 468 t.setParameter("prunethresh", 0.0f);
matthiasm@423 469 }
matthiasm@423 470
Chris@83 471 transforms.push_back(t);
Chris@83 472
Chris@83 473 t.setOutput(noteout);
Chris@83 474
Chris@83 475 transforms.push_back(t);
Chris@83 476
Chris@83 477 std::vector<Layer *> layers =
Chris@83 478 m_document->createDerivedLayers(transforms, m_fileModel);
Chris@83 479
Chris@162 480 for (int i = 0; i < (int)layers.size(); ++i) {
Chris@83 481
Chris@162 482 FlexiNoteLayer *f = qobject_cast<FlexiNoteLayer *>(layers[i]);
Chris@162 483 TimeValueLayer *t = qobject_cast<TimeValueLayer *>(layers[i]);
Chris@162 484
Chris@162 485 if (f) m_layers[Notes] = f;
Chris@162 486 if (t) m_layers[PitchTrack] = t;
Chris@162 487
Chris@162 488 m_document->addLayerToView(m_pane, layers[i]);
Chris@6 489 }
Chris@161 490
Chris@162 491 ColourDatabase *cdb = ColourDatabase::getInstance();
Chris@162 492
Chris@162 493 TimeValueLayer *pitchLayer =
Chris@162 494 qobject_cast<TimeValueLayer *>(m_layers[PitchTrack]);
Chris@162 495 if (pitchLayer) {
Chris@162 496 pitchLayer->setBaseColour(cdb->getColourIndex(tr("Black")));
Chris@573 497 auto params = pitchLayer->getPlayParameters();
Chris@433 498 if (params) {
Chris@433 499 params->setPlayPan(1);
Chris@433 500 params->setPlayGain(0.5);
Chris@433 501 }
Chris@576 502 connect(pitchLayer, SIGNAL(modelCompletionChanged(ModelId)),
Chris@576 503 this, SLOT(layerCompletionChanged(ModelId)));
Chris@162 504 }
Chris@162 505
Chris@162 506 FlexiNoteLayer *flexiNoteLayer =
Chris@162 507 qobject_cast<FlexiNoteLayer *>(m_layers[Notes]);
Chris@162 508 if (flexiNoteLayer) {
Chris@162 509 flexiNoteLayer->setBaseColour(cdb->getColourIndex(tr("Bright Blue")));
Chris@573 510 auto params = flexiNoteLayer->getPlayParameters();
Chris@433 511 if (params) {
Chris@433 512 params->setPlayPan(1);
Chris@433 513 params->setPlayGain(0.5);
Chris@433 514 }
Chris@576 515 connect(flexiNoteLayer, SIGNAL(modelCompletionChanged(ModelId)),
Chris@576 516 this, SLOT(layerCompletionChanged(ModelId)));
Chris@403 517 connect(flexiNoteLayer, SIGNAL(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float)),
Chris@403 518 this, SLOT(reAnalyseRegion(sv_frame_t, sv_frame_t, float, float)));
Chris@398 519 connect(flexiNoteLayer, SIGNAL(materialiseReAnalysis()),
Chris@398 520 this, SLOT(materialiseReAnalysis()));
Chris@162 521 }
Chris@162 522
Chris@162 523 return "";
Chris@162 524 }
Chris@162 525
Chris@396 526 void
Chris@403 527 Analyser::reAnalyseRegion(sv_frame_t frame0, sv_frame_t frame1, float freq0, float freq1)
Chris@396 528 {
Chris@396 529 cerr << "Analyser::reAnalyseRegion(" << frame0 << ", " << frame1
Chris@396 530 << ", " << freq0 << ", " << freq1 << ")" << endl;
Chris@396 531 showPitchCandidates(true);
Chris@396 532 (void)reAnalyseSelection(Selection(frame0, frame1),
Chris@396 533 FrequencyRange(freq0, freq1));
Chris@396 534 }
Chris@396 535
Chris@398 536 void
Chris@398 537 Analyser::materialiseReAnalysis()
Chris@398 538 {
Chris@398 539 if (m_reAnalysingSelection.isEmpty()) return;
Chris@398 540 switchPitchCandidate(m_reAnalysingSelection, true); // or false, doesn't matter
Chris@398 541 }
Chris@398 542
Chris@162 543 QString
Chris@192 544 Analyser::reAnalyseSelection(Selection sel, FrequencyRange range)
Chris@162 545 {
Chris@341 546 QMutexLocker locker(&m_asyncMutex);
Chris@341 547
Chris@572 548 auto waveFileModel = ModelById::getAs<WaveFileModel>(m_fileModel);
Chris@572 549 if (!waveFileModel) {
Chris@572 550 return "Internal error: Analyser::reAnalyseSelection() called with no model present";
Chris@572 551 }
Chris@572 552
Chris@396 553 if (!m_reAnalysingSelection.isEmpty()) {
Chris@396 554 if (sel == m_reAnalysingSelection && range == m_reAnalysingRange) {
Chris@396 555 cerr << "selection & range are same as current analysis, ignoring" << endl;
Chris@396 556 return "";
Chris@396 557 }
Chris@396 558 }
Chris@396 559
Chris@396 560 if (sel.isEmpty()) return "";
Chris@165 561
Chris@341 562 if (m_currentAsyncHandle) {
Chris@341 563 m_document->cancelAsyncLayerCreation(m_currentAsyncHandle);
Chris@341 564 }
Chris@341 565
Chris@252 566 if (!m_reAnalysisCandidates.empty()) {
Chris@252 567 CommandHistory::getInstance()->startCompoundOperation
Chris@252 568 (tr("Discard Previous Candidates"), true);
Chris@252 569 discardPitchCandidates();
Chris@252 570 CommandHistory::getInstance()->endCompoundOperation();
Chris@252 571 }
Chris@199 572
Chris@194 573 m_reAnalysingSelection = sel;
Chris@396 574 m_reAnalysingRange = range;
Chris@167 575
Chris@194 576 m_preAnalysis = Clipboard();
Chris@194 577 Layer *myLayer = m_layers[PitchTrack];
Chris@194 578 if (myLayer) {
Chris@194 579 myLayer->copy(m_pane, sel, m_preAnalysis);
Chris@194 580 }
Chris@165 581
Chris@162 582 TransformFactory *tf = TransformFactory::getInstance();
Chris@162 583
Chris@223 584 QString plugname1 = "pYIN";
Chris@223 585 QString plugname2 = "CHP";
Chris@223 586
Chris@162 587 QString base = "vamp:pyin:localcandidatepyin:";
Chris@162 588 QString out = "pitchtrackcandidates";
Chris@162 589
Chris@192 590 if (range.isConstrained()) {
Chris@223 591 base = "vamp:chp:constrainedharmonicpeak:";
Chris@223 592 out = "peak";
Chris@192 593 }
Chris@192 594
Chris@162 595 Transforms transforms;
Chris@162 596
Chris@223 597 QString notFound = tr("Transform \"%1\" not found. Unable to perform interactive analysis.<br><br>Are the %2 and %3 Vamp plugins correctly installed?");
Chris@162 598 if (!tf->haveTransform(base + out)) {
Chris@223 599 return notFound.arg(base + out).arg(plugname1).arg(plugname2);
Chris@162 600 }
Chris@162 601
Chris@162 602 Transform t = tf->getDefaultTransformFor
Chris@572 603 (base + out, waveFileModel->getSampleRate());
Chris@162 604 t.setStepSize(256);
Chris@162 605 t.setBlockSize(2048);
Chris@162 606
Chris@192 607 if (range.isConstrained()) {
Chris@399 608 t.setParameter("minfreq", float(range.min));
Chris@399 609 t.setParameter("maxfreq", float(range.max));
Chris@223 610 t.setBlockSize(4096);
Chris@192 611 }
Chris@192 612
matthiasm@273 613 // get time stamps that align with the 256-sample grid of the original extraction
Chris@399 614 const sv_frame_t grid = 256;
Chris@399 615 sv_frame_t startSample = (sel.getStartFrame() / grid) * grid;
Chris@399 616 if (startSample < sel.getStartFrame()) startSample += grid;
Chris@399 617 sv_frame_t endSample = (sel.getEndFrame() / grid) * grid;
Chris@399 618 if (endSample < sel.getEndFrame()) endSample += grid;
matthiasm@273 619 if (!range.isConstrained()) {
Chris@399 620 startSample -= 4*grid; // 4*256 is for 4 frames offset due to timestamp shift
Chris@399 621 endSample -= 4*grid;
matthiasm@273 622 } else {
Chris@399 623 endSample -= 9*grid; // MM says: not sure what the CHP plugin does there
matthiasm@273 624 }
Chris@572 625 RealTime start = RealTime::frame2RealTime(startSample, waveFileModel->getSampleRate());
Chris@572 626 RealTime end = RealTime::frame2RealTime(endSample, waveFileModel->getSampleRate());
Chris@164 627
Chris@164 628 RealTime duration;
Chris@164 629
Chris@164 630 if (sel.getEndFrame() > sel.getStartFrame()) {
Chris@164 631 duration = end - start;
Chris@164 632 }
Chris@164 633
Chris@200 634 cerr << "Analyser::reAnalyseSelection: start " << start << " end " << end << " original selection start " << sel.getStartFrame() << " end " << sel.getEndFrame() << " duration " << duration << endl;
Chris@200 635
Chris@200 636 if (duration <= RealTime::zeroTime) {
Chris@200 637 cerr << "Analyser::reAnalyseSelection: duration <= 0, not analysing" << endl;
Chris@200 638 return "";
Chris@200 639 }
Chris@200 640
Chris@164 641 t.setStartTime(start);
Chris@164 642 t.setDuration(duration);
Chris@162 643
Chris@162 644 transforms.push_back(t);
Chris@341 645
Chris@341 646 m_currentAsyncHandle =
Chris@573 647 m_document->createDerivedLayersAsync(transforms, m_fileModel, this);
Chris@162 648
Chris@163 649 return "";
Chris@163 650 }
Chris@162 651
Chris@184 652 bool
Chris@184 653 Analyser::arePitchCandidatesShown() const
Chris@184 654 {
Chris@184 655 return m_candidatesVisible;
Chris@184 656 }
Chris@184 657
Chris@184 658 void
Chris@184 659 Analyser::showPitchCandidates(bool shown)
Chris@184 660 {
Chris@184 661 if (m_candidatesVisible == shown) return;
Chris@184 662
Chris@184 663 foreach (Layer *layer, m_reAnalysisCandidates) {
Chris@184 664 if (shown) {
Chris@199 665 CommandHistory::getInstance()->addCommand
Chris@199 666 (new ShowLayerCommand(m_pane, layer, true,
Chris@199 667 tr("Show Pitch Candidates")));
Chris@184 668 } else {
Chris@199 669 CommandHistory::getInstance()->addCommand
Chris@199 670 (new ShowLayerCommand(m_pane, layer, false,
Chris@199 671 tr("Hide Pitch Candidates")));
Chris@184 672 }
Chris@184 673 }
Chris@184 674
Chris@184 675 m_candidatesVisible = shown;
Chris@184 676 }
Chris@184 677
Chris@163 678 void
Chris@341 679 Analyser::layersCreated(Document::LayerCreationAsyncHandle handle,
Chris@341 680 vector<Layer *> primary,
Chris@163 681 vector<Layer *> additional)
Chris@163 682 {
Chris@349 683 {
Chris@349 684 QMutexLocker locker(&m_asyncMutex);
Chris@165 685
Chris@349 686 if (handle != m_currentAsyncHandle ||
Chris@349 687 m_reAnalysingSelection == Selection()) {
Chris@349 688 // We don't want these!
Chris@349 689 for (int i = 0; i < (int)primary.size(); ++i) {
Chris@349 690 m_document->deleteLayer(primary[i]);
Chris@349 691 }
Chris@349 692 for (int i = 0; i < (int)additional.size(); ++i) {
Chris@349 693 m_document->deleteLayer(additional[i]);
Chris@349 694 }
Chris@349 695 return;
Chris@349 696 }
Chris@349 697 m_currentAsyncHandle = 0;
Chris@349 698
Chris@349 699 CommandHistory::getInstance()->startCompoundOperation
Chris@349 700 (tr("Re-Analyse Selection"), true);
Chris@349 701
Chris@349 702 m_reAnalysisCandidates.clear();
Chris@349 703
Chris@349 704 vector<Layer *> all;
Chris@226 705 for (int i = 0; i < (int)primary.size(); ++i) {
Chris@349 706 all.push_back(primary[i]);
Chris@226 707 }
Chris@226 708 for (int i = 0; i < (int)additional.size(); ++i) {
Chris@349 709 all.push_back(additional[i]);
Chris@226 710 }
Chris@349 711
Chris@349 712 for (int i = 0; i < (int)all.size(); ++i) {
Chris@349 713 TimeValueLayer *t = qobject_cast<TimeValueLayer *>(all[i]);
Chris@349 714 if (t) {
Chris@573 715 auto params = t->getPlayParameters();
Chris@349 716 if (params) {
Chris@349 717 params->setPlayAudible(false);
Chris@349 718 }
Chris@349 719 t->setBaseColour
Chris@349 720 (ColourDatabase::getInstance()->getColourIndex(tr("Bright Orange")));
Chris@349 721 t->setPresentationName("candidate");
Chris@349 722 m_document->addLayerToView(m_pane, t);
Chris@349 723 m_reAnalysisCandidates.push_back(t);
Chris@570 724 /*
Chris@428 725 cerr << "New re-analysis candidate model has "
Chris@570 726 << ((SparseTimeValueModel *)t->getModel())->getAllEvents().size() << " point(s)" << endl;
Chris@570 727 */
Chris@349 728 }
Chris@349 729 }
Chris@349 730
Chris@349 731 if (!all.empty()) {
Chris@349 732 bool show = m_candidatesVisible;
Chris@349 733 m_candidatesVisible = !show; // to ensure the following takes effect
Chris@349 734 showPitchCandidates(show);
Chris@349 735 }
Chris@349 736
Chris@349 737 CommandHistory::getInstance()->endCompoundOperation();
Chris@226 738 }
Chris@199 739
Chris@187 740 emit layersChanged();
Chris@6 741 }
Chris@6 742
Chris@198 743 bool
Chris@198 744 Analyser::haveHigherPitchCandidate() const
Chris@198 745 {
Chris@198 746 if (m_reAnalysisCandidates.empty()) return false;
Chris@198 747 return (m_currentCandidate < 0 ||
Chris@198 748 (m_currentCandidate + 1 < (int)m_reAnalysisCandidates.size()));
Chris@198 749 }
Chris@198 750
Chris@198 751 bool
Chris@198 752 Analyser::haveLowerPitchCandidate() const
Chris@198 753 {
Chris@198 754 if (m_reAnalysisCandidates.empty()) return false;
Chris@198 755 return (m_currentCandidate < 0 || m_currentCandidate >= 1);
Chris@198 756 }
Chris@198 757
gyorgyf@45 758 void
Chris@167 759 Analyser::switchPitchCandidate(Selection sel, bool up)
Chris@167 760 {
Chris@167 761 if (m_reAnalysisCandidates.empty()) return;
Chris@167 762
Chris@167 763 if (up) {
Chris@167 764 m_currentCandidate = m_currentCandidate + 1;
Chris@168 765 if (m_currentCandidate >= (int)m_reAnalysisCandidates.size()) {
Chris@167 766 m_currentCandidate = 0;
Chris@167 767 }
Chris@167 768 } else {
Chris@167 769 m_currentCandidate = m_currentCandidate - 1;
Chris@167 770 if (m_currentCandidate < 0) {
Chris@168 771 m_currentCandidate = (int)m_reAnalysisCandidates.size() - 1;
Chris@167 772 }
Chris@167 773 }
Chris@167 774
Chris@167 775 Layer *pitchTrack = m_layers[PitchTrack];
Chris@167 776 if (!pitchTrack) return;
Chris@167 777
Chris@167 778 Clipboard clip;
Chris@167 779 pitchTrack->deleteSelection(sel);
Chris@167 780 m_reAnalysisCandidates[m_currentCandidate]->copy(m_pane, sel, clip);
Chris@167 781 pitchTrack->paste(m_pane, clip, 0, false);
Chris@264 782
Chris@264 783 stackLayers();
Chris@260 784 }
Chris@167 785
Chris@260 786 void
Chris@260 787 Analyser::stackLayers()
Chris@260 788 {
Chris@167 789 // raise the pitch track, then notes on top (if present)
Chris@260 790 if (m_layers[PitchTrack]) {
Chris@260 791 m_paneStack->setCurrentLayer(m_pane, m_layers[PitchTrack]);
Chris@260 792 }
Chris@167 793 if (m_layers[Notes] && !m_layers[Notes]->isLayerDormant(m_pane)) {
Chris@167 794 m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]);
Chris@167 795 }
Chris@167 796 }
Chris@167 797
Chris@167 798 void
Chris@168 799 Analyser::shiftOctave(Selection sel, bool up)
Chris@168 800 {
Chris@168 801 float factor = (up ? 2.f : 0.5f);
Chris@168 802
Chris@168 803 vector<Layer *> actOn;
Chris@168 804
Chris@168 805 Layer *pitchTrack = m_layers[PitchTrack];
Chris@168 806 if (pitchTrack) actOn.push_back(pitchTrack);
Chris@168 807
Chris@168 808 foreach (Layer *layer, actOn) {
Chris@168 809
Chris@168 810 Clipboard clip;
Chris@168 811 layer->copy(m_pane, sel, clip);
Chris@168 812 layer->deleteSelection(sel);
Chris@168 813
Chris@168 814 Clipboard shifted;
Chris@570 815 foreach (Event e, clip.getPoints()) {
Chris@570 816 if (e.hasValue()) {
Chris@570 817 Event se = e.withValue(e.getValue() * factor);
Chris@570 818 shifted.addPoint(se);
Chris@168 819 } else {
Chris@570 820 shifted.addPoint(e);
Chris@168 821 }
Chris@168 822 }
Chris@168 823
Chris@168 824 layer->paste(m_pane, shifted, 0, false);
Chris@168 825 }
Chris@168 826 }
Chris@168 827
Chris@168 828 void
Chris@184 829 Analyser::deletePitches(Selection sel)
Chris@168 830 {
Chris@168 831 Layer *pitchTrack = m_layers[PitchTrack];
Chris@168 832 if (!pitchTrack) return;
Chris@168 833
Chris@168 834 pitchTrack->deleteSelection(sel);
Chris@168 835 }
Chris@168 836
Chris@168 837 void
Chris@199 838 Analyser::abandonReAnalysis(Selection sel)
Chris@167 839 {
Chris@252 840 // A compound command is already in progress
Chris@252 841
Chris@199 842 discardPitchCandidates();
Chris@194 843
Chris@194 844 Layer *myLayer = m_layers[PitchTrack];
Chris@194 845 if (!myLayer) return;
Chris@194 846 myLayer->deleteSelection(sel);
Chris@194 847 myLayer->paste(m_pane, m_preAnalysis, 0, false);
Chris@167 848 }
Chris@167 849
Chris@167 850 void
Chris@269 851 Analyser::clearReAnalysis()
Chris@269 852 {
Chris@269 853 discardPitchCandidates();
Chris@269 854 }
Chris@269 855
Chris@269 856 void
Chris@199 857 Analyser::discardPitchCandidates()
Chris@199 858 {
Chris@242 859 if (!m_reAnalysisCandidates.empty()) {
Chris@252 860 // We don't use a compound command here, because we may be
Chris@252 861 // already in one. Caller bears responsibility for doing that
Chris@242 862 foreach (Layer *layer, m_reAnalysisCandidates) {
Chris@242 863 // This will cause the layer to be deleted later (ownership is
Chris@242 864 // transferred to the remove command)
Chris@242 865 m_document->removeLayerFromView(m_pane, layer);
Chris@242 866 }
Chris@243 867 m_reAnalysisCandidates.clear();
Chris@199 868 }
Chris@199 869
Chris@199 870 m_currentCandidate = -1;
Chris@199 871 m_reAnalysingSelection = Selection();
Chris@199 872 m_candidatesVisible = false;
Chris@199 873 }
Chris@199 874
Chris@199 875 void
Chris@242 876 Analyser::layerAboutToBeDeleted(Layer *doomed)
Chris@242 877 {
Chris@242 878 cerr << "Analyser::layerAboutToBeDeleted(" << doomed << ")" << endl;
Chris@242 879
Chris@242 880 vector<Layer *> notDoomed;
Chris@242 881
Chris@242 882 foreach (Layer *layer, m_reAnalysisCandidates) {
Chris@242 883 if (layer != doomed) {
Chris@242 884 notDoomed.push_back(layer);
Chris@242 885 }
Chris@242 886 }
Chris@242 887
Chris@242 888 m_reAnalysisCandidates = notDoomed;
Chris@242 889 }
Chris@242 890
Chris@242 891 void
Chris@174 892 Analyser::takePitchTrackFrom(Layer *otherLayer)
Chris@174 893 {
Chris@174 894 Layer *myLayer = m_layers[PitchTrack];
Chris@573 895 if (!myLayer || !otherLayer) return;
Chris@573 896
Chris@573 897 auto myModel = ModelById::get(myLayer->getModel());
Chris@573 898 auto otherModel = ModelById::get(otherLayer->getModel());
Chris@573 899 if (!myModel || !otherModel) return;
Chris@174 900
Chris@174 901 Clipboard clip;
Chris@573 902
Chris@573 903 Selection sel = Selection(myModel->getStartFrame(),
Chris@573 904 myModel->getEndFrame());
Chris@174 905 myLayer->deleteSelection(sel);
Chris@174 906
Chris@573 907 sel = Selection(otherModel->getStartFrame(),
Chris@573 908 otherModel->getEndFrame());
Chris@174 909 otherLayer->copy(m_pane, sel, clip);
Chris@174 910
Chris@441 911 // Remove all pitches <= 0Hz -- we now save absent pitches as 0Hz
Chris@441 912 // values when exporting a pitch track, so we need to exclude them
Chris@441 913 // here when importing again
Chris@570 914 EventVector after;
Chris@441 915 int excl = 0;
Chris@570 916 for (const auto &p: clip.getPoints()) {
Chris@570 917 if (p.hasValue() && p.getValue() > 0.f) {
Chris@441 918 after.push_back(p);
Chris@441 919 } else {
Chris@441 920 ++excl;
Chris@441 921 }
Chris@441 922 }
Chris@441 923 clip.setPoints(after);
Chris@441 924
Chris@174 925 myLayer->paste(m_pane, clip, 0, false);
Chris@174 926 }
Chris@174 927
Chris@174 928 void
Chris@399 929 Analyser::getEnclosingSelectionScope(sv_frame_t f, sv_frame_t &f0, sv_frame_t &f1)
Chris@139 930 {
Chris@139 931 FlexiNoteLayer *flexiNoteLayer =
Chris@139 932 qobject_cast<FlexiNoteLayer *>(m_layers[Notes]);
Chris@139 933
Chris@399 934 sv_frame_t f0i = f, f1i = f;
Chris@355 935 int res = 1;
Chris@139 936
Chris@139 937 if (!flexiNoteLayer) {
Chris@139 938 f0 = f1 = f;
Chris@139 939 return;
Chris@139 940 }
Chris@139 941
Chris@641 942 flexiNoteLayer->snapToFeatureFrame(m_pane, f0i, res, Layer::SnapLeft, -1);
Chris@641 943 flexiNoteLayer->snapToFeatureFrame(m_pane, f1i, res, Layer::SnapRight, -1);
Chris@139 944
Chris@139 945 f0 = (f0i < 0 ? 0 : f0i);
Chris@139 946 f1 = (f1i < 0 ? 0 : f1i);
Chris@139 947 }
Chris@139 948
Chris@139 949 void
Chris@132 950 Analyser::saveState(Component c) const
Chris@132 951 {
Chris@132 952 bool v = isVisible(c);
Chris@132 953 bool a = isAudible(c);
Chris@132 954 QSettings settings;
Chris@132 955 settings.beginGroup("Analyser");
Chris@145 956 settings.setValue(QString("visible-%1").arg(int(c)), v);
Chris@145 957 settings.setValue(QString("audible-%1").arg(int(c)), a);
Chris@132 958 settings.endGroup();
Chris@132 959 }
Chris@132 960
Chris@132 961 void
Chris@132 962 Analyser::loadState(Component c)
Chris@132 963 {
Chris@132 964 QSettings settings;
Chris@132 965 settings.beginGroup("Analyser");
Chris@145 966 bool deflt = (c == Spectrogram ? false : true);
Chris@145 967 bool v = settings.value(QString("visible-%1").arg(int(c)), deflt).toBool();
Chris@145 968 bool a = settings.value(QString("audible-%1").arg(int(c)), true).toBool();
Chris@132 969 settings.endGroup();
Chris@132 970 setVisible(c, v);
Chris@132 971 setAudible(c, a);
Chris@132 972 }
Chris@132 973
Chris@132 974 void
gyorgyf@45 975 Analyser::setIntelligentActions(bool on)
gyorgyf@45 976 {
gyorgyf@45 977 std::cerr << "toggle setIntelligentActions " << on << std::endl;
Chris@128 978
Chris@128 979 FlexiNoteLayer *flexiNoteLayer =
Chris@128 980 qobject_cast<FlexiNoteLayer *>(m_layers[Notes]);
Chris@128 981 if (flexiNoteLayer) {
Chris@128 982 flexiNoteLayer->setIntelligentActions(on);
Chris@70 983 }
gyorgyf@45 984 }
Chris@128 985
Chris@128 986 bool
Chris@128 987 Analyser::isVisible(Component c) const
Chris@128 988 {
Chris@128 989 if (m_layers[c]) {
Chris@128 990 return !m_layers[c]->isLayerDormant(m_pane);
Chris@128 991 } else {
Chris@128 992 return false;
Chris@128 993 }
Chris@128 994 }
Chris@128 995
Chris@128 996 void
Chris@128 997 Analyser::setVisible(Component c, bool v)
Chris@128 998 {
Chris@128 999 if (m_layers[c]) {
Chris@128 1000 m_layers[c]->setLayerDormant(m_pane, !v);
Chris@133 1001
Chris@167 1002 if (v) {
Chris@167 1003 if (c == Notes) {
Chris@167 1004 m_paneStack->setCurrentLayer(m_pane, m_layers[c]);
Chris@167 1005 } else if (c == PitchTrack) {
Chris@167 1006 // raise the pitch track, then notes on top (if present)
Chris@167 1007 m_paneStack->setCurrentLayer(m_pane, m_layers[c]);
Chris@167 1008 if (m_layers[Notes] &&
Chris@167 1009 !m_layers[Notes]->isLayerDormant(m_pane)) {
Chris@167 1010 m_paneStack->setCurrentLayer(m_pane, m_layers[Notes]);
Chris@167 1011 }
Chris@167 1012 }
Chris@133 1013 }
Chris@133 1014
Chris@128 1015 m_pane->layerParametersChanged();
Chris@132 1016 saveState(c);
Chris@128 1017 }
Chris@128 1018 }
Chris@128 1019
Chris@128 1020 bool
Chris@128 1021 Analyser::isAudible(Component c) const
Chris@128 1022 {
Chris@128 1023 if (m_layers[c]) {
Chris@573 1024 auto params = m_layers[c]->getPlayParameters();
Chris@128 1025 if (!params) return false;
Chris@128 1026 return params->isPlayAudible();
Chris@128 1027 } else {
Chris@128 1028 return false;
Chris@128 1029 }
Chris@128 1030 }
Chris@128 1031
Chris@128 1032 void
Chris@128 1033 Analyser::setAudible(Component c, bool a)
Chris@128 1034 {
Chris@128 1035 if (m_layers[c]) {
Chris@573 1036 auto params = m_layers[c]->getPlayParameters();
Chris@128 1037 if (!params) return;
Chris@128 1038 params->setPlayAudible(a);
Chris@132 1039 saveState(c);
Chris@128 1040 }
Chris@128 1041 }
Chris@128 1042
Chris@158 1043 float
Chris@158 1044 Analyser::getGain(Component c) const
Chris@158 1045 {
Chris@158 1046 if (m_layers[c]) {
Chris@573 1047 auto params = m_layers[c]->getPlayParameters();
Chris@158 1048 if (!params) return 1.f;
Chris@158 1049 return params->getPlayGain();
Chris@158 1050 } else {
Chris@158 1051 return 1.f;
Chris@158 1052 }
Chris@158 1053 }
Chris@158 1054
Chris@158 1055 void
Chris@158 1056 Analyser::setGain(Component c, float gain)
Chris@158 1057 {
Chris@158 1058 if (m_layers[c]) {
Chris@573 1059 auto params = m_layers[c]->getPlayParameters();
Chris@158 1060 if (!params) return;
Chris@158 1061 params->setPlayGain(gain);
Chris@158 1062 saveState(c);
Chris@158 1063 }
Chris@158 1064 }
Chris@158 1065
Chris@158 1066 float
Chris@158 1067 Analyser::getPan(Component c) const
Chris@158 1068 {
Chris@158 1069 if (m_layers[c]) {
Chris@573 1070 auto params = m_layers[c]->getPlayParameters();
Chris@158 1071 if (!params) return 1.f;
Chris@158 1072 return params->getPlayPan();
Chris@158 1073 } else {
Chris@158 1074 return 1.f;
Chris@158 1075 }
Chris@158 1076 }
Chris@158 1077
Chris@158 1078 void
Chris@158 1079 Analyser::setPan(Component c, float pan)
Chris@158 1080 {
Chris@158 1081 if (m_layers[c]) {
Chris@573 1082 auto params = m_layers[c]->getPlayParameters();
Chris@158 1083 if (!params) return;
Chris@158 1084 params->setPlayPan(pan);
Chris@158 1085 saveState(c);
Chris@158 1086 }
Chris@158 1087 }
Chris@158 1088
Chris@158 1089
Chris@158 1090