Mercurial > hg > tony
comparison main/MainWindow.cpp @ 580:f52766aa747b
Rename src -> main for consistency with SV/Sonic Lineup
author | Chris Cannam |
---|---|
date | Wed, 14 Aug 2019 11:57:06 +0100 |
parents | src/MainWindow.cpp@335fd9b439a0 |
children | d882f64e60db |
comparison
equal
deleted
inserted
replaced
579:47f96711069f | 580:f52766aa747b |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Tony | |
5 An intonation analysis and annotation tool | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 This file copyright 2006-2012 Chris Cannam and QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "../version.h" | |
17 | |
18 #include "MainWindow.h" | |
19 #include "NetworkPermissionTester.h" | |
20 #include "Analyser.h" | |
21 | |
22 #include "framework/Document.h" | |
23 #include "framework/VersionTester.h" | |
24 | |
25 #include "view/Pane.h" | |
26 #include "view/PaneStack.h" | |
27 #include "data/model/WaveFileModel.h" | |
28 #include "data/model/NoteModel.h" | |
29 #include "layer/FlexiNoteLayer.h" | |
30 #include "view/ViewManager.h" | |
31 #include "base/Preferences.h" | |
32 #include "base/RecordDirectory.h" | |
33 #include "base/AudioLevel.h" | |
34 #include "layer/WaveformLayer.h" | |
35 #include "layer/TimeInstantLayer.h" | |
36 #include "layer/TimeValueLayer.h" | |
37 #include "layer/SpectrogramLayer.h" | |
38 #include "widgets/Fader.h" | |
39 #include "view/Overview.h" | |
40 #include "widgets/AudioDial.h" | |
41 #include "widgets/IconLoader.h" | |
42 #include "widgets/KeyReference.h" | |
43 #include "widgets/LevelPanToolButton.h" | |
44 #include "audio/AudioCallbackPlaySource.h" | |
45 #include "audio/AudioCallbackRecordTarget.h" | |
46 #include "audio/PlaySpeedRangeMapper.h" | |
47 #include "base/Profiler.h" | |
48 #include "base/UnitDatabase.h" | |
49 #include "layer/ColourDatabase.h" | |
50 #include "base/Selection.h" | |
51 | |
52 #include "rdf/RDFImporter.h" | |
53 #include "data/fileio/DataFileReaderFactory.h" | |
54 #include "data/fileio/CSVFormat.h" | |
55 #include "data/fileio/CSVFileWriter.h" | |
56 #include "data/fileio/MIDIFileWriter.h" | |
57 #include "rdf/RDFExporter.h" | |
58 | |
59 #include "widgets/RangeInputDialog.h" | |
60 #include "widgets/ActivityLog.h" | |
61 | |
62 // For version information | |
63 #include "vamp/vamp.h" | |
64 #include "vamp-sdk/PluginBase.h" | |
65 #include "plugin/api/ladspa.h" | |
66 #include "plugin/api/dssi.h" | |
67 | |
68 #include <bqaudioio/SystemPlaybackTarget.h> | |
69 #include <bqaudioio/SystemAudioIO.h> | |
70 | |
71 #include <QApplication> | |
72 #include <QMessageBox> | |
73 #include <QGridLayout> | |
74 #include <QLabel> | |
75 #include <QMenuBar> | |
76 #include <QToolBar> | |
77 #include <QToolButton> | |
78 #include <QInputDialog> | |
79 #include <QStatusBar> | |
80 #include <QFileInfo> | |
81 #include <QDir> | |
82 #include <QProcess> | |
83 #include <QPushButton> | |
84 #include <QSettings> | |
85 #include <QScrollArea> | |
86 #include <QPainter> | |
87 #include <QWidgetAction> | |
88 | |
89 #include <iostream> | |
90 #include <cstdio> | |
91 #include <errno.h> | |
92 | |
93 using std::vector; | |
94 | |
95 | |
96 MainWindow::MainWindow(SoundOptions options, bool withSonification, bool withSpectrogram) : | |
97 MainWindowBase(options), | |
98 m_overview(0), | |
99 m_mainMenusCreated(false), | |
100 m_playbackMenu(0), | |
101 m_recentFilesMenu(0), | |
102 m_rightButtonMenu(0), | |
103 m_rightButtonPlaybackMenu(0), | |
104 m_deleteSelectedAction(0), | |
105 m_ffwdAction(0), | |
106 m_rwdAction(0), | |
107 m_intelligentActionOn(true), //GF: !!! temporary | |
108 m_activityLog(new ActivityLog()), | |
109 m_keyReference(new KeyReference()), | |
110 m_selectionAnchor(0), | |
111 m_withSonification(withSonification), | |
112 m_withSpectrogram(withSpectrogram) | |
113 { | |
114 setWindowTitle(QApplication::applicationName()); | |
115 | |
116 #ifdef Q_OS_MAC | |
117 #if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) | |
118 setUnifiedTitleAndToolBarOnMac(true); | |
119 #endif | |
120 #endif | |
121 | |
122 UnitDatabase *udb = UnitDatabase::getInstance(); | |
123 udb->registerUnit("Hz"); | |
124 udb->registerUnit("dB"); | |
125 udb->registerUnit("s"); | |
126 | |
127 ColourDatabase *cdb = ColourDatabase::getInstance(); | |
128 cdb->addColour(Qt::black, tr("Black")); | |
129 cdb->addColour(Qt::darkRed, tr("Red")); | |
130 cdb->addColour(Qt::darkBlue, tr("Blue")); | |
131 cdb->addColour(Qt::darkGreen, tr("Green")); | |
132 cdb->addColour(QColor(200, 50, 255), tr("Purple")); | |
133 cdb->addColour(QColor(255, 150, 50), tr("Orange")); | |
134 cdb->addColour(QColor(180, 180, 180), tr("Grey")); | |
135 cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); | |
136 cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); | |
137 cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); | |
138 cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); | |
139 cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); | |
140 cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); | |
141 | |
142 Preferences::getInstance()->setResampleOnLoad(true); | |
143 Preferences::getInstance()->setFixedSampleRate(44100); | |
144 Preferences::getInstance()->setSpectrogramSmoothing | |
145 (Preferences::SpectrogramInterpolated); | |
146 Preferences::getInstance()->setNormaliseAudio(true); | |
147 | |
148 QSettings settings; | |
149 | |
150 settings.beginGroup("MainWindow"); | |
151 settings.setValue("showstatusbar", false); | |
152 settings.endGroup(); | |
153 | |
154 settings.beginGroup("Transformer"); | |
155 settings.setValue("use-flexi-note-model", true); | |
156 settings.endGroup(); | |
157 | |
158 settings.beginGroup("LayerDefaults"); | |
159 settings.setValue("waveform", | |
160 QString("<layer scale=\"%1\" channelMode=\"%2\"/>") | |
161 .arg(int(WaveformLayer::LinearScale)) | |
162 .arg(int(WaveformLayer::MixChannels))); | |
163 settings.endGroup(); | |
164 | |
165 m_viewManager->setAlignMode(false); | |
166 m_viewManager->setPlaySoloMode(false); | |
167 m_viewManager->setToolMode(ViewManager::NavigateMode); | |
168 m_viewManager->setZoomWheelsEnabled(false); | |
169 m_viewManager->setIlluminateLocalFeatures(true); | |
170 m_viewManager->setShowWorkTitle(false); | |
171 m_viewManager->setShowCentreLine(false); | |
172 m_viewManager->setShowDuration(false); | |
173 m_viewManager->setOverlayMode(ViewManager::GlobalOverlays); | |
174 | |
175 connect(m_viewManager, SIGNAL(selectionChangedByUser()), | |
176 this, SLOT(selectionChangedByUser())); | |
177 | |
178 QFrame *frame = new QFrame; | |
179 setCentralWidget(frame); | |
180 | |
181 QGridLayout *layout = new QGridLayout; | |
182 | |
183 QScrollArea *scroll = new QScrollArea(frame); | |
184 scroll->setWidgetResizable(true); | |
185 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | |
186 scroll->setFrameShape(QFrame::NoFrame); | |
187 | |
188 // We have a pane stack: it comes with the territory. However, we | |
189 // have a fixed and known number of panes in it -- it isn't | |
190 // variable | |
191 m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); | |
192 m_paneStack->setShowPaneAccessories(false); | |
193 connect(m_paneStack, SIGNAL(doubleClickSelectInvoked(sv_frame_t)), | |
194 this, SLOT(doubleClickSelectInvoked(sv_frame_t))); | |
195 scroll->setWidget(m_paneStack); | |
196 | |
197 m_overview = new Overview(frame); | |
198 m_overview->setPlaybackFollow(PlaybackScrollPage); | |
199 m_overview->setViewManager(m_viewManager); | |
200 m_overview->setFixedHeight(60); | |
201 #ifndef _WIN32 | |
202 // For some reason, the contents of the overview never appear if we | |
203 // make this setting on Windows. I have no inclination at the moment | |
204 // to track down the reason why. | |
205 m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); | |
206 #endif | |
207 connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), | |
208 this, SLOT(contextHelpChanged(const QString &))); | |
209 | |
210 m_panLayer = new WaveformLayer; | |
211 m_panLayer->setChannelMode(WaveformLayer::MergeChannels); | |
212 m_panLayer->setAggressiveCacheing(true); | |
213 m_panLayer->setGain(0.5); | |
214 m_overview->addLayer(m_panLayer); | |
215 | |
216 if (m_viewManager->getGlobalDarkBackground()) { | |
217 m_panLayer->setBaseColour | |
218 (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green"))); | |
219 } else { | |
220 m_panLayer->setBaseColour | |
221 (ColourDatabase::getInstance()->getColourIndex(tr("Blue"))); | |
222 } | |
223 | |
224 m_fader = new Fader(frame, false); | |
225 connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
226 connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
227 | |
228 m_playSpeed = new AudioDial(frame); | |
229 m_playSpeed->setMeterColor(Qt::darkBlue); | |
230 m_playSpeed->setMinimum(0); | |
231 m_playSpeed->setMaximum(120); | |
232 m_playSpeed->setValue(60); | |
233 m_playSpeed->setFixedWidth(24); | |
234 m_playSpeed->setFixedHeight(24); | |
235 m_playSpeed->setNotchesVisible(true); | |
236 m_playSpeed->setPageStep(10); | |
237 m_playSpeed->setObjectName(tr("Playback Speed")); | |
238 m_playSpeed->setDefaultValue(60); | |
239 m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); | |
240 m_playSpeed->setShowToolTip(true); | |
241 connect(m_playSpeed, SIGNAL(valueChanged(int)), | |
242 this, SLOT(playSpeedChanged(int))); | |
243 connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
244 connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
245 | |
246 m_audioLPW = new LevelPanToolButton(frame); | |
247 m_audioLPW->setIncludeMute(false); | |
248 m_audioLPW->setObjectName(tr("Audio Track Level and Pan")); | |
249 connect(m_audioLPW, SIGNAL(levelChanged(float)), this, SLOT(audioGainChanged(float))); | |
250 connect(m_audioLPW, SIGNAL(panChanged(float)), this, SLOT(audioPanChanged(float))); | |
251 | |
252 if (m_withSonification) { | |
253 | |
254 m_pitchLPW = new LevelPanToolButton(frame); | |
255 m_pitchLPW->setIncludeMute(false); | |
256 m_pitchLPW->setObjectName(tr("Pitch Track Level and Pan")); | |
257 connect(m_pitchLPW, SIGNAL(levelChanged(float)), this, SLOT(pitchGainChanged(float))); | |
258 connect(m_pitchLPW, SIGNAL(panChanged(float)), this, SLOT(pitchPanChanged(float))); | |
259 | |
260 m_notesLPW = new LevelPanToolButton(frame); | |
261 m_notesLPW->setIncludeMute(false); | |
262 m_notesLPW->setObjectName(tr("Note Track Level and Pan")); | |
263 connect(m_notesLPW, SIGNAL(levelChanged(float)), this, SLOT(notesGainChanged(float))); | |
264 connect(m_notesLPW, SIGNAL(panChanged(float)), this, SLOT(notesPanChanged(float))); | |
265 } | |
266 | |
267 layout->setSpacing(4); | |
268 layout->addWidget(m_overview, 0, 1); | |
269 layout->addWidget(scroll, 1, 1); | |
270 | |
271 layout->setColumnStretch(1, 10); | |
272 | |
273 frame->setLayout(layout); | |
274 | |
275 m_analyser = new Analyser(); | |
276 connect(m_analyser, SIGNAL(layersChanged()), | |
277 this, SLOT(updateLayerStatuses())); | |
278 connect(m_analyser, SIGNAL(layersChanged()), | |
279 this, SLOT(updateMenuStates())); | |
280 | |
281 setupMenus(); | |
282 setupToolbars(); | |
283 setupHelpMenu(); | |
284 | |
285 statusBar(); | |
286 | |
287 finaliseMenus(); | |
288 | |
289 connect(m_viewManager, SIGNAL(activity(QString)), | |
290 m_activityLog, SLOT(activityHappened(QString))); | |
291 connect(m_playSource, SIGNAL(activity(QString)), | |
292 m_activityLog, SLOT(activityHappened(QString))); | |
293 connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), | |
294 m_activityLog, SLOT(activityHappened(QString))); | |
295 connect(this, SIGNAL(activity(QString)), | |
296 m_activityLog, SLOT(activityHappened(QString))); | |
297 connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); | |
298 connect(this, SIGNAL(sessionLoaded()), this, SLOT(analyseNewMainModel())); | |
299 connect(this, SIGNAL(audioFileLoaded()), this, SLOT(analyseNewMainModel())); | |
300 m_activityLog->hide(); | |
301 | |
302 setAudioRecordMode(RecordReplaceSession); | |
303 | |
304 newSession(); | |
305 | |
306 settings.beginGroup("MainWindow"); | |
307 settings.setValue("zoom-default", 512); | |
308 settings.endGroup(); | |
309 zoomDefault(); | |
310 | |
311 NetworkPermissionTester tester; | |
312 bool networkPermission = tester.havePermission(); | |
313 if (networkPermission) { | |
314 m_versionTester = new VersionTester | |
315 ("sonicvisualiser.org", "latest-tony-version.txt", TONY_VERSION); | |
316 connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), | |
317 this, SLOT(newerVersionAvailable(QString))); | |
318 } else { | |
319 m_versionTester = 0; | |
320 } | |
321 } | |
322 | |
323 MainWindow::~MainWindow() | |
324 { | |
325 delete m_analyser; | |
326 delete m_keyReference; | |
327 Profiles::getInstance()->dump(); | |
328 } | |
329 | |
330 void | |
331 MainWindow::setupMenus() | |
332 { | |
333 if (!m_mainMenusCreated) { | |
334 | |
335 #ifdef Q_OS_LINUX | |
336 // In Ubuntu 14.04 the window's menu bar goes missing entirely | |
337 // if the user is running any desktop environment other than Unity | |
338 // (in which the faux single-menubar appears). The user has a | |
339 // workaround, to remove the appmenu-qt5 package, but that is | |
340 // awkward and the problem is so severe that it merits disabling | |
341 // the system menubar integration altogether. Like this: | |
342 menuBar()->setNativeMenuBar(false); | |
343 #endif | |
344 | |
345 m_rightButtonMenu = new QMenu(); | |
346 } | |
347 | |
348 if (!m_mainMenusCreated) { | |
349 CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); | |
350 m_rightButtonMenu->addSeparator(); | |
351 } | |
352 | |
353 setupFileMenu(); | |
354 setupEditMenu(); | |
355 setupViewMenu(); | |
356 setupAnalysisMenu(); | |
357 | |
358 m_mainMenusCreated = true; | |
359 } | |
360 | |
361 void | |
362 MainWindow::setupFileMenu() | |
363 { | |
364 if (m_mainMenusCreated) return; | |
365 | |
366 QMenu *menu = menuBar()->addMenu(tr("&File")); | |
367 menu->setTearOffEnabled(true); | |
368 QToolBar *toolbar = addToolBar(tr("File Toolbar")); | |
369 | |
370 m_keyReference->setCategory(tr("File and Session Management")); | |
371 | |
372 IconLoader il; | |
373 QIcon icon; | |
374 QAction *action; | |
375 | |
376 icon = il.load("fileopen"); | |
377 action = new QAction(icon, tr("&Open..."), this); | |
378 action->setShortcut(tr("Ctrl+O")); | |
379 action->setStatusTip(tr("Open a session or audio file")); | |
380 connect(action, SIGNAL(triggered()), this, SLOT(openFile())); | |
381 m_keyReference->registerShortcut(action); | |
382 menu->addAction(action); | |
383 toolbar->addAction(action); | |
384 | |
385 action = new QAction(tr("Open Lo&cation..."), this); | |
386 action->setShortcut(tr("Ctrl+Shift+O")); | |
387 action->setStatusTip(tr("Open a file from a remote URL")); | |
388 connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); | |
389 m_keyReference->registerShortcut(action); | |
390 menu->addAction(action); | |
391 | |
392 m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); | |
393 m_recentFilesMenu->setTearOffEnabled(true); | |
394 setupRecentFilesMenu(); | |
395 connect(&m_recentFiles, SIGNAL(recentChanged()), | |
396 this, SLOT(setupRecentFilesMenu())); | |
397 | |
398 menu->addSeparator(); | |
399 | |
400 icon = il.load("filesave"); | |
401 action = new QAction(icon, tr("&Save Session"), this); | |
402 action->setShortcut(tr("Ctrl+S")); | |
403 action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); | |
404 connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); | |
405 connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); | |
406 m_keyReference->registerShortcut(action); | |
407 menu->addAction(action); | |
408 toolbar->addAction(action); | |
409 | |
410 icon = il.load("filesaveas"); | |
411 action = new QAction(icon, tr("Save Session &As..."), this); | |
412 action->setShortcut(tr("Ctrl+Shift+S")); | |
413 action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); | |
414 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); | |
415 connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); | |
416 menu->addAction(action); | |
417 toolbar->addAction(action); | |
418 | |
419 action = new QAction(tr("Save Session to Audio File &Path"), this); | |
420 action->setShortcut(tr("Ctrl+Alt+S")); | |
421 action->setStatusTip(tr("Save the current session into a %1 session file with the same filename as the audio but a .ton extension.").arg(QApplication::applicationName())); | |
422 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionInAudioPath())); | |
423 connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); | |
424 menu->addAction(action); | |
425 | |
426 menu->addSeparator(); | |
427 | |
428 action = new QAction(tr("I&mport Pitch Track Data..."), this); | |
429 action->setStatusTip(tr("Import pitch-track data from a CSV, RDF, or layer XML file")); | |
430 connect(action, SIGNAL(triggered()), this, SLOT(importPitchLayer())); | |
431 connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); | |
432 menu->addAction(action); | |
433 | |
434 action = new QAction(tr("E&xport Pitch Track Data..."), this); | |
435 action->setStatusTip(tr("Export pitch-track data to a CSV, RDF, or layer XML file")); | |
436 connect(action, SIGNAL(triggered()), this, SLOT(exportPitchLayer())); | |
437 connect(this, SIGNAL(canExportPitchTrack(bool)), action, SLOT(setEnabled(bool))); | |
438 menu->addAction(action); | |
439 | |
440 action = new QAction(tr("&Export Note Data..."), this); | |
441 action->setStatusTip(tr("Export note data to a CSV, RDF, layer XML, or MIDI file")); | |
442 connect(action, SIGNAL(triggered()), this, SLOT(exportNoteLayer())); | |
443 connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); | |
444 menu->addAction(action); | |
445 | |
446 menu->addSeparator(); | |
447 | |
448 action = new QAction(tr("Browse Recorded Audio"), this); | |
449 action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); | |
450 connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); | |
451 menu->addAction(action); | |
452 | |
453 menu->addSeparator(); | |
454 | |
455 action = new QAction(il.load("exit"), tr("&Quit"), this); | |
456 action->setShortcut(tr("Ctrl+Q")); | |
457 action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); | |
458 connect(action, SIGNAL(triggered()), this, SLOT(close())); | |
459 m_keyReference->registerShortcut(action); | |
460 menu->addAction(action); | |
461 } | |
462 | |
463 void | |
464 MainWindow::setupEditMenu() | |
465 { | |
466 if (m_mainMenusCreated) return; | |
467 | |
468 QMenu *menu = menuBar()->addMenu(tr("&Edit")); | |
469 menu->setTearOffEnabled(true); | |
470 CommandHistory::getInstance()->registerMenu(menu); | |
471 menu->addSeparator(); | |
472 | |
473 m_keyReference->setCategory | |
474 (tr("Selection Strip Mouse Actions")); | |
475 m_keyReference->registerShortcut | |
476 (tr("Jump"), tr("Left"), | |
477 tr("Click left button to move the playback position to a time")); | |
478 m_keyReference->registerShortcut | |
479 (tr("Select"), tr("Left"), | |
480 tr("Click left button and drag to select a region of time")); | |
481 m_keyReference->registerShortcut | |
482 (tr("Select Note Duration"), tr("Double-Click Left"), | |
483 tr("Double-click left button to select the region of time corresponding to a note")); | |
484 | |
485 QToolBar *toolbar = addToolBar(tr("Tools Toolbar")); | |
486 | |
487 CommandHistory::getInstance()->registerToolbar(toolbar); | |
488 | |
489 QActionGroup *group = new QActionGroup(this); | |
490 | |
491 IconLoader il; | |
492 | |
493 m_keyReference->setCategory(tr("Tool Selection")); | |
494 QAction *action = toolbar->addAction(il.load("navigate"), | |
495 tr("Navigate")); | |
496 action->setCheckable(true); | |
497 action->setChecked(true); | |
498 action->setShortcut(tr("1")); | |
499 action->setStatusTip(tr("Navigate")); | |
500 connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); | |
501 connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); | |
502 group->addAction(action); | |
503 menu->addAction(action); | |
504 m_keyReference->registerShortcut(action); | |
505 | |
506 m_keyReference->setCategory | |
507 (tr("Navigate Tool Mouse Actions")); | |
508 m_keyReference->registerShortcut | |
509 (tr("Navigate"), tr("Left"), | |
510 tr("Click left button and drag to move around")); | |
511 m_keyReference->registerShortcut | |
512 (tr("Re-Analyse Area"), tr("Shift+Left"), | |
513 tr("Shift-click left button and drag to define a specific pitch and time range to re-analyse")); | |
514 m_keyReference->registerShortcut | |
515 (tr("Edit"), tr("Double-Click Left"), | |
516 tr("Double-click left button on an item to edit it")); | |
517 | |
518 m_keyReference->setCategory(tr("Tool Selection")); | |
519 action = toolbar->addAction(il.load("move"), | |
520 tr("Edit")); | |
521 action->setCheckable(true); | |
522 action->setShortcut(tr("2")); | |
523 action->setStatusTip(tr("Edit with Note Intelligence")); | |
524 connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); | |
525 group->addAction(action); | |
526 menu->addAction(action); | |
527 m_keyReference->registerShortcut(action); | |
528 | |
529 m_keyReference->setCategory | |
530 (tr("Note Edit Tool Mouse Actions")); | |
531 m_keyReference->registerShortcut | |
532 (tr("Adjust Pitch"), tr("Left"), | |
533 tr("Click left button on the main part of a note and drag to move it up or down")); | |
534 m_keyReference->registerShortcut | |
535 (tr("Split"), tr("Left"), | |
536 tr("Click left button on the bottom edge of a note to split it at the click point")); | |
537 m_keyReference->registerShortcut | |
538 (tr("Resize"), tr("Left"), | |
539 tr("Click left button on the left or right edge of a note and drag to change the time or duration of the note")); | |
540 m_keyReference->registerShortcut | |
541 (tr("Erase"), tr("Shift+Left"), | |
542 tr("Shift-click left button on a note to remove it")); | |
543 | |
544 | |
545 /* Remove for now... | |
546 | |
547 m_keyReference->setCategory(tr("Tool Selection")); | |
548 action = toolbar->addAction(il.load("notes"), | |
549 tr("Free Edit")); | |
550 action->setCheckable(true); | |
551 action->setShortcut(tr("3")); | |
552 action->setStatusTip(tr("Free Edit")); | |
553 connect(action, SIGNAL(triggered()), this, SLOT(toolFreeEditSelected())); | |
554 group->addAction(action); | |
555 m_keyReference->registerShortcut(action); | |
556 */ | |
557 | |
558 menu->addSeparator(); | |
559 | |
560 m_keyReference->setCategory(tr("Selection")); | |
561 | |
562 action = new QAction(tr("Select &All"), this); | |
563 action->setShortcut(tr("Ctrl+A")); | |
564 action->setStatusTip(tr("Select the whole duration of the current session")); | |
565 connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); | |
566 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); | |
567 m_keyReference->registerShortcut(action); | |
568 menu->addAction(action); | |
569 m_rightButtonMenu->addAction(action); | |
570 | |
571 action = new QAction(tr("C&lear Selection"), this); | |
572 action->setShortcuts(QList<QKeySequence>() | |
573 << QKeySequence(tr("Esc")) | |
574 << QKeySequence(tr("Ctrl+Esc"))); | |
575 action->setStatusTip(tr("Clear the selection and abandon any pending pitch choices in it")); | |
576 connect(action, SIGNAL(triggered()), this, SLOT(abandonSelection())); | |
577 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); | |
578 m_keyReference->registerShortcut(action); | |
579 m_keyReference->registerAlternativeShortcut(action, QKeySequence(tr("Ctrl+Esc"))); | |
580 menu->addAction(action); | |
581 m_rightButtonMenu->addAction(action); | |
582 | |
583 menu->addSeparator(); | |
584 m_rightButtonMenu->addSeparator(); | |
585 | |
586 m_keyReference->setCategory(tr("Pitch Track")); | |
587 | |
588 action = new QAction(tr("Choose Higher Pitch"), this); | |
589 action->setShortcut(tr("Ctrl+Up")); | |
590 action->setStatusTip(tr("Move pitches up an octave, or to the next higher pitch candidate")); | |
591 m_keyReference->registerShortcut(action); | |
592 connect(action, SIGNAL(triggered()), this, SLOT(switchPitchUp())); | |
593 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); | |
594 menu->addAction(action); | |
595 m_rightButtonMenu->addAction(action); | |
596 | |
597 action = new QAction(tr("Choose Lower Pitch"), this); | |
598 action->setShortcut(tr("Ctrl+Down")); | |
599 action->setStatusTip(tr("Move pitches down an octave, or to the next lower pitch candidate")); | |
600 m_keyReference->registerShortcut(action); | |
601 connect(action, SIGNAL(triggered()), this, SLOT(switchPitchDown())); | |
602 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); | |
603 menu->addAction(action); | |
604 m_rightButtonMenu->addAction(action); | |
605 | |
606 m_showCandidatesAction = new QAction(tr("Show Pitch Candidates"), this); | |
607 m_showCandidatesAction->setShortcut(tr("Ctrl+Return")); | |
608 m_showCandidatesAction->setStatusTip(tr("Toggle the display of alternative pitch candidates for the selected region")); | |
609 m_keyReference->registerShortcut(m_showCandidatesAction); | |
610 connect(m_showCandidatesAction, SIGNAL(triggered()), this, SLOT(togglePitchCandidates())); | |
611 connect(this, SIGNAL(canClearSelection(bool)), m_showCandidatesAction, SLOT(setEnabled(bool))); | |
612 menu->addAction(m_showCandidatesAction); | |
613 m_rightButtonMenu->addAction(m_showCandidatesAction); | |
614 | |
615 action = new QAction(tr("Remove Pitches"), this); | |
616 action->setShortcut(tr("Ctrl+Backspace")); | |
617 action->setStatusTip(tr("Remove all pitch estimates within the selected region, making it unvoiced")); | |
618 m_keyReference->registerShortcut(action); | |
619 connect(action, SIGNAL(triggered()), this, SLOT(clearPitches())); | |
620 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); | |
621 menu->addAction(action); | |
622 m_rightButtonMenu->addAction(action); | |
623 | |
624 menu->addSeparator(); | |
625 m_rightButtonMenu->addSeparator(); | |
626 | |
627 m_keyReference->setCategory(tr("Note Track")); | |
628 | |
629 action = new QAction(tr("Split Note"), this); | |
630 action->setShortcut(tr("/")); | |
631 action->setStatusTip(tr("Split the note at the current playback position into two")); | |
632 m_keyReference->registerShortcut(action); | |
633 connect(action, SIGNAL(triggered()), this, SLOT(splitNote())); | |
634 connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); | |
635 menu->addAction(action); | |
636 m_rightButtonMenu->addAction(action); | |
637 | |
638 action = new QAction(tr("Merge Notes"), this); | |
639 action->setShortcut(tr("\\")); | |
640 action->setStatusTip(tr("Merge all notes within the selected region into a single note")); | |
641 m_keyReference->registerShortcut(action); | |
642 connect(action, SIGNAL(triggered()), this, SLOT(mergeNotes())); | |
643 connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); | |
644 menu->addAction(action); | |
645 m_rightButtonMenu->addAction(action); | |
646 | |
647 action = new QAction(tr("Delete Notes"), this); | |
648 action->setShortcut(tr("Backspace")); | |
649 action->setStatusTip(tr("Delete all notes within the selected region")); | |
650 m_keyReference->registerShortcut(action); | |
651 connect(action, SIGNAL(triggered()), this, SLOT(deleteNotes())); | |
652 connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); | |
653 menu->addAction(action); | |
654 m_rightButtonMenu->addAction(action); | |
655 | |
656 action = new QAction(tr("Form Note from Selection"), this); | |
657 action->setShortcut(tr("=")); | |
658 action->setStatusTip(tr("Form a note spanning the selected region, splitting any existing notes at its boundaries")); | |
659 m_keyReference->registerShortcut(action); | |
660 connect(action, SIGNAL(triggered()), this, SLOT(formNoteFromSelection())); | |
661 connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); | |
662 menu->addAction(action); | |
663 m_rightButtonMenu->addAction(action); | |
664 | |
665 action = new QAction(tr("Snap Notes to Pitch Track"), this); | |
666 action->setStatusTip(tr("Set notes within the selected region to the median frequency of their underlying pitches, or remove them if there are no underlying pitches")); | |
667 // m_keyReference->registerShortcut(action); | |
668 connect(action, SIGNAL(triggered()), this, SLOT(snapNotesToPitches())); | |
669 connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); | |
670 menu->addAction(action); | |
671 m_rightButtonMenu->addAction(action); | |
672 } | |
673 | |
674 void | |
675 MainWindow::setupViewMenu() | |
676 { | |
677 if (m_mainMenusCreated) return; | |
678 | |
679 IconLoader il; | |
680 | |
681 QAction *action = 0; | |
682 | |
683 m_keyReference->setCategory(tr("Panning and Navigation")); | |
684 | |
685 QMenu *menu = menuBar()->addMenu(tr("&View")); | |
686 menu->setTearOffEnabled(true); | |
687 action = new QAction(tr("Peek &Left"), this); | |
688 action->setShortcut(tr("Alt+Left")); | |
689 action->setStatusTip(tr("Scroll the current pane to the left without changing the play position")); | |
690 connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); | |
691 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
692 m_keyReference->registerShortcut(action); | |
693 menu->addAction(action); | |
694 | |
695 action = new QAction(tr("Peek &Right"), this); | |
696 action->setShortcut(tr("Alt+Right")); | |
697 action->setStatusTip(tr("Scroll the current pane to the right without changing the play position")); | |
698 connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); | |
699 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
700 m_keyReference->registerShortcut(action); | |
701 menu->addAction(action); | |
702 | |
703 menu->addSeparator(); | |
704 | |
705 m_keyReference->setCategory(tr("Zoom")); | |
706 | |
707 action = new QAction(il.load("zoom-in"), | |
708 tr("Zoom &In"), this); | |
709 action->setShortcut(tr("Up")); | |
710 action->setStatusTip(tr("Increase the zoom level")); | |
711 connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); | |
712 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
713 m_keyReference->registerShortcut(action); | |
714 menu->addAction(action); | |
715 | |
716 action = new QAction(il.load("zoom-out"), | |
717 tr("Zoom &Out"), this); | |
718 action->setShortcut(tr("Down")); | |
719 action->setStatusTip(tr("Decrease the zoom level")); | |
720 connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); | |
721 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
722 m_keyReference->registerShortcut(action); | |
723 menu->addAction(action); | |
724 | |
725 action = new QAction(tr("Restore &Default Zoom"), this); | |
726 action->setStatusTip(tr("Restore the zoom level to the default")); | |
727 connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); | |
728 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
729 menu->addAction(action); | |
730 | |
731 action = new QAction(il.load("zoom-fit"), | |
732 tr("Zoom to &Fit"), this); | |
733 action->setShortcut(tr("F")); | |
734 action->setStatusTip(tr("Zoom to show the whole file")); | |
735 connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); | |
736 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
737 m_keyReference->registerShortcut(action); | |
738 menu->addAction(action); | |
739 | |
740 menu->addSeparator(); | |
741 | |
742 action = new QAction(tr("Set Displayed Fre&quency Range..."), this); | |
743 action->setStatusTip(tr("Set the minimum and maximum frequencies in the visible display")); | |
744 connect(action, SIGNAL(triggered()), this, SLOT(editDisplayExtents())); | |
745 menu->addAction(action); | |
746 } | |
747 | |
748 void | |
749 MainWindow::setupAnalysisMenu() | |
750 { | |
751 if (m_mainMenusCreated) return; | |
752 | |
753 IconLoader il; | |
754 | |
755 QAction *action = 0; | |
756 | |
757 QMenu *menu = menuBar()->addMenu(tr("&Analysis")); | |
758 menu->setTearOffEnabled(true); | |
759 | |
760 m_autoAnalyse = new QAction(tr("Auto-Analyse &New Audio"), this); | |
761 m_autoAnalyse->setStatusTip(tr("Automatically trigger analysis upon opening of a new audio file.")); | |
762 m_autoAnalyse->setCheckable(true); | |
763 connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled())); | |
764 menu->addAction(m_autoAnalyse); | |
765 | |
766 action = new QAction(tr("&Analyse Now!"), this); | |
767 action->setStatusTip(tr("Trigger analysis of pitches and notes. (This will delete all existing pitches and notes.)")); | |
768 connect(action, SIGNAL(triggered()), this, SLOT(analyseNow())); | |
769 menu->addAction(action); | |
770 m_keyReference->registerShortcut(action); | |
771 | |
772 menu->addSeparator(); | |
773 | |
774 m_precise = new QAction(tr("&Unbiased Timing (slow)"), this); | |
775 m_precise->setStatusTip(tr("Use a symmetric window in YIN to remove frequency-dependent timing bias. (This is slow!)")); | |
776 m_precise->setCheckable(true); | |
777 connect(m_precise, SIGNAL(triggered()), this, SLOT(precisionAnalysisToggled())); | |
778 menu->addAction(m_precise); | |
779 | |
780 m_lowamp = new QAction(tr("&Penalise Soft Pitches"), this); | |
781 m_lowamp->setStatusTip(tr("Reduce the likelihood of detecting a pitch when the signal has low amplitude.")); | |
782 m_lowamp->setCheckable(true); | |
783 connect(m_lowamp, SIGNAL(triggered()), this, SLOT(lowampAnalysisToggled())); | |
784 menu->addAction(m_lowamp); | |
785 | |
786 m_onset = new QAction(tr("&High Onset Sensitivity"), this); | |
787 m_onset->setStatusTip(tr("Increase likelihood of separating notes, especially consecutive notes at the same pitch.")); | |
788 m_onset->setCheckable(true); | |
789 connect(m_onset, SIGNAL(triggered()), this, SLOT(onsetAnalysisToggled())); | |
790 menu->addAction(m_onset); | |
791 | |
792 m_prune = new QAction(tr("&Drop Short Notes"), this); | |
793 m_prune->setStatusTip(tr("Duration-based pruning: automatic note estimator will not output notes of less than 100ms duration.")); | |
794 m_prune->setCheckable(true); | |
795 connect(m_prune, SIGNAL(triggered()), this, SLOT(pruneAnalysisToggled())); | |
796 menu->addAction(m_prune); | |
797 | |
798 menu->addSeparator(); | |
799 | |
800 action = new QAction(tr("Reset Options to Defaults"), this); | |
801 action->setStatusTip(tr("Reset all of the Analyse menu options to their default settings.")); | |
802 connect(action, SIGNAL(triggered()), this, SLOT(resetAnalyseOptions())); | |
803 menu->addAction(action); | |
804 | |
805 updateAnalyseStates(); | |
806 } | |
807 | |
808 void | |
809 MainWindow::resetAnalyseOptions() | |
810 { | |
811 //!!! oh no, we need to update the menu states as well... | |
812 QSettings settings; | |
813 settings.beginGroup("Analyser"); | |
814 settings.setValue("auto-analysis", true); | |
815 settings.setValue("precision-analysis", false); | |
816 settings.setValue("lowamp-analysis", true); | |
817 settings.setValue("onset-analysis", true); | |
818 settings.setValue("prune-analysis", true); | |
819 settings.endGroup(); | |
820 updateAnalyseStates(); | |
821 } | |
822 | |
823 void | |
824 MainWindow::updateAnalyseStates() | |
825 { | |
826 QSettings settings; | |
827 settings.beginGroup("Analyser"); | |
828 bool autoAnalyse = settings.value("auto-analysis", true).toBool(); | |
829 bool precise = settings.value("precision-analysis", false).toBool(); | |
830 bool lowamp = settings.value("lowamp-analysis", true).toBool(); | |
831 bool onset = settings.value("onset-analysis", true).toBool(); | |
832 bool prune = settings.value("prune-analysis", true).toBool(); | |
833 settings.endGroup(); | |
834 | |
835 m_autoAnalyse->setChecked(autoAnalyse); | |
836 m_precise->setChecked(precise); | |
837 m_lowamp->setChecked(lowamp); | |
838 m_onset->setChecked(onset); | |
839 m_prune->setChecked(prune); | |
840 } | |
841 | |
842 void | |
843 MainWindow::autoAnalysisToggled() | |
844 { | |
845 QAction *a = qobject_cast<QAction *>(sender()); | |
846 if (!a) return; | |
847 | |
848 bool set = a->isChecked(); | |
849 | |
850 QSettings settings; | |
851 settings.beginGroup("Analyser"); | |
852 settings.setValue("auto-analysis", set); | |
853 settings.endGroup(); | |
854 } | |
855 | |
856 void | |
857 MainWindow::precisionAnalysisToggled() | |
858 { | |
859 QAction *a = qobject_cast<QAction *>(sender()); | |
860 if (!a) return; | |
861 | |
862 bool set = a->isChecked(); | |
863 | |
864 QSettings settings; | |
865 settings.beginGroup("Analyser"); | |
866 settings.setValue("precision-analysis", set); | |
867 settings.endGroup(); | |
868 | |
869 // don't run analyseNow() automatically -- it's a destructive operation | |
870 } | |
871 | |
872 void | |
873 MainWindow::lowampAnalysisToggled() | |
874 { | |
875 QAction *a = qobject_cast<QAction *>(sender()); | |
876 if (!a) return; | |
877 | |
878 bool set = a->isChecked(); | |
879 | |
880 QSettings settings; | |
881 settings.beginGroup("Analyser"); | |
882 settings.setValue("lowamp-analysis", set); | |
883 settings.endGroup(); | |
884 | |
885 // don't run analyseNow() automatically -- it's a destructive operation | |
886 } | |
887 | |
888 void | |
889 MainWindow::onsetAnalysisToggled() | |
890 { | |
891 QAction *a = qobject_cast<QAction *>(sender()); | |
892 if (!a) return; | |
893 | |
894 bool set = a->isChecked(); | |
895 | |
896 QSettings settings; | |
897 settings.beginGroup("Analyser"); | |
898 settings.setValue("onset-analysis", set); | |
899 settings.endGroup(); | |
900 | |
901 // don't run analyseNow() automatically -- it's a destructive operation | |
902 } | |
903 | |
904 void | |
905 MainWindow::pruneAnalysisToggled() | |
906 { | |
907 QAction *a = qobject_cast<QAction *>(sender()); | |
908 if (!a) return; | |
909 | |
910 bool set = a->isChecked(); | |
911 | |
912 QSettings settings; | |
913 settings.beginGroup("Analyser"); | |
914 settings.setValue("prune-analysis", set); | |
915 settings.endGroup(); | |
916 | |
917 // don't run analyseNow() automatically -- it's a destructive operation | |
918 } | |
919 | |
920 void | |
921 MainWindow::setupHelpMenu() | |
922 { | |
923 QMenu *menu = menuBar()->addMenu(tr("&Help")); | |
924 menu->setTearOffEnabled(true); | |
925 | |
926 m_keyReference->setCategory(tr("Help")); | |
927 | |
928 IconLoader il; | |
929 | |
930 QString name = QApplication::applicationName(); | |
931 QAction *action; | |
932 | |
933 action = new QAction(tr("&Key and Mouse Reference"), this); | |
934 action->setShortcut(tr("F2")); | |
935 action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); | |
936 connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); | |
937 m_keyReference->registerShortcut(action); | |
938 menu->addAction(action); | |
939 | |
940 action = new QAction(il.load("help"), | |
941 tr("&Help Reference"), this); | |
942 action->setShortcut(tr("F1")); | |
943 action->setStatusTip(tr("Open the %1 reference manual").arg(name)); | |
944 connect(action, SIGNAL(triggered()), this, SLOT(help())); | |
945 m_keyReference->registerShortcut(action); | |
946 menu->addAction(action); | |
947 | |
948 | |
949 action = new QAction(tr("%1 on the &Web").arg(name), this); | |
950 action->setStatusTip(tr("Open the %1 website").arg(name)); | |
951 connect(action, SIGNAL(triggered()), this, SLOT(website())); | |
952 menu->addAction(action); | |
953 | |
954 action = new QAction(tr("&About %1").arg(name), this); | |
955 action->setStatusTip(tr("Show information about %1").arg(name)); | |
956 connect(action, SIGNAL(triggered()), this, SLOT(about())); | |
957 menu->addAction(action); | |
958 } | |
959 | |
960 void | |
961 MainWindow::setupRecentFilesMenu() | |
962 { | |
963 m_recentFilesMenu->clear(); | |
964 vector<QString> files = m_recentFiles.getRecent(); | |
965 for (size_t i = 0; i < files.size(); ++i) { | |
966 QAction *action = new QAction(files[i], this); | |
967 connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); | |
968 if (i == 0) { | |
969 action->setShortcut(tr("Ctrl+R")); | |
970 m_keyReference->registerShortcut | |
971 (tr("Re-open"), | |
972 action->shortcut().toString(), | |
973 tr("Re-open the current or most recently opened file")); | |
974 } | |
975 m_recentFilesMenu->addAction(action); | |
976 } | |
977 } | |
978 | |
979 void | |
980 MainWindow::setupToolbars() | |
981 { | |
982 m_keyReference->setCategory(tr("Playback and Transport Controls")); | |
983 | |
984 IconLoader il; | |
985 | |
986 QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); | |
987 menu->setTearOffEnabled(true); | |
988 m_rightButtonMenu->addSeparator(); | |
989 m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); | |
990 | |
991 QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); | |
992 | |
993 QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"), | |
994 tr("Rewind to Start")); | |
995 rwdStartAction->setShortcut(tr("Home")); | |
996 rwdStartAction->setStatusTip(tr("Rewind to the start")); | |
997 connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); | |
998 connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool))); | |
999 | |
1000 QAction *m_rwdAction = toolbar->addAction(il.load("rewind"), | |
1001 tr("Rewind")); | |
1002 m_rwdAction->setShortcut(tr("Left")); | |
1003 m_rwdAction->setStatusTip(tr("Rewind to the previous one-second boundary")); | |
1004 connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); | |
1005 connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); | |
1006 | |
1007 setDefaultFfwdRwdStep(RealTime(1, 0)); | |
1008 | |
1009 QAction *playAction = toolbar->addAction(il.load("playpause"), | |
1010 tr("Play / Pause")); | |
1011 playAction->setCheckable(true); | |
1012 playAction->setShortcut(tr("Space")); | |
1013 playAction->setStatusTip(tr("Start or stop playback from the current position")); | |
1014 connect(playAction, SIGNAL(triggered()), this, SLOT(play())); | |
1015 connect(m_playSource, SIGNAL(playStatusChanged(bool)), | |
1016 playAction, SLOT(setChecked(bool))); | |
1017 connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool))); | |
1018 | |
1019 m_ffwdAction = toolbar->addAction(il.load("ffwd"), | |
1020 tr("Fast Forward")); | |
1021 m_ffwdAction->setShortcut(tr("Right")); | |
1022 m_ffwdAction->setStatusTip(tr("Fast-forward to the next one-second boundary")); | |
1023 connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); | |
1024 connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); | |
1025 | |
1026 QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), | |
1027 tr("Fast Forward to End")); | |
1028 ffwdEndAction->setShortcut(tr("End")); | |
1029 ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); | |
1030 connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); | |
1031 connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool))); | |
1032 | |
1033 QAction *recordAction = toolbar->addAction(il.load("record"), | |
1034 tr("Record")); | |
1035 recordAction->setCheckable(true); | |
1036 recordAction->setShortcut(tr("Ctrl+Space")); | |
1037 recordAction->setStatusTip(tr("Record a new audio file")); | |
1038 connect(recordAction, SIGNAL(triggered()), this, SLOT(record())); | |
1039 connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), | |
1040 recordAction, SLOT(setChecked(bool))); | |
1041 connect(m_recordTarget, SIGNAL(recordCompleted()), | |
1042 this, SLOT(analyseNow())); | |
1043 connect(this, SIGNAL(canRecord(bool)), | |
1044 recordAction, SLOT(setEnabled(bool))); | |
1045 | |
1046 toolbar = addToolBar(tr("Play Mode Toolbar")); | |
1047 | |
1048 QAction *psAction = toolbar->addAction(il.load("playselection"), | |
1049 tr("Constrain Playback to Selection")); | |
1050 psAction->setCheckable(true); | |
1051 psAction->setChecked(m_viewManager->getPlaySelectionMode()); | |
1052 psAction->setShortcut(tr("s")); | |
1053 psAction->setStatusTip(tr("Constrain playback to the selected regions")); | |
1054 connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), | |
1055 psAction, SLOT(setChecked(bool))); | |
1056 connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); | |
1057 connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool))); | |
1058 | |
1059 QAction *plAction = toolbar->addAction(il.load("playloop"), | |
1060 tr("Loop Playback")); | |
1061 plAction->setCheckable(true); | |
1062 plAction->setChecked(m_viewManager->getPlayLoopMode()); | |
1063 plAction->setShortcut(tr("l")); | |
1064 plAction->setStatusTip(tr("Loop playback")); | |
1065 connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), | |
1066 plAction, SLOT(setChecked(bool))); | |
1067 connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); | |
1068 connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool))); | |
1069 | |
1070 QAction *oneLeftAction = new QAction(tr("&One Note Left"), this); | |
1071 oneLeftAction->setShortcut(tr("Ctrl+Left")); | |
1072 oneLeftAction->setStatusTip(tr("Move cursor to the preceding note (or silence) onset.")); | |
1073 connect(oneLeftAction, SIGNAL(triggered()), this, SLOT(moveOneNoteLeft())); | |
1074 connect(this, SIGNAL(canScroll(bool)), oneLeftAction, SLOT(setEnabled(bool))); | |
1075 | |
1076 QAction *oneRightAction = new QAction(tr("O&ne Note Right"), this); | |
1077 oneRightAction->setShortcut(tr("Ctrl+Right")); | |
1078 oneRightAction->setStatusTip(tr("Move cursor to the succeeding note (or silence).")); | |
1079 connect(oneRightAction, SIGNAL(triggered()), this, SLOT(moveOneNoteRight())); | |
1080 connect(this, SIGNAL(canScroll(bool)), oneRightAction, SLOT(setEnabled(bool))); | |
1081 | |
1082 QAction *selectOneLeftAction = new QAction(tr("&Select One Note Left"), this); | |
1083 selectOneLeftAction->setShortcut(tr("Ctrl+Shift+Left")); | |
1084 selectOneLeftAction->setStatusTip(tr("Select to the preceding note (or silence) onset.")); | |
1085 connect(selectOneLeftAction, SIGNAL(triggered()), this, SLOT(selectOneNoteLeft())); | |
1086 connect(this, SIGNAL(canScroll(bool)), selectOneLeftAction, SLOT(setEnabled(bool))); | |
1087 | |
1088 QAction *selectOneRightAction = new QAction(tr("S&elect One Note Right"), this); | |
1089 selectOneRightAction->setShortcut(tr("Ctrl+Shift+Right")); | |
1090 selectOneRightAction->setStatusTip(tr("Select to the succeeding note (or silence).")); | |
1091 connect(selectOneRightAction, SIGNAL(triggered()), this, SLOT(selectOneNoteRight())); | |
1092 connect(this, SIGNAL(canScroll(bool)), selectOneRightAction, SLOT(setEnabled(bool))); | |
1093 | |
1094 m_keyReference->registerShortcut(psAction); | |
1095 m_keyReference->registerShortcut(plAction); | |
1096 m_keyReference->registerShortcut(playAction); | |
1097 m_keyReference->registerShortcut(recordAction); | |
1098 m_keyReference->registerShortcut(m_rwdAction); | |
1099 m_keyReference->registerShortcut(m_ffwdAction); | |
1100 m_keyReference->registerShortcut(rwdStartAction); | |
1101 m_keyReference->registerShortcut(ffwdEndAction); | |
1102 m_keyReference->registerShortcut(recordAction); | |
1103 m_keyReference->registerShortcut(oneLeftAction); | |
1104 m_keyReference->registerShortcut(oneRightAction); | |
1105 m_keyReference->registerShortcut(selectOneLeftAction); | |
1106 m_keyReference->registerShortcut(selectOneRightAction); | |
1107 | |
1108 menu->addAction(playAction); | |
1109 menu->addAction(psAction); | |
1110 menu->addAction(plAction); | |
1111 menu->addSeparator(); | |
1112 menu->addAction(m_rwdAction); | |
1113 menu->addAction(m_ffwdAction); | |
1114 menu->addSeparator(); | |
1115 menu->addAction(rwdStartAction); | |
1116 menu->addAction(ffwdEndAction); | |
1117 menu->addSeparator(); | |
1118 menu->addAction(oneLeftAction); | |
1119 menu->addAction(oneRightAction); | |
1120 menu->addAction(selectOneLeftAction); | |
1121 menu->addAction(selectOneRightAction); | |
1122 menu->addSeparator(); | |
1123 menu->addAction(recordAction); | |
1124 menu->addSeparator(); | |
1125 | |
1126 m_rightButtonPlaybackMenu->addAction(playAction); | |
1127 m_rightButtonPlaybackMenu->addAction(psAction); | |
1128 m_rightButtonPlaybackMenu->addAction(plAction); | |
1129 m_rightButtonPlaybackMenu->addSeparator(); | |
1130 m_rightButtonPlaybackMenu->addAction(m_rwdAction); | |
1131 m_rightButtonPlaybackMenu->addAction(m_ffwdAction); | |
1132 m_rightButtonPlaybackMenu->addSeparator(); | |
1133 m_rightButtonPlaybackMenu->addAction(rwdStartAction); | |
1134 m_rightButtonPlaybackMenu->addAction(ffwdEndAction); | |
1135 m_rightButtonPlaybackMenu->addSeparator(); | |
1136 m_rightButtonPlaybackMenu->addAction(oneLeftAction); | |
1137 m_rightButtonPlaybackMenu->addAction(oneRightAction); | |
1138 m_rightButtonPlaybackMenu->addAction(selectOneLeftAction); | |
1139 m_rightButtonPlaybackMenu->addAction(selectOneRightAction); | |
1140 m_rightButtonPlaybackMenu->addSeparator(); | |
1141 m_rightButtonPlaybackMenu->addAction(recordAction); | |
1142 m_rightButtonPlaybackMenu->addSeparator(); | |
1143 | |
1144 QAction *fastAction = menu->addAction(tr("Speed Up")); | |
1145 fastAction->setShortcut(tr("Ctrl+PgUp")); | |
1146 fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); | |
1147 connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); | |
1148 connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); | |
1149 | |
1150 QAction *slowAction = menu->addAction(tr("Slow Down")); | |
1151 slowAction->setShortcut(tr("Ctrl+PgDown")); | |
1152 slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); | |
1153 connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); | |
1154 connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); | |
1155 | |
1156 QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); | |
1157 normalAction->setShortcut(tr("Ctrl+Home")); | |
1158 normalAction->setStatusTip(tr("Restore non-time-stretched playback")); | |
1159 connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); | |
1160 connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); | |
1161 | |
1162 m_keyReference->registerShortcut(fastAction); | |
1163 m_keyReference->registerShortcut(slowAction); | |
1164 m_keyReference->registerShortcut(normalAction); | |
1165 | |
1166 m_rightButtonPlaybackMenu->addAction(fastAction); | |
1167 m_rightButtonPlaybackMenu->addAction(slowAction); | |
1168 m_rightButtonPlaybackMenu->addAction(normalAction); | |
1169 | |
1170 toolbar = new QToolBar(tr("Playback Controls")); | |
1171 addToolBar(Qt::BottomToolBarArea, toolbar); | |
1172 | |
1173 toolbar->addWidget(m_playSpeed); | |
1174 toolbar->addWidget(m_fader); | |
1175 | |
1176 toolbar = addToolBar(tr("Show and Play")); | |
1177 addToolBar(Qt::BottomToolBarArea, toolbar); | |
1178 | |
1179 m_showAudio = toolbar->addAction(il.load("waveform"), tr("Show Audio")); | |
1180 m_showAudio->setCheckable(true); | |
1181 connect(m_showAudio, SIGNAL(triggered()), this, SLOT(showAudioToggled())); | |
1182 connect(this, SIGNAL(canPlay(bool)), m_showAudio, SLOT(setEnabled(bool))); | |
1183 | |
1184 m_playAudio = toolbar->addAction(il.load("speaker"), tr("Play Audio")); | |
1185 m_playAudio->setCheckable(true); | |
1186 connect(m_playAudio, SIGNAL(triggered()), this, SLOT(playAudioToggled())); | |
1187 connect(this, SIGNAL(canPlayWaveform(bool)), m_playAudio, SLOT(setEnabled(bool))); | |
1188 | |
1189 int lpwSize, bigLpwSize; | |
1190 #ifdef Q_OS_MAC | |
1191 lpwSize = m_viewManager->scalePixelSize(32); // Mac toolbars are fatter | |
1192 bigLpwSize = int(lpwSize * 2.2); | |
1193 #else | |
1194 lpwSize = m_viewManager->scalePixelSize(26); | |
1195 bigLpwSize = int(lpwSize * 2.8); | |
1196 #endif | |
1197 | |
1198 m_audioLPW->setImageSize(lpwSize); | |
1199 m_audioLPW->setBigImageSize(bigLpwSize); | |
1200 toolbar->addWidget(m_audioLPW); | |
1201 | |
1202 // Pitch (f0) | |
1203 QLabel *spacer = new QLabel; // blank | |
1204 spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); | |
1205 toolbar->addWidget(spacer); | |
1206 | |
1207 m_showPitch = toolbar->addAction(il.load("values"), tr("Show Pitch Track")); | |
1208 m_showPitch->setCheckable(true); | |
1209 connect(m_showPitch, SIGNAL(triggered()), this, SLOT(showPitchToggled())); | |
1210 connect(this, SIGNAL(canPlay(bool)), m_showPitch, SLOT(setEnabled(bool))); | |
1211 | |
1212 if (m_withSonification) { | |
1213 m_playPitch = toolbar->addAction(il.load("speaker"), tr("Play Pitch Track")); | |
1214 m_playPitch->setCheckable(true); | |
1215 connect(m_playPitch, SIGNAL(triggered()), this, SLOT(playPitchToggled())); | |
1216 connect(this, SIGNAL(canPlayPitch(bool)), m_playPitch, SLOT(setEnabled(bool))); | |
1217 | |
1218 m_pitchLPW->setImageSize(lpwSize); | |
1219 m_pitchLPW->setBigImageSize(bigLpwSize); | |
1220 toolbar->addWidget(m_pitchLPW); | |
1221 } else { | |
1222 m_playPitch = 0; | |
1223 } | |
1224 | |
1225 // Notes | |
1226 spacer = new QLabel; | |
1227 spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); | |
1228 toolbar->addWidget(spacer); | |
1229 | |
1230 m_showNotes = toolbar->addAction(il.load("notes"), tr("Show Notes")); | |
1231 m_showNotes->setCheckable(true); | |
1232 connect(m_showNotes, SIGNAL(triggered()), this, SLOT(showNotesToggled())); | |
1233 connect(this, SIGNAL(canPlay(bool)), m_showNotes, SLOT(setEnabled(bool))); | |
1234 | |
1235 if (m_withSonification) { | |
1236 m_playNotes = toolbar->addAction(il.load("speaker"), tr("Play Notes")); | |
1237 m_playNotes->setCheckable(true); | |
1238 connect(m_playNotes, SIGNAL(triggered()), this, SLOT(playNotesToggled())); | |
1239 connect(this, SIGNAL(canPlayNotes(bool)), m_playNotes, SLOT(setEnabled(bool))); | |
1240 | |
1241 m_notesLPW->setImageSize(lpwSize); | |
1242 m_notesLPW->setBigImageSize(bigLpwSize); | |
1243 toolbar->addWidget(m_notesLPW); | |
1244 } else { | |
1245 m_playNotes = 0; | |
1246 } | |
1247 | |
1248 // Spectrogram | |
1249 spacer = new QLabel; | |
1250 spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); | |
1251 toolbar->addWidget(spacer); | |
1252 | |
1253 if (!m_withSpectrogram) | |
1254 { | |
1255 m_showSpect = new QAction(tr("Show Spectrogram"), this); | |
1256 } else { | |
1257 m_showSpect = toolbar->addAction(il.load("spectrogram"), tr("Show Spectrogram")); | |
1258 } | |
1259 m_showSpect->setCheckable(true); | |
1260 connect(m_showSpect, SIGNAL(triggered()), this, SLOT(showSpectToggled())); | |
1261 connect(this, SIGNAL(canPlay(bool)), m_showSpect, SLOT(setEnabled(bool))); | |
1262 | |
1263 Pane::registerShortcuts(*m_keyReference); | |
1264 | |
1265 updateLayerStatuses(); | |
1266 } | |
1267 | |
1268 | |
1269 void | |
1270 MainWindow::moveOneNoteRight() | |
1271 { | |
1272 // cerr << "MainWindow::moveOneNoteRight" << endl; | |
1273 moveByOneNote(true, false); | |
1274 } | |
1275 | |
1276 void | |
1277 MainWindow::moveOneNoteLeft() | |
1278 { | |
1279 // cerr << "MainWindow::moveOneNoteLeft" << endl; | |
1280 moveByOneNote(false, false); | |
1281 } | |
1282 | |
1283 void | |
1284 MainWindow::selectOneNoteRight() | |
1285 { | |
1286 moveByOneNote(true, true); | |
1287 } | |
1288 | |
1289 void | |
1290 MainWindow::selectOneNoteLeft() | |
1291 { | |
1292 moveByOneNote(false, true); | |
1293 } | |
1294 | |
1295 | |
1296 void | |
1297 MainWindow::moveByOneNote(bool right, bool doSelect) | |
1298 { | |
1299 sv_frame_t frame = m_viewManager->getPlaybackFrame(); | |
1300 cerr << "MainWindow::moveByOneNote startframe: " << frame << endl; | |
1301 | |
1302 bool isAtSelectionBoundary = false; | |
1303 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
1304 if (!selections.empty()) { | |
1305 Selection sel = *selections.begin(); | |
1306 isAtSelectionBoundary = (frame == sel.getStartFrame()) || (frame == sel.getEndFrame()); | |
1307 } | |
1308 if (!doSelect || !isAtSelectionBoundary) { | |
1309 m_selectionAnchor = frame; | |
1310 } | |
1311 | |
1312 Layer *layer = m_analyser->getLayer(Analyser::Notes); | |
1313 if (!layer) return; | |
1314 | |
1315 auto model = ModelById::getAs<NoteModel>(layer->getModel()); | |
1316 if (!model) return; | |
1317 | |
1318 //!!! This seems like a strange and inefficient way to do this - | |
1319 //!!! there is almost certainly a better way making use of | |
1320 //!!! EventSeries api | |
1321 | |
1322 EventVector points = model->getAllEvents(); | |
1323 if (points.empty()) return; | |
1324 | |
1325 EventVector::iterator i = points.begin(); | |
1326 std::set<sv_frame_t> snapFrames; | |
1327 snapFrames.insert(0); | |
1328 while (i != points.end()) { | |
1329 snapFrames.insert(i->getFrame()); | |
1330 snapFrames.insert(i->getFrame() + i->getDuration() + 1); | |
1331 ++i; | |
1332 } | |
1333 std::set<sv_frame_t>::iterator i2; | |
1334 if (snapFrames.find(frame) == snapFrames.end()) { | |
1335 // we're not on an existing snap point, so go to previous | |
1336 snapFrames.insert(frame); | |
1337 } | |
1338 i2 = snapFrames.find(frame); | |
1339 if (right) { | |
1340 i2++; | |
1341 if (i2 == snapFrames.end()) i2--; | |
1342 } else { | |
1343 if (i2 != snapFrames.begin()) i2--; | |
1344 } | |
1345 frame = *i2; | |
1346 m_viewManager->setPlaybackFrame(frame); | |
1347 if (doSelect) { | |
1348 Selection sel; | |
1349 if (frame > m_selectionAnchor) { | |
1350 sel = Selection(m_selectionAnchor, frame); | |
1351 } else { | |
1352 sel = Selection(frame, m_selectionAnchor); | |
1353 } | |
1354 m_viewManager->setSelection(sel); | |
1355 } | |
1356 cerr << "MainWindow::moveByOneNote endframe: " << frame << endl; | |
1357 } | |
1358 | |
1359 void | |
1360 MainWindow::toolNavigateSelected() | |
1361 { | |
1362 m_viewManager->setToolMode(ViewManager::NavigateMode); | |
1363 m_intelligentActionOn = true; | |
1364 } | |
1365 | |
1366 void | |
1367 MainWindow::toolEditSelected() | |
1368 { | |
1369 cerr << "MainWindow::toolEditSelected" << endl; | |
1370 m_viewManager->setToolMode(ViewManager::NoteEditMode); | |
1371 m_intelligentActionOn = true; | |
1372 m_analyser->setIntelligentActions(m_intelligentActionOn); | |
1373 } | |
1374 | |
1375 void | |
1376 MainWindow::toolFreeEditSelected() | |
1377 { | |
1378 m_viewManager->setToolMode(ViewManager::NoteEditMode); | |
1379 m_intelligentActionOn = false; | |
1380 m_analyser->setIntelligentActions(m_intelligentActionOn); | |
1381 } | |
1382 | |
1383 void | |
1384 MainWindow::updateMenuStates() | |
1385 { | |
1386 MainWindowBase::updateMenuStates(); | |
1387 | |
1388 Pane *currentPane = 0; | |
1389 Layer *currentLayer = 0; | |
1390 | |
1391 if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); | |
1392 if (currentPane) currentLayer = currentPane->getSelectedLayer(); | |
1393 | |
1394 bool haveMainModel = | |
1395 (getMainModel() != 0); | |
1396 bool havePlayTarget = | |
1397 (m_playTarget != 0 || m_audioIO != 0); | |
1398 bool haveCurrentPane = | |
1399 (currentPane != 0); | |
1400 bool haveCurrentLayer = | |
1401 (haveCurrentPane && | |
1402 (currentLayer != 0)); | |
1403 bool haveSelection = | |
1404 (m_viewManager && | |
1405 !m_viewManager->getSelections().empty()); | |
1406 bool haveCurrentTimeInstantsLayer = | |
1407 (haveCurrentLayer && | |
1408 qobject_cast<TimeInstantLayer *>(currentLayer)); | |
1409 bool haveCurrentTimeValueLayer = | |
1410 (haveCurrentLayer && | |
1411 qobject_cast<TimeValueLayer *>(currentLayer)); | |
1412 bool pitchCandidatesVisible = | |
1413 m_analyser->arePitchCandidatesShown(); | |
1414 | |
1415 emit canChangePlaybackSpeed(true); | |
1416 int v = m_playSpeed->value(); | |
1417 emit canSpeedUpPlayback(v < m_playSpeed->maximum()); | |
1418 emit canSlowDownPlayback(v > m_playSpeed->minimum()); | |
1419 | |
1420 bool haveWaveform = | |
1421 m_analyser->isVisible(Analyser::Audio) && | |
1422 m_analyser->getLayer(Analyser::Audio); | |
1423 | |
1424 bool havePitchTrack = | |
1425 m_analyser->isVisible(Analyser::PitchTrack) && | |
1426 m_analyser->getLayer(Analyser::PitchTrack); | |
1427 | |
1428 bool haveNotes = | |
1429 m_analyser->isVisible(Analyser::Notes) && | |
1430 m_analyser->getLayer(Analyser::Notes); | |
1431 | |
1432 emit canExportPitchTrack(havePitchTrack); | |
1433 emit canExportNotes(haveNotes); | |
1434 emit canSnapNotes(haveSelection && haveNotes); | |
1435 | |
1436 emit canPlayWaveform(haveWaveform && haveMainModel && havePlayTarget); | |
1437 emit canPlayPitch(havePitchTrack && haveMainModel && havePlayTarget); | |
1438 emit canPlayNotes(haveNotes && haveMainModel && havePlayTarget); | |
1439 | |
1440 if (pitchCandidatesVisible) { | |
1441 m_showCandidatesAction->setText(tr("Hide Pitch Candidates")); | |
1442 m_showCandidatesAction->setStatusTip(tr("Remove the display of alternate pitch candidates for the selected region")); | |
1443 } else { | |
1444 m_showCandidatesAction->setText(tr("Show Pitch Candidates")); | |
1445 m_showCandidatesAction->setStatusTip(tr("Show alternate pitch candidates for the selected region")); | |
1446 } | |
1447 | |
1448 if (m_ffwdAction && m_rwdAction) { | |
1449 if (haveCurrentTimeInstantsLayer) { | |
1450 m_ffwdAction->setText(tr("Fast Forward to Next Instant")); | |
1451 m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); | |
1452 m_rwdAction->setText(tr("Rewind to Previous Instant")); | |
1453 m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); | |
1454 } else if (haveCurrentTimeValueLayer) { | |
1455 m_ffwdAction->setText(tr("Fast Forward to Next Point")); | |
1456 m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); | |
1457 m_rwdAction->setText(tr("Rewind to Previous Point")); | |
1458 m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); | |
1459 } else { | |
1460 m_ffwdAction->setText(tr("Fast Forward")); | |
1461 m_ffwdAction->setStatusTip(tr("Fast forward")); | |
1462 m_rwdAction->setText(tr("Rewind")); | |
1463 m_rwdAction->setStatusTip(tr("Rewind")); | |
1464 } | |
1465 } | |
1466 } | |
1467 | |
1468 void | |
1469 MainWindow::showAudioToggled() | |
1470 { | |
1471 m_analyser->toggleVisible(Analyser::Audio); | |
1472 | |
1473 QSettings settings; | |
1474 settings.beginGroup("MainWindow"); | |
1475 | |
1476 bool playOn = false; | |
1477 if (m_analyser->isVisible(Analyser::Audio)) { | |
1478 // just switched layer on; check whether playback was also on previously | |
1479 playOn = settings.value("playaudiowas", true).toBool(); | |
1480 } else { | |
1481 settings.setValue("playaudiowas", m_playAudio->isChecked()); | |
1482 } | |
1483 m_analyser->setAudible(Analyser::Audio, playOn); | |
1484 | |
1485 settings.endGroup(); | |
1486 | |
1487 updateMenuStates(); | |
1488 updateLayerStatuses(); | |
1489 } | |
1490 | |
1491 void | |
1492 MainWindow::showPitchToggled() | |
1493 { | |
1494 m_analyser->toggleVisible(Analyser::PitchTrack); | |
1495 | |
1496 QSettings settings; | |
1497 settings.beginGroup("MainWindow"); | |
1498 | |
1499 bool playOn = false; | |
1500 if (m_analyser->isVisible(Analyser::PitchTrack)) { | |
1501 // just switched layer on; check whether playback was also on previously | |
1502 playOn = settings.value("playpitchwas", true).toBool(); | |
1503 } else { | |
1504 settings.setValue("playpitchwas", m_playPitch->isChecked()); | |
1505 } | |
1506 m_analyser->setAudible(Analyser::PitchTrack, playOn); | |
1507 | |
1508 settings.endGroup(); | |
1509 | |
1510 updateMenuStates(); | |
1511 updateLayerStatuses(); | |
1512 } | |
1513 | |
1514 void | |
1515 MainWindow::showSpectToggled() | |
1516 { | |
1517 m_analyser->toggleVisible(Analyser::Spectrogram); | |
1518 } | |
1519 | |
1520 void | |
1521 MainWindow::showNotesToggled() | |
1522 { | |
1523 m_analyser->toggleVisible(Analyser::Notes); | |
1524 | |
1525 QSettings settings; | |
1526 settings.beginGroup("MainWindow"); | |
1527 | |
1528 bool playOn = false; | |
1529 if (m_analyser->isVisible(Analyser::Notes)) { | |
1530 // just switched layer on; check whether playback was also on previously | |
1531 playOn = settings.value("playnoteswas", true).toBool(); | |
1532 } else { | |
1533 settings.setValue("playnoteswas", m_playNotes->isChecked()); | |
1534 } | |
1535 m_analyser->setAudible(Analyser::Notes, playOn); | |
1536 | |
1537 settings.endGroup(); | |
1538 | |
1539 updateMenuStates(); | |
1540 updateLayerStatuses(); | |
1541 } | |
1542 | |
1543 void | |
1544 MainWindow::playAudioToggled() | |
1545 { | |
1546 m_analyser->toggleAudible(Analyser::Audio); | |
1547 updateLayerStatuses(); | |
1548 } | |
1549 | |
1550 void | |
1551 MainWindow::playPitchToggled() | |
1552 { | |
1553 m_analyser->toggleAudible(Analyser::PitchTrack); | |
1554 updateLayerStatuses(); | |
1555 } | |
1556 | |
1557 void | |
1558 MainWindow::playNotesToggled() | |
1559 { | |
1560 m_analyser->toggleAudible(Analyser::Notes); | |
1561 updateLayerStatuses(); | |
1562 } | |
1563 | |
1564 void | |
1565 MainWindow::updateLayerStatuses() | |
1566 { | |
1567 m_showAudio->setChecked(m_analyser->isVisible(Analyser::Audio)); | |
1568 m_playAudio->setChecked(m_analyser->isAudible(Analyser::Audio)); | |
1569 m_audioLPW->setEnabled(m_analyser->isAudible(Analyser::Audio)); | |
1570 m_audioLPW->setLevel(m_analyser->getGain(Analyser::Audio)); | |
1571 m_audioLPW->setPan(m_analyser->getPan(Analyser::Audio)); | |
1572 | |
1573 m_showPitch->setChecked(m_analyser->isVisible(Analyser::PitchTrack)); | |
1574 m_playPitch->setChecked(m_analyser->isAudible(Analyser::PitchTrack)); | |
1575 m_pitchLPW->setEnabled(m_analyser->isAudible(Analyser::PitchTrack)); | |
1576 m_pitchLPW->setLevel(m_analyser->getGain(Analyser::PitchTrack)); | |
1577 m_pitchLPW->setPan(m_analyser->getPan(Analyser::PitchTrack)); | |
1578 | |
1579 m_showNotes->setChecked(m_analyser->isVisible(Analyser::Notes)); | |
1580 m_playNotes->setChecked(m_analyser->isAudible(Analyser::Notes)); | |
1581 m_notesLPW->setEnabled(m_analyser->isAudible(Analyser::Notes)); | |
1582 m_notesLPW->setLevel(m_analyser->getGain(Analyser::Notes)); | |
1583 m_notesLPW->setPan(m_analyser->getPan(Analyser::Notes)); | |
1584 | |
1585 m_showSpect->setChecked(m_analyser->isVisible(Analyser::Spectrogram)); | |
1586 } | |
1587 | |
1588 void | |
1589 MainWindow::editDisplayExtents() | |
1590 { | |
1591 double min, max; | |
1592 double vmin = 0; | |
1593 double vmax = getMainModel()->getSampleRate() /2; | |
1594 | |
1595 if (!m_analyser->getDisplayFrequencyExtents(min, max)) { | |
1596 //!!! | |
1597 return; | |
1598 } | |
1599 | |
1600 RangeInputDialog dialog(tr("Set frequency range"), | |
1601 tr("Enter new frequency range, from %1 to %2 Hz.\nThese values will be rounded to the nearest spectrogram bin.") | |
1602 .arg(vmin).arg(vmax), | |
1603 "Hz", float(vmin), float(vmax), this); | |
1604 dialog.setRange(float(min), float(max)); | |
1605 | |
1606 if (dialog.exec() == QDialog::Accepted) { | |
1607 float fmin, fmax; | |
1608 dialog.getRange(fmin, fmax); | |
1609 min = fmin; | |
1610 max = fmax; | |
1611 if (min > max) { | |
1612 double tmp = max; | |
1613 max = min; | |
1614 min = tmp; | |
1615 } | |
1616 m_analyser->setDisplayFrequencyExtents(min, max); | |
1617 } | |
1618 } | |
1619 | |
1620 void | |
1621 MainWindow::updateDescriptionLabel() | |
1622 { | |
1623 // Nothing, we don't have one | |
1624 } | |
1625 | |
1626 void | |
1627 MainWindow::documentModified() | |
1628 { | |
1629 MainWindowBase::documentModified(); | |
1630 } | |
1631 | |
1632 void | |
1633 MainWindow::documentRestored() | |
1634 { | |
1635 MainWindowBase::documentRestored(); | |
1636 } | |
1637 | |
1638 void | |
1639 MainWindow::newSession() | |
1640 { | |
1641 if (!checkSaveModified()) return; | |
1642 | |
1643 closeSession(); | |
1644 createDocument(); | |
1645 m_document->setAutoAlignment(true); | |
1646 | |
1647 Pane *pane = m_paneStack->addPane(); | |
1648 pane->setPlaybackFollow(PlaybackScrollPage); | |
1649 | |
1650 m_viewManager->setGlobalCentreFrame | |
1651 (pane->getFrameForX(width() / 2)); | |
1652 | |
1653 connect(pane, SIGNAL(contextHelpChanged(const QString &)), | |
1654 this, SLOT(contextHelpChanged(const QString &))); | |
1655 | |
1656 // Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); | |
1657 // m_document->addLayerToView(pane, waveform); | |
1658 | |
1659 m_overview->registerView(pane); | |
1660 | |
1661 CommandHistory::getInstance()->clear(); | |
1662 CommandHistory::getInstance()->documentSaved(); | |
1663 documentRestored(); | |
1664 updateMenuStates(); | |
1665 } | |
1666 | |
1667 void | |
1668 MainWindow::documentReplaced() | |
1669 { | |
1670 if (m_document) { | |
1671 connect(m_document, SIGNAL(activity(QString)), | |
1672 m_activityLog, SLOT(activityHappened(QString))); | |
1673 } | |
1674 } | |
1675 | |
1676 void | |
1677 MainWindow::closeSession() | |
1678 { | |
1679 if (!checkSaveModified()) return; | |
1680 | |
1681 m_analyser->fileClosed(); | |
1682 | |
1683 while (m_paneStack->getPaneCount() > 0) { | |
1684 | |
1685 Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); | |
1686 | |
1687 while (pane->getLayerCount() > 0) { | |
1688 m_document->removeLayerFromView | |
1689 (pane, pane->getLayer(pane->getLayerCount() - 1)); | |
1690 } | |
1691 | |
1692 m_overview->unregisterView(pane); | |
1693 m_paneStack->deletePane(pane); | |
1694 } | |
1695 | |
1696 while (m_paneStack->getHiddenPaneCount() > 0) { | |
1697 | |
1698 Pane *pane = m_paneStack->getHiddenPane | |
1699 (m_paneStack->getHiddenPaneCount() - 1); | |
1700 | |
1701 while (pane->getLayerCount() > 0) { | |
1702 m_document->removeLayerFromView | |
1703 (pane, pane->getLayer(pane->getLayerCount() - 1)); | |
1704 } | |
1705 | |
1706 m_overview->unregisterView(pane); | |
1707 m_paneStack->deletePane(pane); | |
1708 } | |
1709 | |
1710 delete m_document; | |
1711 m_document = 0; | |
1712 m_viewManager->clearSelections(); | |
1713 m_timeRulerLayer = 0; // document owned this | |
1714 | |
1715 m_sessionFile = ""; | |
1716 | |
1717 CommandHistory::getInstance()->clear(); | |
1718 CommandHistory::getInstance()->documentSaved(); | |
1719 documentRestored(); | |
1720 } | |
1721 | |
1722 void | |
1723 MainWindow::openFile() | |
1724 { | |
1725 QString orig = m_audioFile; | |
1726 if (orig == "") orig = "."; | |
1727 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); | |
1728 | |
1729 QString path = getOpenFileName(FileFinder::AnyFile); | |
1730 | |
1731 if (path.isEmpty()) return; | |
1732 | |
1733 FileOpenStatus status = openPath(path, ReplaceSession); | |
1734 | |
1735 if (status == FileOpenFailed) { | |
1736 QMessageBox::critical(this, tr("Failed to open file"), | |
1737 tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path)); | |
1738 } else if (status == FileOpenWrongMode) { | |
1739 QMessageBox::critical(this, tr("Failed to open file"), | |
1740 tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | |
1741 } | |
1742 } | |
1743 | |
1744 void | |
1745 MainWindow::openLocation() | |
1746 { | |
1747 QSettings settings; | |
1748 settings.beginGroup("MainWindow"); | |
1749 QString lastLocation = settings.value("lastremote", "").toString(); | |
1750 | |
1751 bool ok = false; | |
1752 QString text = QInputDialog::getText | |
1753 (this, tr("Open Location"), | |
1754 tr("Please enter the URL of the location to open:"), | |
1755 QLineEdit::Normal, lastLocation, &ok); | |
1756 | |
1757 if (!ok) return; | |
1758 | |
1759 settings.setValue("lastremote", text); | |
1760 | |
1761 if (text.isEmpty()) return; | |
1762 | |
1763 FileOpenStatus status = openPath(text, ReplaceSession); | |
1764 | |
1765 if (status == FileOpenFailed) { | |
1766 QMessageBox::critical(this, tr("Failed to open location"), | |
1767 tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text)); | |
1768 } else if (status == FileOpenWrongMode) { | |
1769 QMessageBox::critical(this, tr("Failed to open location"), | |
1770 tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | |
1771 } | |
1772 } | |
1773 | |
1774 void | |
1775 MainWindow::openRecentFile() | |
1776 { | |
1777 QObject *obj = sender(); | |
1778 QAction *action = qobject_cast<QAction *>(obj); | |
1779 | |
1780 if (!action) { | |
1781 cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" | |
1782 << endl; | |
1783 return; | |
1784 } | |
1785 | |
1786 QString path = action->text(); | |
1787 if (path == "") return; | |
1788 | |
1789 FileOpenStatus status = openPath(path, ReplaceSession); | |
1790 | |
1791 if (status == FileOpenFailed) { | |
1792 QMessageBox::critical(this, tr("Failed to open location"), | |
1793 tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path)); | |
1794 } else if (status == FileOpenWrongMode) { | |
1795 QMessageBox::critical(this, tr("Failed to open location"), | |
1796 tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | |
1797 } | |
1798 } | |
1799 | |
1800 void | |
1801 MainWindow::paneAdded(Pane *pane) | |
1802 { | |
1803 pane->setPlaybackFollow(PlaybackScrollPage); | |
1804 m_paneStack->sizePanesEqually(); | |
1805 if (m_overview) m_overview->registerView(pane); | |
1806 } | |
1807 | |
1808 void | |
1809 MainWindow::paneHidden(Pane *pane) | |
1810 { | |
1811 if (m_overview) m_overview->unregisterView(pane); | |
1812 } | |
1813 | |
1814 void | |
1815 MainWindow::paneAboutToBeDeleted(Pane *pane) | |
1816 { | |
1817 if (m_overview) m_overview->unregisterView(pane); | |
1818 } | |
1819 | |
1820 void | |
1821 MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) | |
1822 { | |
1823 if (pane) m_paneStack->setCurrentPane(pane); | |
1824 | |
1825 for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { | |
1826 | |
1827 FileOpenStatus status = openPath(*i, ReplaceSession); | |
1828 | |
1829 if (status == FileOpenFailed) { | |
1830 QMessageBox::critical(this, tr("Failed to open dropped URL"), | |
1831 tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i)); | |
1832 } else if (status == FileOpenWrongMode) { | |
1833 QMessageBox::critical(this, tr("Failed to open dropped URL"), | |
1834 tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | |
1835 } | |
1836 } | |
1837 } | |
1838 | |
1839 void | |
1840 MainWindow::paneDropAccepted(Pane *pane, QString text) | |
1841 { | |
1842 if (pane) m_paneStack->setCurrentPane(pane); | |
1843 | |
1844 QUrl testUrl(text); | |
1845 if (testUrl.scheme() == "file" || | |
1846 testUrl.scheme() == "http" || | |
1847 testUrl.scheme() == "ftp") { | |
1848 QStringList list; | |
1849 list.push_back(text); | |
1850 paneDropAccepted(pane, list); | |
1851 return; | |
1852 } | |
1853 | |
1854 //!!! open as text -- but by importing as if a CSV, or just adding | |
1855 //to a text layer? | |
1856 } | |
1857 | |
1858 void | |
1859 MainWindow::closeEvent(QCloseEvent *e) | |
1860 { | |
1861 // cerr << "MainWindow::closeEvent" << endl; | |
1862 | |
1863 if (m_openingAudioFile) { | |
1864 // cerr << "Busy - ignoring close event" << endl; | |
1865 e->ignore(); | |
1866 return; | |
1867 } | |
1868 | |
1869 if (!m_abandoning && !checkSaveModified()) { | |
1870 // cerr << "Ignoring close event" << endl; | |
1871 e->ignore(); | |
1872 return; | |
1873 } | |
1874 | |
1875 QSettings settings; | |
1876 settings.beginGroup("MainWindow"); | |
1877 settings.setValue("size", size()); | |
1878 settings.setValue("position", pos()); | |
1879 settings.endGroup(); | |
1880 | |
1881 delete m_keyReference; | |
1882 m_keyReference = 0; | |
1883 | |
1884 closeSession(); | |
1885 | |
1886 e->accept(); | |
1887 return; | |
1888 } | |
1889 | |
1890 bool | |
1891 MainWindow::commitData(bool mayAskUser) | |
1892 { | |
1893 if (mayAskUser) { | |
1894 bool rv = checkSaveModified(); | |
1895 return rv; | |
1896 } else { | |
1897 if (!m_documentModified) return true; | |
1898 | |
1899 // If we can't check with the user first, then we can't save | |
1900 // to the original session file (even if we have it) -- have | |
1901 // to use a temporary file | |
1902 | |
1903 QString svDirBase = ".sv1"; | |
1904 QString svDir = QDir::home().filePath(svDirBase); | |
1905 | |
1906 if (!QFileInfo(svDir).exists()) { | |
1907 if (!QDir::home().mkdir(svDirBase)) return false; | |
1908 } else { | |
1909 if (!QFileInfo(svDir).isDir()) return false; | |
1910 } | |
1911 | |
1912 // This name doesn't have to be unguessable | |
1913 #ifndef _WIN32 | |
1914 QString fname = QString("tmp-%1-%2.sv") | |
1915 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) | |
1916 .arg(QProcess().pid()); | |
1917 #else | |
1918 QString fname = QString("tmp-%1.sv") | |
1919 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); | |
1920 #endif | |
1921 QString fpath = QDir(svDir).filePath(fname); | |
1922 if (saveSessionFile(fpath)) { | |
1923 m_recentFiles.addFile(fpath); | |
1924 return true; | |
1925 } else { | |
1926 return false; | |
1927 } | |
1928 } | |
1929 } | |
1930 | |
1931 bool | |
1932 MainWindow::checkSaveModified() | |
1933 { | |
1934 // Called before some destructive operation (e.g. new session, | |
1935 // exit program). Return true if we can safely proceed, false to | |
1936 // cancel. | |
1937 | |
1938 if (!m_documentModified) return true; | |
1939 | |
1940 int button = | |
1941 QMessageBox::warning(this, | |
1942 tr("Session modified"), | |
1943 tr("The current session has been modified.\nDo you want to save it?"), | |
1944 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, | |
1945 QMessageBox::Yes); | |
1946 | |
1947 if (button == QMessageBox::Yes) { | |
1948 saveSession(); | |
1949 if (m_documentModified) { // save failed -- don't proceed! | |
1950 return false; | |
1951 } else { | |
1952 return true; // saved, so it's safe to continue now | |
1953 } | |
1954 } else if (button == QMessageBox::No) { | |
1955 m_documentModified = false; // so we know to abandon it | |
1956 return true; | |
1957 } | |
1958 | |
1959 // else cancel | |
1960 return false; | |
1961 } | |
1962 | |
1963 bool | |
1964 MainWindow::waitForInitialAnalysis() | |
1965 { | |
1966 // Called before saving a session. We can't safely save while the | |
1967 // initial analysis is happening, because then we end up with an | |
1968 // incomplete session on reload. There are certainly theoretically | |
1969 // better ways to handle this... | |
1970 | |
1971 QSettings settings; | |
1972 settings.beginGroup("Analyser"); | |
1973 bool autoAnalyse = settings.value("auto-analysis", true).toBool(); | |
1974 settings.endGroup(); | |
1975 | |
1976 if (!autoAnalyse) { | |
1977 return true; | |
1978 } | |
1979 | |
1980 if (!m_analyser || m_analyser->getInitialAnalysisCompletion() >= 100) { | |
1981 return true; | |
1982 } | |
1983 | |
1984 QMessageBox mb(QMessageBox::Information, | |
1985 tr("Waiting for analysis"), | |
1986 tr("Waiting for initial analysis to finish before loading or saving..."), | |
1987 QMessageBox::Cancel, | |
1988 this); | |
1989 | |
1990 connect(m_analyser, SIGNAL(initialAnalysisCompleted()), | |
1991 &mb, SLOT(accept())); | |
1992 | |
1993 if (mb.exec() == QDialog::Accepted) { | |
1994 return true; | |
1995 } else { | |
1996 return false; | |
1997 } | |
1998 } | |
1999 | |
2000 void | |
2001 MainWindow::saveSession() | |
2002 { | |
2003 // We do not want to save mid-analysis regions -- that would cause | |
2004 // confusion on reloading | |
2005 m_analyser->clearReAnalysis(); | |
2006 clearSelection(); | |
2007 | |
2008 if (m_sessionFile != "") { | |
2009 if (!saveSessionFile(m_sessionFile)) { | |
2010 QMessageBox::critical | |
2011 (this, tr("Failed to save file"), | |
2012 tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); | |
2013 } else { | |
2014 CommandHistory::getInstance()->documentSaved(); | |
2015 documentRestored(); | |
2016 } | |
2017 } else { | |
2018 saveSessionAs(); | |
2019 } | |
2020 } | |
2021 | |
2022 void | |
2023 MainWindow::saveSessionInAudioPath() | |
2024 { | |
2025 if (m_audioFile == "") return; | |
2026 | |
2027 if (!waitForInitialAnalysis()) return; | |
2028 | |
2029 // We do not want to save mid-analysis regions -- that would cause | |
2030 // confusion on reloading | |
2031 m_analyser->clearReAnalysis(); | |
2032 clearSelection(); | |
2033 | |
2034 QString filepath = QFileInfo(m_audioFile).absoluteDir().canonicalPath(); | |
2035 QString basename = QFileInfo(m_audioFile).completeBaseName(); | |
2036 | |
2037 QString path = QDir(filepath).filePath(basename + ".ton"); | |
2038 | |
2039 cerr << path << endl; | |
2040 | |
2041 // We don't want to overwrite an existing .ton file unless we put | |
2042 // it there in the first place | |
2043 bool shouldVerify = true; | |
2044 if (m_sessionFile == path) { | |
2045 shouldVerify = false; | |
2046 } | |
2047 | |
2048 if (shouldVerify && QFileInfo(path).exists()) { | |
2049 if (QMessageBox::question(0, tr("File exists"), | |
2050 tr("<b>File exists</b><p>The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), | |
2051 QMessageBox::Ok, | |
2052 QMessageBox::Cancel) != QMessageBox::Ok) { | |
2053 return; | |
2054 } | |
2055 } | |
2056 | |
2057 if (!waitForInitialAnalysis()) { | |
2058 QMessageBox::warning(this, tr("File not saved"), | |
2059 tr("Wait cancelled: the session has not been saved.")); | |
2060 } | |
2061 | |
2062 if (!saveSessionFile(path)) { | |
2063 QMessageBox::critical(this, tr("Failed to save file"), | |
2064 tr("Session file \"%1\" could not be saved.").arg(path)); | |
2065 } else { | |
2066 setWindowTitle(tr("%1: %2") | |
2067 .arg(QApplication::applicationName()) | |
2068 .arg(QFileInfo(path).fileName())); | |
2069 m_sessionFile = path; | |
2070 CommandHistory::getInstance()->documentSaved(); | |
2071 documentRestored(); | |
2072 m_recentFiles.addFile(path); | |
2073 } | |
2074 } | |
2075 | |
2076 void | |
2077 MainWindow::saveSessionAs() | |
2078 { | |
2079 // We do not want to save mid-analysis regions -- that would cause | |
2080 // confusion on reloading | |
2081 m_analyser->clearReAnalysis(); | |
2082 clearSelection(); | |
2083 | |
2084 QString path = getSaveFileName(FileFinder::SessionFile); | |
2085 | |
2086 if (path == "") { | |
2087 return; | |
2088 } | |
2089 | |
2090 if (!waitForInitialAnalysis()) { | |
2091 QMessageBox::warning(this, tr("File not saved"), | |
2092 tr("Wait cancelled: the session has not been saved.")); | |
2093 return; | |
2094 } | |
2095 | |
2096 if (!saveSessionFile(path)) { | |
2097 QMessageBox::critical(this, tr("Failed to save file"), | |
2098 tr("Session file \"%1\" could not be saved.").arg(path)); | |
2099 } else { | |
2100 setWindowTitle(tr("%1: %2") | |
2101 .arg(QApplication::applicationName()) | |
2102 .arg(QFileInfo(path).fileName())); | |
2103 m_sessionFile = path; | |
2104 CommandHistory::getInstance()->documentSaved(); | |
2105 documentRestored(); | |
2106 m_recentFiles.addFile(path); | |
2107 } | |
2108 } | |
2109 | |
2110 QString | |
2111 MainWindow::exportToSVL(QString path, Layer *layer) | |
2112 { | |
2113 auto model = ModelById::get(layer->getModel()); | |
2114 if (!model) return "Internal error: No model in layer"; | |
2115 | |
2116 QFile file(path); | |
2117 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { | |
2118 return tr("Failed to open file %1 for writing").arg(path); | |
2119 } else { | |
2120 QTextStream out(&file); | |
2121 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | |
2122 << "<!DOCTYPE sonic-visualiser>\n" | |
2123 << "<sv>\n" | |
2124 << " <data>\n"; | |
2125 | |
2126 model->toXml(out, " "); | |
2127 | |
2128 out << " </data>\n" | |
2129 << " <display>\n"; | |
2130 | |
2131 layer->toXml(out, " "); | |
2132 | |
2133 out << " </display>\n" | |
2134 << "</sv>\n"; | |
2135 | |
2136 return ""; | |
2137 } | |
2138 } | |
2139 | |
2140 void | |
2141 MainWindow::importPitchLayer() | |
2142 { | |
2143 QString path = getOpenFileName(FileFinder::LayerFileNoMidiNonSV); | |
2144 if (path == "") return; | |
2145 | |
2146 FileOpenStatus status = importPitchLayer(path); | |
2147 | |
2148 if (status == FileOpenFailed) { | |
2149 emit hideSplash(); | |
2150 QMessageBox::critical(this, tr("Failed to open file"), | |
2151 tr("<b>File open failed</b><p>Layer file %1 could not be opened.").arg(path)); | |
2152 return; | |
2153 } else if (status == FileOpenWrongMode) { | |
2154 emit hideSplash(); | |
2155 QMessageBox::critical(this, tr("Failed to open file"), | |
2156 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path)); | |
2157 } | |
2158 } | |
2159 | |
2160 MainWindow::FileOpenStatus | |
2161 MainWindow::importPitchLayer(FileSource source) | |
2162 { | |
2163 if (!source.isAvailable()) return FileOpenFailed; | |
2164 source.waitForData(); | |
2165 | |
2166 if (!waitForInitialAnalysis()) return FileOpenCancelled; | |
2167 | |
2168 QString path = source.getLocalFilename(); | |
2169 | |
2170 RDFImporter::RDFDocumentType rdfType = | |
2171 RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); | |
2172 | |
2173 if (rdfType != RDFImporter::NotRDF) { | |
2174 | |
2175 //!!! | |
2176 return FileOpenFailed; | |
2177 | |
2178 } else if (source.getExtension().toLower() == "svl" || | |
2179 (source.getExtension().toLower() == "xml" && | |
2180 (SVFileReader::identifyXmlFile(source.getLocalFilename()) | |
2181 == SVFileReader::SVLayerFile))) { | |
2182 | |
2183 //!!! | |
2184 return FileOpenFailed; | |
2185 | |
2186 } else { | |
2187 | |
2188 try { | |
2189 | |
2190 CSVFormat format(path); | |
2191 format.setSampleRate(getMainModel()->getSampleRate()); | |
2192 | |
2193 if (format.getModelType() != CSVFormat::TwoDimensionalModel) { | |
2194 //!!! error report | |
2195 return FileOpenFailed; | |
2196 } | |
2197 | |
2198 Model *model = DataFileReaderFactory::loadCSV | |
2199 (path, format, getMainModel()->getSampleRate()); | |
2200 | |
2201 if (model) { | |
2202 | |
2203 SVDEBUG << "MainWindow::importPitchLayer: Have model" << endl; | |
2204 | |
2205 ModelId modelId = ModelById::add | |
2206 (std::shared_ptr<Model>(model)); | |
2207 | |
2208 CommandHistory::getInstance()->startCompoundOperation | |
2209 (tr("Import Pitch Track"), true); | |
2210 | |
2211 Layer *newLayer = m_document->createImportedLayer(modelId); | |
2212 | |
2213 m_analyser->takePitchTrackFrom(newLayer); | |
2214 | |
2215 m_document->deleteLayer(newLayer); | |
2216 | |
2217 CommandHistory::getInstance()->endCompoundOperation(); | |
2218 | |
2219 if (!source.isRemote()) { | |
2220 registerLastOpenedFilePath | |
2221 (FileFinder::LayerFile, | |
2222 path); // for file dialog | |
2223 } | |
2224 | |
2225 return FileOpenSucceeded; | |
2226 } | |
2227 } catch (DataFileReaderFactory::Exception e) { | |
2228 if (e == DataFileReaderFactory::ImportCancelled) { | |
2229 return FileOpenCancelled; | |
2230 } | |
2231 } | |
2232 } | |
2233 | |
2234 return FileOpenFailed; | |
2235 } | |
2236 | |
2237 void | |
2238 MainWindow::exportPitchLayer() | |
2239 { | |
2240 Layer *layer = m_analyser->getLayer(Analyser::PitchTrack); | |
2241 if (!layer) return; | |
2242 | |
2243 auto model = ModelById::getAs<SparseTimeValueModel>(layer->getModel()); | |
2244 if (!model) return; | |
2245 | |
2246 FileFinder::FileType type = FileFinder::LayerFileNoMidiNonSV; | |
2247 | |
2248 QString path = getSaveFileName(type); | |
2249 | |
2250 if (path == "") return; | |
2251 | |
2252 if (!waitForInitialAnalysis()) return; | |
2253 | |
2254 if (QFileInfo(path).suffix() == "") path += ".svl"; | |
2255 | |
2256 QString suffix = QFileInfo(path).suffix().toLower(); | |
2257 | |
2258 QString error; | |
2259 | |
2260 if (suffix == "xml" || suffix == "svl") { | |
2261 | |
2262 error = exportToSVL(path, layer); | |
2263 | |
2264 } else if (suffix == "ttl" || suffix == "n3") { | |
2265 | |
2266 RDFExporter exporter(path, model.get()); | |
2267 exporter.write(); | |
2268 if (!exporter.isOK()) { | |
2269 error = exporter.getError(); | |
2270 } | |
2271 | |
2272 } else { | |
2273 | |
2274 DataExportOptions options = DataExportFillGaps; | |
2275 | |
2276 CSVFileWriter writer(path, model.get(), | |
2277 ((suffix == "csv") ? "," : "\t"), | |
2278 options); | |
2279 writer.write(); | |
2280 | |
2281 if (!writer.isOK()) { | |
2282 error = writer.getError(); | |
2283 } | |
2284 } | |
2285 | |
2286 if (error != "") { | |
2287 QMessageBox::critical(this, tr("Failed to write file"), error); | |
2288 } else { | |
2289 emit activity(tr("Export layer to \"%1\"").arg(path)); | |
2290 } | |
2291 } | |
2292 | |
2293 void | |
2294 MainWindow::exportNoteLayer() | |
2295 { | |
2296 Layer *layer = m_analyser->getLayer(Analyser::Notes); | |
2297 if (!layer) return; | |
2298 | |
2299 auto model = ModelById::getAs<NoteModel>(layer->getModel()); | |
2300 if (!model) return; | |
2301 | |
2302 FileFinder::FileType type = FileFinder::LayerFileNonSV; | |
2303 | |
2304 QString path = getSaveFileName(type); | |
2305 | |
2306 if (path == "") return; | |
2307 | |
2308 if (QFileInfo(path).suffix() == "") path += ".svl"; | |
2309 | |
2310 QString suffix = QFileInfo(path).suffix().toLower(); | |
2311 | |
2312 QString error; | |
2313 | |
2314 if (suffix == "xml" || suffix == "svl") { | |
2315 | |
2316 error = exportToSVL(path, layer); | |
2317 | |
2318 } else if (suffix == "mid" || suffix == "midi") { | |
2319 | |
2320 MIDIFileWriter writer(path, model.get(), model->getSampleRate()); | |
2321 writer.write(); | |
2322 if (!writer.isOK()) { | |
2323 error = writer.getError(); | |
2324 } | |
2325 | |
2326 } else if (suffix == "ttl" || suffix == "n3") { | |
2327 | |
2328 RDFExporter exporter(path, model.get()); | |
2329 exporter.write(); | |
2330 if (!exporter.isOK()) { | |
2331 error = exporter.getError(); | |
2332 } | |
2333 | |
2334 } else { | |
2335 | |
2336 DataExportOptions options = DataExportOmitLevels; | |
2337 | |
2338 CSVFileWriter writer(path, model.get(), | |
2339 ((suffix == "csv") ? "," : "\t"), | |
2340 options); | |
2341 writer.write(); | |
2342 | |
2343 if (!writer.isOK()) { | |
2344 error = writer.getError(); | |
2345 } | |
2346 } | |
2347 | |
2348 if (error != "") { | |
2349 QMessageBox::critical(this, tr("Failed to write file"), error); | |
2350 } else { | |
2351 emit activity(tr("Export layer to \"%1\"").arg(path)); | |
2352 } | |
2353 } | |
2354 | |
2355 void | |
2356 MainWindow::browseRecordedAudio() | |
2357 { | |
2358 if (!m_recordTarget) return; | |
2359 | |
2360 QString path = RecordDirectory::getRecordContainerDirectory(); | |
2361 if (path == "") path = RecordDirectory::getRecordDirectory(); | |
2362 if (path == "") return; | |
2363 | |
2364 openLocalFolder(path); | |
2365 } | |
2366 | |
2367 void | |
2368 MainWindow::doubleClickSelectInvoked(sv_frame_t frame) | |
2369 { | |
2370 sv_frame_t f0, f1; | |
2371 m_analyser->getEnclosingSelectionScope(frame, f0, f1); | |
2372 | |
2373 cerr << "MainWindow::doubleClickSelectInvoked(" << frame << "): [" << f0 << "," << f1 << "]" << endl; | |
2374 | |
2375 Selection sel(f0, f1); | |
2376 m_viewManager->setSelection(sel); | |
2377 } | |
2378 | |
2379 void | |
2380 MainWindow::abandonSelection() | |
2381 { | |
2382 // Named abandonSelection rather than clearSelection to indicate | |
2383 // that this is an active operation -- it restores the original | |
2384 // content of the pitch track in the selected region rather than | |
2385 // simply un-selecting. | |
2386 | |
2387 cerr << "MainWindow::abandonSelection()" << endl; | |
2388 | |
2389 CommandHistory::getInstance()->startCompoundOperation(tr("Abandon Selection"), true); | |
2390 | |
2391 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2392 if (!selections.empty()) { | |
2393 Selection sel = *selections.begin(); | |
2394 m_analyser->abandonReAnalysis(sel); | |
2395 auxSnapNotes(sel); | |
2396 } | |
2397 | |
2398 MainWindowBase::clearSelection(); | |
2399 | |
2400 CommandHistory::getInstance()->endCompoundOperation(); | |
2401 } | |
2402 | |
2403 void | |
2404 MainWindow::selectionChangedByUser() | |
2405 { | |
2406 if (!m_document) { | |
2407 // we're exiting, most likely | |
2408 return; | |
2409 } | |
2410 | |
2411 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2412 | |
2413 cerr << "MainWindow::selectionChangedByUser" << endl; | |
2414 | |
2415 m_analyser->showPitchCandidates(m_pendingConstraint.isConstrained()); | |
2416 | |
2417 if (!selections.empty()) { | |
2418 Selection sel = *selections.begin(); | |
2419 cerr << "MainWindow::selectionChangedByUser: have selection" << endl; | |
2420 QString error = m_analyser->reAnalyseSelection | |
2421 (sel, m_pendingConstraint); | |
2422 if (error != "") { | |
2423 QMessageBox::critical | |
2424 (this, tr("Failed to analyse selection"), | |
2425 tr("<b>Analysis failed</b><p>%2</p>").arg(error)); | |
2426 } | |
2427 } | |
2428 | |
2429 m_pendingConstraint = Analyser::FrequencyRange(); | |
2430 } | |
2431 | |
2432 void | |
2433 MainWindow::regionOutlined(QRect r) | |
2434 { | |
2435 cerr << "MainWindow::regionOutlined(" << r.x() << "," << r.y() << "," << r.width() << "," << r.height() << ")" << endl; | |
2436 | |
2437 Pane *pane = qobject_cast<Pane *>(sender()); | |
2438 if (!pane) { | |
2439 cerr << "MainWindow::regionOutlined: not sent by pane, ignoring" << endl; | |
2440 return; | |
2441 } | |
2442 | |
2443 if (!m_analyser) { | |
2444 cerr << "MainWindow::regionOutlined: no analyser, ignoring" << endl; | |
2445 return; | |
2446 } | |
2447 | |
2448 SpectrogramLayer *spectrogram = qobject_cast<SpectrogramLayer *> | |
2449 (m_analyser->getLayer(Analyser::Spectrogram)); | |
2450 if (!spectrogram) { | |
2451 cerr << "MainWindow::regionOutlined: no spectrogram layer, ignoring" << endl; | |
2452 return; | |
2453 } | |
2454 | |
2455 sv_frame_t f0 = pane->getFrameForX(r.x()); | |
2456 sv_frame_t f1 = pane->getFrameForX(r.x() + r.width()); | |
2457 | |
2458 double v0 = spectrogram->getFrequencyForY(pane, r.y() + r.height()); | |
2459 double v1 = spectrogram->getFrequencyForY(pane, r.y()); | |
2460 | |
2461 cerr << "MainWindow::regionOutlined: frame " << f0 << " -> " << f1 | |
2462 << ", frequency " << v0 << " -> " << v1 << endl; | |
2463 | |
2464 m_pendingConstraint = Analyser::FrequencyRange(v0, v1); | |
2465 | |
2466 Selection sel(f0, f1); | |
2467 m_viewManager->setSelection(sel); | |
2468 } | |
2469 | |
2470 void | |
2471 MainWindow::clearPitches() | |
2472 { | |
2473 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2474 | |
2475 CommandHistory::getInstance()->startCompoundOperation(tr("Clear Pitches"), true); | |
2476 | |
2477 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2478 k != selections.end(); ++k) { | |
2479 m_analyser->deletePitches(*k); | |
2480 auxSnapNotes(*k); | |
2481 } | |
2482 | |
2483 CommandHistory::getInstance()->endCompoundOperation(); | |
2484 } | |
2485 | |
2486 void | |
2487 MainWindow::octaveShift(bool up) | |
2488 { | |
2489 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2490 | |
2491 CommandHistory::getInstance()->startCompoundOperation | |
2492 (up ? tr("Choose Higher Octave") : tr("Choose Lower Octave"), true); | |
2493 | |
2494 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2495 k != selections.end(); ++k) { | |
2496 | |
2497 m_analyser->shiftOctave(*k, up); | |
2498 auxSnapNotes(*k); | |
2499 } | |
2500 | |
2501 CommandHistory::getInstance()->endCompoundOperation(); | |
2502 } | |
2503 | |
2504 void | |
2505 MainWindow::togglePitchCandidates() | |
2506 { | |
2507 CommandHistory::getInstance()->startCompoundOperation(tr("Toggle Pitch Candidates"), true); | |
2508 | |
2509 m_analyser->showPitchCandidates(!m_analyser->arePitchCandidatesShown()); | |
2510 | |
2511 CommandHistory::getInstance()->endCompoundOperation(); | |
2512 | |
2513 updateMenuStates(); | |
2514 } | |
2515 | |
2516 void | |
2517 MainWindow::switchPitchUp() | |
2518 { | |
2519 if (m_analyser->arePitchCandidatesShown()) { | |
2520 if (m_analyser->haveHigherPitchCandidate()) { | |
2521 | |
2522 CommandHistory::getInstance()->startCompoundOperation | |
2523 (tr("Choose Higher Pitch Candidate"), true); | |
2524 | |
2525 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2526 | |
2527 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2528 k != selections.end(); ++k) { | |
2529 m_analyser->switchPitchCandidate(*k, true); | |
2530 auxSnapNotes(*k); | |
2531 } | |
2532 | |
2533 CommandHistory::getInstance()->endCompoundOperation(); | |
2534 } | |
2535 } else { | |
2536 octaveShift(true); | |
2537 } | |
2538 } | |
2539 | |
2540 void | |
2541 MainWindow::switchPitchDown() | |
2542 { | |
2543 if (m_analyser->arePitchCandidatesShown()) { | |
2544 if (m_analyser->haveLowerPitchCandidate()) { | |
2545 | |
2546 CommandHistory::getInstance()->startCompoundOperation | |
2547 (tr("Choose Lower Pitch Candidate"), true); | |
2548 | |
2549 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2550 | |
2551 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2552 k != selections.end(); ++k) { | |
2553 m_analyser->switchPitchCandidate(*k, false); | |
2554 auxSnapNotes(*k); | |
2555 } | |
2556 | |
2557 CommandHistory::getInstance()->endCompoundOperation(); | |
2558 } | |
2559 } else { | |
2560 octaveShift(false); | |
2561 } | |
2562 } | |
2563 | |
2564 void | |
2565 MainWindow::snapNotesToPitches() | |
2566 { | |
2567 cerr << "in snapNotesToPitches" << endl; | |
2568 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2569 | |
2570 if (!selections.empty()) { | |
2571 | |
2572 CommandHistory::getInstance()->startCompoundOperation | |
2573 (tr("Snap Notes to Pitches"), true); | |
2574 | |
2575 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2576 k != selections.end(); ++k) { | |
2577 auxSnapNotes(*k); | |
2578 } | |
2579 | |
2580 CommandHistory::getInstance()->endCompoundOperation(); | |
2581 } | |
2582 } | |
2583 | |
2584 void | |
2585 MainWindow::auxSnapNotes(Selection s) | |
2586 { | |
2587 cerr << "in auxSnapNotes" << endl; | |
2588 FlexiNoteLayer *layer = | |
2589 qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); | |
2590 if (!layer) return; | |
2591 | |
2592 layer->snapSelectedNotesToPitchTrack(m_analyser->getPane(), s); | |
2593 } | |
2594 | |
2595 void | |
2596 MainWindow::splitNote() | |
2597 { | |
2598 FlexiNoteLayer *layer = | |
2599 qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); | |
2600 if (!layer) return; | |
2601 | |
2602 layer->splitNotesAt(m_analyser->getPane(), m_viewManager->getPlaybackFrame()); | |
2603 } | |
2604 | |
2605 void | |
2606 MainWindow::mergeNotes() | |
2607 { | |
2608 FlexiNoteLayer *layer = | |
2609 qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); | |
2610 if (!layer) return; | |
2611 | |
2612 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2613 | |
2614 if (!selections.empty()) { | |
2615 | |
2616 CommandHistory::getInstance()->startCompoundOperation | |
2617 (tr("Merge Notes"), true); | |
2618 | |
2619 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2620 k != selections.end(); ++k) { | |
2621 layer->mergeNotes(m_analyser->getPane(), *k, true); | |
2622 } | |
2623 | |
2624 CommandHistory::getInstance()->endCompoundOperation(); | |
2625 } | |
2626 } | |
2627 | |
2628 void | |
2629 MainWindow::deleteNotes() | |
2630 { | |
2631 FlexiNoteLayer *layer = | |
2632 qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); | |
2633 if (!layer) return; | |
2634 | |
2635 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2636 | |
2637 if (!selections.empty()) { | |
2638 | |
2639 CommandHistory::getInstance()->startCompoundOperation | |
2640 (tr("Delete Notes"), true); | |
2641 | |
2642 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2643 k != selections.end(); ++k) { | |
2644 layer->deleteSelectionInclusive(*k); | |
2645 } | |
2646 | |
2647 CommandHistory::getInstance()->endCompoundOperation(); | |
2648 } | |
2649 } | |
2650 | |
2651 | |
2652 void | |
2653 MainWindow::formNoteFromSelection() | |
2654 { | |
2655 Pane *pane = m_analyser->getPane(); | |
2656 Layer *layer0 = m_analyser->getLayer(Analyser::Notes); | |
2657 auto model = ModelById::getAs<NoteModel>(layer0->getModel()); | |
2658 FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(layer0); | |
2659 if (!layer || !model) return; | |
2660 | |
2661 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2662 | |
2663 if (!selections.empty()) { | |
2664 | |
2665 CommandHistory::getInstance()->startCompoundOperation | |
2666 (tr("Form Note from Selection"), true); | |
2667 | |
2668 for (MultiSelection::SelectionList::iterator k = selections.begin(); | |
2669 k != selections.end(); ++k) { | |
2670 | |
2671 // Chop existing events at start and end frames; remember | |
2672 // the first starting pitch, to use as default for new | |
2673 // note; delete existing events; create new note; ask | |
2674 // layer to merge, just in order to adapt the note to the | |
2675 // existing pitch track if possible. This way we should | |
2676 // handle all the possible cases of existing notes that | |
2677 // may or may not overlap the start or end times | |
2678 | |
2679 sv_frame_t start = k->getStartFrame(); | |
2680 sv_frame_t end = k->getEndFrame(); | |
2681 | |
2682 EventVector existing = | |
2683 model->getEventsStartingWithin(start, end - start); | |
2684 | |
2685 int defaultPitch = 100; | |
2686 if (!existing.empty()) { | |
2687 defaultPitch = int(roundf(existing.begin()->getValue())); | |
2688 } | |
2689 | |
2690 layer->splitNotesAt(pane, start); | |
2691 layer->splitNotesAt(pane, end); | |
2692 layer->deleteSelection(*k); | |
2693 | |
2694 layer->addNoteOn(start, defaultPitch, 100); | |
2695 layer->addNoteOff(end, defaultPitch); | |
2696 | |
2697 layer->mergeNotes(pane, *k, false); | |
2698 } | |
2699 | |
2700 CommandHistory::getInstance()->endCompoundOperation(); | |
2701 } | |
2702 } | |
2703 | |
2704 void | |
2705 MainWindow::playSpeedChanged(int position) | |
2706 { | |
2707 PlaySpeedRangeMapper mapper; | |
2708 | |
2709 double percent = m_playSpeed->mappedValue(); | |
2710 double factor = mapper.getFactorForValue(percent); | |
2711 | |
2712 int centre = m_playSpeed->defaultValue(); | |
2713 | |
2714 // Percentage is shown to 0dp if >100, to 1dp if <100; factor is | |
2715 // shown to 3sf | |
2716 | |
2717 char pcbuf[30]; | |
2718 char facbuf[30]; | |
2719 | |
2720 if (position == centre) { | |
2721 contextHelpChanged(tr("Playback speed: Normal")); | |
2722 } else if (position < centre) { | |
2723 sprintf(pcbuf, "%.1f", percent); | |
2724 sprintf(facbuf, "%.3g", 1.0 / factor); | |
2725 contextHelpChanged(tr("Playback speed: %1% (%2x slower)") | |
2726 .arg(pcbuf) | |
2727 .arg(facbuf)); | |
2728 } else { | |
2729 sprintf(pcbuf, "%.0f", percent); | |
2730 sprintf(facbuf, "%.3g", factor); | |
2731 contextHelpChanged(tr("Playback speed: %1% (%2x faster)") | |
2732 .arg(pcbuf) | |
2733 .arg(facbuf)); | |
2734 } | |
2735 | |
2736 m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup | |
2737 | |
2738 updateMenuStates(); | |
2739 } | |
2740 | |
2741 void | |
2742 MainWindow::playSharpenToggled() | |
2743 { | |
2744 QSettings settings; | |
2745 settings.beginGroup("MainWindow"); | |
2746 settings.setValue("playsharpen", m_playSharpen->isChecked()); | |
2747 settings.endGroup(); | |
2748 | |
2749 playSpeedChanged(m_playSpeed->value()); | |
2750 // TODO: pitch gain? | |
2751 } | |
2752 | |
2753 void | |
2754 MainWindow::playMonoToggled() | |
2755 { | |
2756 QSettings settings; | |
2757 settings.beginGroup("MainWindow"); | |
2758 settings.setValue("playmono", m_playMono->isChecked()); | |
2759 settings.endGroup(); | |
2760 | |
2761 playSpeedChanged(m_playSpeed->value()); | |
2762 // TODO: pitch gain? | |
2763 } | |
2764 | |
2765 void | |
2766 MainWindow::speedUpPlayback() | |
2767 { | |
2768 int value = m_playSpeed->value(); | |
2769 value = value + m_playSpeed->pageStep(); | |
2770 if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); | |
2771 m_playSpeed->setValue(value); | |
2772 } | |
2773 | |
2774 void | |
2775 MainWindow::slowDownPlayback() | |
2776 { | |
2777 int value = m_playSpeed->value(); | |
2778 value = value - m_playSpeed->pageStep(); | |
2779 if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); | |
2780 m_playSpeed->setValue(value); | |
2781 } | |
2782 | |
2783 void | |
2784 MainWindow::restoreNormalPlayback() | |
2785 { | |
2786 m_playSpeed->setValue(m_playSpeed->defaultValue()); | |
2787 } | |
2788 | |
2789 void | |
2790 MainWindow::audioGainChanged(float gain) | |
2791 { | |
2792 double db = AudioLevel::multiplier_to_dB(gain); | |
2793 cerr << "gain = " << gain << " (" << db << " dB)" << endl; | |
2794 contextHelpChanged(tr("Audio Gain: %1 dB").arg(db)); | |
2795 if (gain == 0.f) { | |
2796 m_analyser->setAudible(Analyser::Audio, false); | |
2797 } else { | |
2798 m_analyser->setAudible(Analyser::Audio, true); | |
2799 m_analyser->setGain(Analyser::Audio, gain); | |
2800 } | |
2801 updateMenuStates(); | |
2802 } | |
2803 | |
2804 void | |
2805 MainWindow::pitchGainChanged(float gain) | |
2806 { | |
2807 double db = AudioLevel::multiplier_to_dB(gain); | |
2808 cerr << "gain = " << gain << " (" << db << " dB)" << endl; | |
2809 contextHelpChanged(tr("Pitch Gain: %1 dB").arg(db)); | |
2810 if (gain == 0.f) { | |
2811 m_analyser->setAudible(Analyser::PitchTrack, false); | |
2812 } else { | |
2813 m_analyser->setAudible(Analyser::PitchTrack, true); | |
2814 m_analyser->setGain(Analyser::PitchTrack, gain); | |
2815 } | |
2816 updateMenuStates(); | |
2817 } | |
2818 | |
2819 void | |
2820 MainWindow::notesGainChanged(float gain) | |
2821 { | |
2822 double db = AudioLevel::multiplier_to_dB(gain); | |
2823 cerr << "gain = " << gain << " (" << db << " dB)" << endl; | |
2824 contextHelpChanged(tr("Notes Gain: %1 dB").arg(db)); | |
2825 if (gain == 0.f) { | |
2826 m_analyser->setAudible(Analyser::Notes, false); | |
2827 } else { | |
2828 m_analyser->setAudible(Analyser::Notes, true); | |
2829 m_analyser->setGain(Analyser::Notes, gain); | |
2830 } | |
2831 updateMenuStates(); | |
2832 } | |
2833 | |
2834 void | |
2835 MainWindow::audioPanChanged(float pan) | |
2836 { | |
2837 contextHelpChanged(tr("Audio Pan: %1").arg(pan)); | |
2838 m_analyser->setPan(Analyser::Audio, pan); | |
2839 updateMenuStates(); | |
2840 } | |
2841 | |
2842 void | |
2843 MainWindow::pitchPanChanged(float pan) | |
2844 { | |
2845 contextHelpChanged(tr("Pitch Pan: %1").arg(pan)); | |
2846 m_analyser->setPan(Analyser::PitchTrack, pan); | |
2847 updateMenuStates(); | |
2848 } | |
2849 | |
2850 void | |
2851 MainWindow::notesPanChanged(float pan) | |
2852 { | |
2853 contextHelpChanged(tr("Notes Pan: %1").arg(pan)); | |
2854 m_analyser->setPan(Analyser::Notes, pan); | |
2855 updateMenuStates(); | |
2856 } | |
2857 | |
2858 void | |
2859 MainWindow::updateVisibleRangeDisplay(Pane *p) const | |
2860 { | |
2861 if (!getMainModel() || !p) { | |
2862 return; | |
2863 } | |
2864 | |
2865 bool haveSelection = false; | |
2866 sv_frame_t startFrame = 0, endFrame = 0; | |
2867 | |
2868 if (m_viewManager && m_viewManager->haveInProgressSelection()) { | |
2869 | |
2870 bool exclusive = false; | |
2871 Selection s = m_viewManager->getInProgressSelection(exclusive); | |
2872 | |
2873 if (!s.isEmpty()) { | |
2874 haveSelection = true; | |
2875 startFrame = s.getStartFrame(); | |
2876 endFrame = s.getEndFrame(); | |
2877 } | |
2878 } | |
2879 | |
2880 if (!haveSelection) { | |
2881 startFrame = p->getFirstVisibleFrame(); | |
2882 endFrame = p->getLastVisibleFrame(); | |
2883 } | |
2884 | |
2885 RealTime start = RealTime::frame2RealTime | |
2886 (startFrame, getMainModel()->getSampleRate()); | |
2887 | |
2888 RealTime end = RealTime::frame2RealTime | |
2889 (endFrame, getMainModel()->getSampleRate()); | |
2890 | |
2891 RealTime duration = end - start; | |
2892 | |
2893 QString startStr, endStr, durationStr; | |
2894 startStr = start.toText(true).c_str(); | |
2895 endStr = end.toText(true).c_str(); | |
2896 durationStr = duration.toText(true).c_str(); | |
2897 | |
2898 if (haveSelection) { | |
2899 m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") | |
2900 .arg(startStr).arg(endStr).arg(durationStr); | |
2901 } else { | |
2902 m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") | |
2903 .arg(startStr).arg(endStr).arg(durationStr); | |
2904 } | |
2905 | |
2906 getStatusLabel()->setText(m_myStatusMessage); | |
2907 } | |
2908 | |
2909 void | |
2910 MainWindow::updatePositionStatusDisplays() const | |
2911 { | |
2912 if (!statusBar()->isVisible()) return; | |
2913 | |
2914 } | |
2915 | |
2916 void | |
2917 MainWindow::monitoringLevelsChanged(float left, float right) | |
2918 { | |
2919 m_fader->setPeakLeft(left); | |
2920 m_fader->setPeakRight(right); | |
2921 } | |
2922 | |
2923 void | |
2924 MainWindow::sampleRateMismatch(sv_samplerate_t , | |
2925 sv_samplerate_t , | |
2926 bool ) | |
2927 { | |
2928 updateDescriptionLabel(); | |
2929 } | |
2930 | |
2931 void | |
2932 MainWindow::audioOverloadPluginDisabled() | |
2933 { | |
2934 QMessageBox::information | |
2935 (this, tr("Audio processing overload"), | |
2936 tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload.")); | |
2937 } | |
2938 | |
2939 void | |
2940 MainWindow::audioTimeStretchMultiChannelDisabled() | |
2941 { | |
2942 static bool shownOnce = false; | |
2943 if (shownOnce) return; | |
2944 QMessageBox::information | |
2945 (this, tr("Audio processing overload"), | |
2946 tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); | |
2947 shownOnce = true; | |
2948 } | |
2949 | |
2950 void | |
2951 MainWindow::layerRemoved(Layer *layer) | |
2952 { | |
2953 MainWindowBase::layerRemoved(layer); | |
2954 } | |
2955 | |
2956 void | |
2957 MainWindow::layerInAView(Layer *layer, bool inAView) | |
2958 { | |
2959 MainWindowBase::layerInAView(layer, inAView); | |
2960 } | |
2961 | |
2962 void | |
2963 MainWindow::modelAdded(ModelId model) | |
2964 { | |
2965 MainWindowBase::modelAdded(model); | |
2966 auto dtvm = ModelById::getAs<DenseTimeValueModel>(model); | |
2967 if (dtvm) { | |
2968 cerr << "A dense time-value model (such as an audio file) has been loaded" << endl; | |
2969 } | |
2970 } | |
2971 | |
2972 void | |
2973 MainWindow::mainModelChanged(ModelId model) | |
2974 { | |
2975 m_panLayer->setModel(model); | |
2976 | |
2977 MainWindowBase::mainModelChanged(model); | |
2978 | |
2979 if (m_playTarget || m_audioIO) { | |
2980 connect(m_fader, SIGNAL(valueChanged(float)), | |
2981 this, SLOT(mainModelGainChanged(float))); | |
2982 } | |
2983 } | |
2984 | |
2985 void | |
2986 MainWindow::mainModelGainChanged(float gain) | |
2987 { | |
2988 if (m_playTarget) { | |
2989 m_playTarget->setOutputGain(gain); | |
2990 } else if (m_audioIO) { | |
2991 m_audioIO->setOutputGain(gain); | |
2992 } | |
2993 } | |
2994 | |
2995 void | |
2996 MainWindow::analyseNow() | |
2997 { | |
2998 cerr << "analyseNow called" << endl; | |
2999 if (!m_analyser) return; | |
3000 | |
3001 CommandHistory::getInstance()->startCompoundOperation | |
3002 (tr("Analyse Audio"), true); | |
3003 | |
3004 QString error = m_analyser->analyseExistingFile(); | |
3005 | |
3006 CommandHistory::getInstance()->endCompoundOperation(); | |
3007 | |
3008 if (error != "") { | |
3009 QMessageBox::warning | |
3010 (this, | |
3011 tr("Failed to analyse audio"), | |
3012 tr("<b>Analysis failed</b><p>%1</p>").arg(error), | |
3013 QMessageBox::Ok); | |
3014 } | |
3015 } | |
3016 | |
3017 void | |
3018 MainWindow::analyseNewMainModel() | |
3019 { | |
3020 auto model = getMainModel(); | |
3021 | |
3022 cerr << "MainWindow::analyseNewMainModel: main model is " << model << endl; | |
3023 | |
3024 cerr << "(document is " << m_document << ", it says main model is " << m_document->getMainModel() << ")" << endl; | |
3025 | |
3026 if (!model) { | |
3027 cerr << "no main model!" << endl; | |
3028 return; | |
3029 } | |
3030 | |
3031 if (!m_paneStack) { | |
3032 cerr << "no pane stack!" << endl; | |
3033 return; | |
3034 } | |
3035 | |
3036 int pc = m_paneStack->getPaneCount(); | |
3037 Pane *pane = 0; | |
3038 Pane *selectionStrip = 0; | |
3039 | |
3040 if (pc < 2) { | |
3041 pane = m_paneStack->addPane(); | |
3042 selectionStrip = m_paneStack->addPane(); | |
3043 m_document->addLayerToView | |
3044 (selectionStrip, | |
3045 m_document->createMainModelLayer(LayerFactory::TimeRuler)); | |
3046 } else { | |
3047 pane = m_paneStack->getPane(0); | |
3048 selectionStrip = m_paneStack->getPane(1); | |
3049 } | |
3050 | |
3051 pane->setPlaybackFollow(PlaybackScrollPage); | |
3052 | |
3053 if (selectionStrip) { | |
3054 selectionStrip->setPlaybackFollow(PlaybackScrollPage); | |
3055 selectionStrip->setFixedHeight(26); | |
3056 m_paneStack->sizePanesEqually(); | |
3057 m_viewManager->clearToolModeOverrides(); | |
3058 m_viewManager->setToolModeFor(selectionStrip, | |
3059 ViewManager::SelectMode); | |
3060 } | |
3061 | |
3062 if (pane) { | |
3063 | |
3064 disconnect(pane, SIGNAL(regionOutlined(QRect)), | |
3065 pane, SLOT(zoomToRegion(QRect))); | |
3066 connect(pane, SIGNAL(regionOutlined(QRect)), | |
3067 this, SLOT(regionOutlined(QRect))); | |
3068 | |
3069 QString error = m_analyser->newFileLoaded | |
3070 (m_document, getMainModelId(), m_paneStack, pane); | |
3071 if (error != "") { | |
3072 QMessageBox::warning | |
3073 (this, | |
3074 tr("Failed to analyse audio"), | |
3075 tr("<b>Analysis failed</b><p>%1</p>").arg(error), | |
3076 QMessageBox::Ok); | |
3077 } | |
3078 } | |
3079 | |
3080 if (!m_withSpectrogram) { | |
3081 m_analyser->setVisible(Analyser::Spectrogram, false); | |
3082 } | |
3083 | |
3084 if (!m_withSonification) { | |
3085 m_analyser->setAudible(Analyser::PitchTrack, false); | |
3086 m_analyser->setAudible(Analyser::Notes, false); | |
3087 } | |
3088 | |
3089 updateLayerStatuses(); | |
3090 documentRestored(); | |
3091 } | |
3092 | |
3093 void | |
3094 MainWindow::modelGenerationFailed(QString transformName, QString message) | |
3095 { | |
3096 if (message != "") { | |
3097 | |
3098 QMessageBox::warning | |
3099 (this, | |
3100 tr("Failed to generate layer"), | |
3101 tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2") | |
3102 .arg(transformName).arg(message), | |
3103 QMessageBox::Ok); | |
3104 } else { | |
3105 QMessageBox::warning | |
3106 (this, | |
3107 tr("Failed to generate layer"), | |
3108 tr("<b>Layer generation failed</b><p>Failed to generate a derived layer.<p>The layer transform \"%1\" failed.<p>No error information is available.") | |
3109 .arg(transformName), | |
3110 QMessageBox::Ok); | |
3111 } | |
3112 } | |
3113 | |
3114 void | |
3115 MainWindow::modelGenerationWarning(QString /* transformName */, QString message) | |
3116 { | |
3117 QMessageBox::warning | |
3118 (this, tr("Warning"), message, QMessageBox::Ok); | |
3119 } | |
3120 | |
3121 void | |
3122 MainWindow::modelRegenerationFailed(QString layerName, | |
3123 QString transformName, | |
3124 QString message) | |
3125 { | |
3126 if (message != "") { | |
3127 | |
3128 QMessageBox::warning | |
3129 (this, | |
3130 tr("Failed to regenerate layer"), | |
3131 tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed:<p>%3") | |
3132 .arg(layerName).arg(transformName).arg(message), | |
3133 QMessageBox::Ok); | |
3134 } else { | |
3135 QMessageBox::warning | |
3136 (this, | |
3137 tr("Failed to regenerate layer"), | |
3138 tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed.<p>No error information is available.") | |
3139 .arg(layerName).arg(transformName), | |
3140 QMessageBox::Ok); | |
3141 } | |
3142 } | |
3143 | |
3144 void | |
3145 MainWindow::modelRegenerationWarning(QString layerName, | |
3146 QString /* transformName */, | |
3147 QString message) | |
3148 { | |
3149 QMessageBox::warning | |
3150 (this, tr("Warning"), tr("<b>Warning when regenerating layer</b><p>When regenerating the derived layer \"%1\" using new data model as input:<p>%2").arg(layerName).arg(message), QMessageBox::Ok); | |
3151 } | |
3152 | |
3153 void | |
3154 MainWindow::alignmentFailed(QString message) | |
3155 { | |
3156 QMessageBox::warning | |
3157 (this, | |
3158 tr("Failed to calculate alignment"), | |
3159 tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1") | |
3160 .arg(message), | |
3161 QMessageBox::Ok); | |
3162 } | |
3163 | |
3164 void | |
3165 MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) | |
3166 { | |
3167 // cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl; | |
3168 m_paneStack->setCurrentPane(pane); | |
3169 m_rightButtonMenu->popup(position); | |
3170 } | |
3171 | |
3172 void | |
3173 MainWindow::handleOSCMessage(const OSCMessage &) | |
3174 { | |
3175 cerr << "MainWindow::handleOSCMessage: Not implemented" << endl; | |
3176 } | |
3177 | |
3178 void | |
3179 MainWindow::mouseEnteredWidget() | |
3180 { | |
3181 QWidget *w = qobject_cast<QWidget *>(sender()); | |
3182 if (!w) return; | |
3183 | |
3184 if (w == m_fader) { | |
3185 contextHelpChanged(tr("Adjust the master playback level")); | |
3186 } else if (w == m_playSpeed) { | |
3187 contextHelpChanged(tr("Adjust the master playback speed")); | |
3188 } else if (w == m_playSharpen && w->isEnabled()) { | |
3189 contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); | |
3190 } else if (w == m_playMono && w->isEnabled()) { | |
3191 contextHelpChanged(tr("Toggle mono mode for playback time scaling")); | |
3192 } | |
3193 } | |
3194 | |
3195 void | |
3196 MainWindow::mouseLeftWidget() | |
3197 { | |
3198 contextHelpChanged(""); | |
3199 } | |
3200 | |
3201 void | |
3202 MainWindow::website() | |
3203 { | |
3204 //!!! todo: URL! | |
3205 openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/")); | |
3206 } | |
3207 | |
3208 void | |
3209 MainWindow::help() | |
3210 { | |
3211 //!!! todo: help URL! | |
3212 openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/wiki/Reference")); | |
3213 } | |
3214 | |
3215 void | |
3216 MainWindow::about() | |
3217 { | |
3218 bool debug = false; | |
3219 QString version = "(unknown version)"; | |
3220 | |
3221 #ifdef BUILD_DEBUG | |
3222 debug = true; | |
3223 #endif | |
3224 version = tr("Release %1").arg(TONY_VERSION); | |
3225 | |
3226 QString aboutText; | |
3227 | |
3228 aboutText += tr("<h3>About Tony</h3>"); | |
3229 aboutText += tr("<p>Tony is a program for interactive note and pitch analysis and annotation.</p>"); | |
3230 aboutText += tr("<p>%1 : %2 configuration</p>") | |
3231 .arg(version) | |
3232 .arg(debug ? tr("Debug") : tr("Release")); | |
3233 aboutText += tr("<p>Using Qt framework version %1.</p>") | |
3234 .arg(QT_VERSION_STR); | |
3235 | |
3236 aboutText += | |
3237 "<p>Copyright © 2005–2015 Chris Cannam, Queen Mary University of London, and the Tony project authors: Matthias Mauch, George Fazekas, Justin Salamon, and Rachel Bittner.</p>" | |
3238 "<p>pYIN analysis plugin written by Matthias Mauch.</p>" | |
3239 "<p>This program is free software; you can redistribute it and/or " | |
3240 "modify it under the terms of the GNU General Public License as " | |
3241 "published by the Free Software Foundation; either version 2 of the " | |
3242 "License, or (at your option) any later version.<br>See the file " | |
3243 "COPYING included with this distribution for more information.</p>"; | |
3244 | |
3245 QMessageBox::about(this, tr("About %1").arg(QApplication::applicationName()), aboutText); | |
3246 } | |
3247 | |
3248 void | |
3249 MainWindow::keyReference() | |
3250 { | |
3251 m_keyReference->show(); | |
3252 } | |
3253 | |
3254 void | |
3255 MainWindow::newerVersionAvailable(QString version) | |
3256 { | |
3257 //!!! nicer URL would be nicer | |
3258 QSettings settings; | |
3259 settings.beginGroup("NewerVersionWarning"); | |
3260 QString tag = QString("version-%1-available-show").arg(version); | |
3261 if (settings.value(tag, true).toBool()) { | |
3262 QString title(tr("Newer version available")); | |
3263 QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of Tony, but version %2 is now available.</p><p>Please see the <a href=\"http://code.soundsoftware.ac.uk/projects/tony/\">Tony website</a> for more information.</p>").arg(TONY_VERSION).arg(version)); | |
3264 QMessageBox::information(this, title, text); | |
3265 settings.setValue(tag, false); | |
3266 } | |
3267 settings.endGroup(); | |
3268 } | |
3269 | |
3270 void | |
3271 MainWindow::ffwd() | |
3272 { | |
3273 if (!getMainModel()) return; | |
3274 | |
3275 sv_frame_t frame = m_viewManager->getPlaybackFrame(); | |
3276 ++frame; | |
3277 | |
3278 sv_samplerate_t sr = getMainModel()->getSampleRate(); | |
3279 | |
3280 // The step is supposed to scale and be as wide as a step of | |
3281 // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 | |
3282 | |
3283 ZoomLevel zoom = m_viewManager->getGlobalZoom(); | |
3284 double framesPerPixel = 1.0; | |
3285 if (zoom.zone == ZoomLevel::FramesPerPixel) { | |
3286 framesPerPixel = zoom.level; | |
3287 } else { | |
3288 framesPerPixel = 1.0 / zoom.level; | |
3289 } | |
3290 double defaultFramesPerPixel = (720 * 44100) / sr; | |
3291 double scaler = framesPerPixel / defaultFramesPerPixel; | |
3292 RealTime step = m_defaultFfwdRwdStep * scaler; | |
3293 | |
3294 frame = RealTime::realTime2Frame | |
3295 (RealTime::frame2RealTime(frame, sr) + step, sr); | |
3296 | |
3297 if (frame > getMainModel()->getEndFrame()) { | |
3298 frame = getMainModel()->getEndFrame(); | |
3299 } | |
3300 | |
3301 if (frame < 0) frame = 0; | |
3302 | |
3303 if (m_viewManager->getPlaySelectionMode()) { | |
3304 frame = m_viewManager->constrainFrameToSelection(frame); | |
3305 } | |
3306 | |
3307 m_viewManager->setPlaybackFrame(frame); | |
3308 | |
3309 if (frame == getMainModel()->getEndFrame() && | |
3310 m_playSource && | |
3311 m_playSource->isPlaying() && | |
3312 !m_viewManager->getPlayLoopMode()) { | |
3313 stop(); | |
3314 } | |
3315 } | |
3316 | |
3317 void | |
3318 MainWindow::rewind() | |
3319 { | |
3320 if (!getMainModel()) return; | |
3321 | |
3322 sv_frame_t frame = m_viewManager->getPlaybackFrame(); | |
3323 if (frame > 0) --frame; | |
3324 | |
3325 sv_samplerate_t sr = getMainModel()->getSampleRate(); | |
3326 | |
3327 // The step is supposed to scale and be as wide as a step of | |
3328 // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 | |
3329 | |
3330 ZoomLevel zoom = m_viewManager->getGlobalZoom(); | |
3331 double framesPerPixel = 1.0; | |
3332 if (zoom.zone == ZoomLevel::FramesPerPixel) { | |
3333 framesPerPixel = zoom.level; | |
3334 } else { | |
3335 framesPerPixel = 1.0 / zoom.level; | |
3336 } | |
3337 double defaultFramesPerPixel = (720 * 44100) / sr; | |
3338 double scaler = framesPerPixel / defaultFramesPerPixel; | |
3339 RealTime step = m_defaultFfwdRwdStep * scaler; | |
3340 | |
3341 frame = RealTime::realTime2Frame | |
3342 (RealTime::frame2RealTime(frame, sr) - step, sr); | |
3343 | |
3344 if (frame < getMainModel()->getStartFrame()) { | |
3345 frame = getMainModel()->getStartFrame(); | |
3346 } | |
3347 | |
3348 if (frame < 0) frame = 0; | |
3349 | |
3350 if (m_viewManager->getPlaySelectionMode()) { | |
3351 frame = m_viewManager->constrainFrameToSelection(frame); | |
3352 } | |
3353 | |
3354 m_viewManager->setPlaybackFrame(frame); | |
3355 } |