annotate src/Analyser.cpp @ 516:449a0355f864 v2.0_osx_deploy

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