Mercurial > hg > tony
view main/MainWindow.cpp @ 598:7079871e62c9
Expand release text
author | Chris Cannam |
---|---|
date | Mon, 30 Sep 2019 12:35:26 +0100 |
parents | 1dfdbd017cf5 |
children | fd86f443678a |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Tony An intonation analysis and annotation tool Centre for Digital Music, Queen Mary, University of London. This file copyright 2006-2012 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "../version.h" #include "MainWindow.h" #include "NetworkPermissionTester.h" #include "Analyser.h" #include "framework/Document.h" #include "framework/VersionTester.h" #include "view/Pane.h" #include "view/PaneStack.h" #include "data/model/WaveFileModel.h" #include "data/model/NoteModel.h" #include "layer/FlexiNoteLayer.h" #include "view/ViewManager.h" #include "base/Preferences.h" #include "base/RecordDirectory.h" #include "base/AudioLevel.h" #include "layer/WaveformLayer.h" #include "layer/TimeInstantLayer.h" #include "layer/TimeValueLayer.h" #include "layer/SpectrogramLayer.h" #include "widgets/Fader.h" #include "view/Overview.h" #include "widgets/AudioDial.h" #include "widgets/IconLoader.h" #include "widgets/KeyReference.h" #include "widgets/LevelPanToolButton.h" #include "audio/AudioCallbackPlaySource.h" #include "audio/AudioCallbackRecordTarget.h" #include "audio/PlaySpeedRangeMapper.h" #include "base/Profiler.h" #include "base/UnitDatabase.h" #include "layer/ColourDatabase.h" #include "base/Selection.h" #include "rdf/RDFImporter.h" #include "data/fileio/DataFileReaderFactory.h" #include "data/fileio/CSVFormat.h" #include "data/fileio/CSVFileWriter.h" #include "data/fileio/MIDIFileWriter.h" #include "rdf/RDFExporter.h" #include "widgets/RangeInputDialog.h" #include "widgets/ActivityLog.h" // For version information #include "vamp/vamp.h" #include "vamp-sdk/PluginBase.h" #include "plugin/api/ladspa.h" #include "plugin/api/dssi.h" #include <bqaudioio/SystemPlaybackTarget.h> #include <bqaudioio/SystemAudioIO.h> #include <QApplication> #include <QMessageBox> #include <QGridLayout> #include <QLabel> #include <QMenuBar> #include <QToolBar> #include <QToolButton> #include <QInputDialog> #include <QStatusBar> #include <QFileInfo> #include <QDir> #include <QProcess> #include <QPushButton> #include <QSettings> #include <QScrollArea> #include <QPainter> #include <QWidgetAction> #include <QTextEdit> #include <QDialogButtonBox> #include <iostream> #include <cstdio> #include <errno.h> using std::vector; MainWindow::MainWindow(SoundOptions options, bool withSonification, bool withSpectrogram) : MainWindowBase(options), m_overview(0), m_mainMenusCreated(false), m_playbackMenu(0), m_recentFilesMenu(0), m_rightButtonMenu(0), m_rightButtonPlaybackMenu(0), m_deleteSelectedAction(0), m_ffwdAction(0), m_rwdAction(0), m_intelligentActionOn(true), //GF: !!! temporary m_activityLog(new ActivityLog()), m_keyReference(new KeyReference()), m_selectionAnchor(0), m_withSonification(withSonification), m_withSpectrogram(withSpectrogram) { setWindowTitle(QApplication::applicationName()); #ifdef Q_OS_MAC #if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) setUnifiedTitleAndToolBarOnMac(true); #endif #endif UnitDatabase *udb = UnitDatabase::getInstance(); udb->registerUnit("Hz"); udb->registerUnit("dB"); udb->registerUnit("s"); ColourDatabase *cdb = ColourDatabase::getInstance(); cdb->addColour(Qt::black, tr("Black")); cdb->addColour(Qt::darkRed, tr("Red")); cdb->addColour(Qt::darkBlue, tr("Blue")); cdb->addColour(Qt::darkGreen, tr("Green")); cdb->addColour(QColor(200, 50, 255), tr("Purple")); cdb->addColour(QColor(255, 150, 50), tr("Orange")); cdb->addColour(QColor(180, 180, 180), tr("Grey")); cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true); cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true); cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true); cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true); cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true); cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true); Preferences::getInstance()->setResampleOnLoad(true); Preferences::getInstance()->setFixedSampleRate(44100); Preferences::getInstance()->setSpectrogramSmoothing (Preferences::SpectrogramInterpolated); Preferences::getInstance()->setNormaliseAudio(true); QSettings settings; settings.beginGroup("MainWindow"); settings.setValue("showstatusbar", false); settings.endGroup(); settings.beginGroup("Transformer"); settings.setValue("use-flexi-note-model", true); settings.endGroup(); settings.beginGroup("LayerDefaults"); settings.setValue("waveform", QString("<layer scale=\"%1\" channelMode=\"%2\"/>") .arg(int(WaveformLayer::LinearScale)) .arg(int(WaveformLayer::MixChannels))); settings.endGroup(); m_viewManager->setAlignMode(false); m_viewManager->setPlaySoloMode(false); m_viewManager->setToolMode(ViewManager::NavigateMode); m_viewManager->setZoomWheelsEnabled(false); m_viewManager->setIlluminateLocalFeatures(true); m_viewManager->setShowWorkTitle(false); m_viewManager->setShowCentreLine(false); m_viewManager->setShowDuration(false); m_viewManager->setOverlayMode(ViewManager::GlobalOverlays); connect(m_viewManager, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChangedByUser())); QFrame *frame = new QFrame; setCentralWidget(frame); QGridLayout *layout = new QGridLayout; QScrollArea *scroll = new QScrollArea(frame); scroll->setWidgetResizable(true); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setFrameShape(QFrame::NoFrame); // We have a pane stack: it comes with the territory. However, we // have a fixed and known number of panes in it -- it isn't // variable m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); m_paneStack->setShowPaneAccessories(false); connect(m_paneStack, SIGNAL(doubleClickSelectInvoked(sv_frame_t)), this, SLOT(doubleClickSelectInvoked(sv_frame_t))); scroll->setWidget(m_paneStack); m_overview = new Overview(frame); m_overview->setPlaybackFollow(PlaybackScrollPage); m_overview->setViewManager(m_viewManager); m_overview->setFixedHeight(60); #ifndef _WIN32 // For some reason, the contents of the overview never appear if we // make this setting on Windows. I have no inclination at the moment // to track down the reason why. m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); #endif connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), this, SLOT(contextHelpChanged(const QString &))); m_panLayer = new WaveformLayer; m_panLayer->setChannelMode(WaveformLayer::MergeChannels); m_panLayer->setAggressiveCacheing(true); m_panLayer->setGain(0.5); m_overview->addLayer(m_panLayer); if (m_viewManager->getGlobalDarkBackground()) { m_panLayer->setBaseColour (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green"))); } else { m_panLayer->setBaseColour (ColourDatabase::getInstance()->getColourIndex(tr("Blue"))); } m_fader = new Fader(frame, false); connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); m_playSpeed = new AudioDial(frame); m_playSpeed->setMeterColor(Qt::darkBlue); m_playSpeed->setMinimum(0); m_playSpeed->setMaximum(120); m_playSpeed->setValue(60); m_playSpeed->setFixedWidth(24); m_playSpeed->setFixedHeight(24); m_playSpeed->setNotchesVisible(true); m_playSpeed->setPageStep(10); m_playSpeed->setObjectName(tr("Playback Speed")); m_playSpeed->setDefaultValue(60); m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper); m_playSpeed->setShowToolTip(true); connect(m_playSpeed, SIGNAL(valueChanged(int)), this, SLOT(playSpeedChanged(int))); connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); m_audioLPW = new LevelPanToolButton(frame); m_audioLPW->setIncludeMute(false); m_audioLPW->setObjectName(tr("Audio Track Level and Pan")); connect(m_audioLPW, SIGNAL(levelChanged(float)), this, SLOT(audioGainChanged(float))); connect(m_audioLPW, SIGNAL(panChanged(float)), this, SLOT(audioPanChanged(float))); if (m_withSonification) { m_pitchLPW = new LevelPanToolButton(frame); m_pitchLPW->setIncludeMute(false); m_pitchLPW->setObjectName(tr("Pitch Track Level and Pan")); connect(m_pitchLPW, SIGNAL(levelChanged(float)), this, SLOT(pitchGainChanged(float))); connect(m_pitchLPW, SIGNAL(panChanged(float)), this, SLOT(pitchPanChanged(float))); m_notesLPW = new LevelPanToolButton(frame); m_notesLPW->setIncludeMute(false); m_notesLPW->setObjectName(tr("Note Track Level and Pan")); connect(m_notesLPW, SIGNAL(levelChanged(float)), this, SLOT(notesGainChanged(float))); connect(m_notesLPW, SIGNAL(panChanged(float)), this, SLOT(notesPanChanged(float))); } layout->setSpacing(4); layout->addWidget(m_overview, 0, 1); layout->addWidget(scroll, 1, 1); layout->setColumnStretch(1, 10); frame->setLayout(layout); m_analyser = new Analyser(); connect(m_analyser, SIGNAL(layersChanged()), this, SLOT(updateLayerStatuses())); connect(m_analyser, SIGNAL(layersChanged()), this, SLOT(updateMenuStates())); setupMenus(); setupToolbars(); setupHelpMenu(); statusBar(); finaliseMenus(); connect(m_viewManager, SIGNAL(activity(QString)), m_activityLog, SLOT(activityHappened(QString))); connect(m_playSource, SIGNAL(activity(QString)), m_activityLog, SLOT(activityHappened(QString))); connect(CommandHistory::getInstance(), SIGNAL(activity(QString)), m_activityLog, SLOT(activityHappened(QString))); connect(this, SIGNAL(activity(QString)), m_activityLog, SLOT(activityHappened(QString))); connect(this, SIGNAL(replacedDocument()), this, SLOT(documentReplaced())); connect(this, SIGNAL(sessionLoaded()), this, SLOT(analyseNewMainModel())); connect(this, SIGNAL(audioFileLoaded()), this, SLOT(analyseNewMainModel())); m_activityLog->hide(); setAudioRecordMode(RecordReplaceSession); newSession(); settings.beginGroup("MainWindow"); settings.setValue("zoom-default", 512); settings.endGroup(); zoomDefault(); NetworkPermissionTester tester; bool networkPermission = tester.havePermission(); if (networkPermission) { m_versionTester = new VersionTester ("sonicvisualiser.org", "latest-tony-version.txt", TONY_VERSION); connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)), this, SLOT(newerVersionAvailable(QString))); } else { m_versionTester = 0; } } MainWindow::~MainWindow() { delete m_analyser; delete m_keyReference; Profiles::getInstance()->dump(); } void MainWindow::setupMenus() { if (!m_mainMenusCreated) { #ifdef Q_OS_LINUX // In Ubuntu 14.04 the window's menu bar goes missing entirely // if the user is running any desktop environment other than Unity // (in which the faux single-menubar appears). The user has a // workaround, to remove the appmenu-qt5 package, but that is // awkward and the problem is so severe that it merits disabling // the system menubar integration altogether. Like this: menuBar()->setNativeMenuBar(false); #endif m_rightButtonMenu = new QMenu(); } if (!m_mainMenusCreated) { CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); m_rightButtonMenu->addSeparator(); } setupFileMenu(); setupEditMenu(); setupViewMenu(); setupAnalysisMenu(); m_mainMenusCreated = true; } void MainWindow::setupFileMenu() { if (m_mainMenusCreated) return; QMenu *menu = menuBar()->addMenu(tr("&File")); menu->setTearOffEnabled(true); QToolBar *toolbar = addToolBar(tr("File Toolbar")); m_keyReference->setCategory(tr("File and Session Management")); IconLoader il; QIcon icon; QAction *action; icon = il.load("fileopen"); action = new QAction(icon, tr("&Open..."), this); action->setShortcut(tr("Ctrl+O")); action->setStatusTip(tr("Open a session or audio file")); connect(action, SIGNAL(triggered()), this, SLOT(openFile())); m_keyReference->registerShortcut(action); menu->addAction(action); toolbar->addAction(action); action = new QAction(tr("Open Lo&cation..."), this); action->setShortcut(tr("Ctrl+Shift+O")); action->setStatusTip(tr("Open a file from a remote URL")); connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); m_keyReference->registerShortcut(action); menu->addAction(action); m_recentFilesMenu = menu->addMenu(tr("Open &Recent")); m_recentFilesMenu->setTearOffEnabled(true); setupRecentFilesMenu(); connect(&m_recentFiles, SIGNAL(recentChanged()), this, SLOT(setupRecentFilesMenu())); menu->addSeparator(); icon = il.load("filesave"); action = new QAction(icon, tr("&Save Session"), this); action->setShortcut(tr("Ctrl+S")); action->setStatusTip(tr("Save the current session into a %1 session file").arg(QApplication::applicationName())); connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); toolbar->addAction(action); icon = il.load("filesaveas"); action = new QAction(icon, tr("Save Session &As..."), this); action->setShortcut(tr("Ctrl+Shift+S")); action->setStatusTip(tr("Save the current session into a new %1 session file").arg(QApplication::applicationName())); connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); toolbar->addAction(action); action = new QAction(tr("Save Session to Audio File &Path"), this); action->setShortcut(tr("Ctrl+Alt+S")); 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())); connect(action, SIGNAL(triggered()), this, SLOT(saveSessionInAudioPath())); connect(this, SIGNAL(canSaveAs(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); menu->addSeparator(); action = new QAction(tr("I&mport Pitch Track Data..."), this); action->setStatusTip(tr("Import pitch-track data from a CSV, RDF, or layer XML file")); connect(action, SIGNAL(triggered()), this, SLOT(importPitchLayer())); connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); action = new QAction(tr("E&xport Pitch Track Data..."), this); action->setStatusTip(tr("Export pitch-track data to a CSV, RDF, or layer XML file")); connect(action, SIGNAL(triggered()), this, SLOT(exportPitchLayer())); connect(this, SIGNAL(canExportPitchTrack(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); action = new QAction(tr("&Export Note Data..."), this); action->setStatusTip(tr("Export note data to a CSV, RDF, layer XML, or MIDI file")); connect(action, SIGNAL(triggered()), this, SLOT(exportNoteLayer())); connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); menu->addSeparator(); action = new QAction(tr("Browse Recorded Audio"), this); action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser")); connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio())); menu->addAction(action); menu->addSeparator(); action = new QAction(il.load("exit"), tr("&Quit"), this); action->setShortcut(tr("Ctrl+Q")); action->setStatusTip(tr("Exit %1").arg(QApplication::applicationName())); connect(action, SIGNAL(triggered()), this, SLOT(close())); m_keyReference->registerShortcut(action); menu->addAction(action); } void MainWindow::setupEditMenu() { if (m_mainMenusCreated) return; QMenu *menu = menuBar()->addMenu(tr("&Edit")); menu->setTearOffEnabled(true); CommandHistory::getInstance()->registerMenu(menu); menu->addSeparator(); m_keyReference->setCategory (tr("Selection Strip Mouse Actions")); m_keyReference->registerShortcut (tr("Jump"), tr("Left"), tr("Click left button to move the playback position to a time")); m_keyReference->registerShortcut (tr("Select"), tr("Left"), tr("Click left button and drag to select a region of time")); m_keyReference->registerShortcut (tr("Select Note Duration"), tr("Double-Click Left"), tr("Double-click left button to select the region of time corresponding to a note")); QToolBar *toolbar = addToolBar(tr("Tools Toolbar")); CommandHistory::getInstance()->registerToolbar(toolbar); QActionGroup *group = new QActionGroup(this); IconLoader il; m_keyReference->setCategory(tr("Tool Selection")); QAction *action = toolbar->addAction(il.load("navigate"), tr("Navigate")); action->setCheckable(true); action->setChecked(true); action->setShortcut(tr("1")); action->setStatusTip(tr("Navigate")); connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); connect(this, SIGNAL(replacedDocument()), action, SLOT(trigger())); group->addAction(action); menu->addAction(action); m_keyReference->registerShortcut(action); m_keyReference->setCategory (tr("Navigate Tool Mouse Actions")); m_keyReference->registerShortcut (tr("Navigate"), tr("Left"), tr("Click left button and drag to move around")); m_keyReference->registerShortcut (tr("Re-Analyse Area"), tr("Shift+Left"), tr("Shift-click left button and drag to define a specific pitch and time range to re-analyse")); m_keyReference->registerShortcut (tr("Edit"), tr("Double-Click Left"), tr("Double-click left button on an item to edit it")); m_keyReference->setCategory(tr("Tool Selection")); action = toolbar->addAction(il.load("move"), tr("Edit")); action->setCheckable(true); action->setShortcut(tr("2")); action->setStatusTip(tr("Edit with Note Intelligence")); connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); group->addAction(action); menu->addAction(action); m_keyReference->registerShortcut(action); m_keyReference->setCategory (tr("Note Edit Tool Mouse Actions")); m_keyReference->registerShortcut (tr("Adjust Pitch"), tr("Left"), tr("Click left button on the main part of a note and drag to move it up or down")); m_keyReference->registerShortcut (tr("Split"), tr("Left"), tr("Click left button on the bottom edge of a note to split it at the click point")); m_keyReference->registerShortcut (tr("Resize"), tr("Left"), tr("Click left button on the left or right edge of a note and drag to change the time or duration of the note")); m_keyReference->registerShortcut (tr("Erase"), tr("Shift+Left"), tr("Shift-click left button on a note to remove it")); /* Remove for now... m_keyReference->setCategory(tr("Tool Selection")); action = toolbar->addAction(il.load("notes"), tr("Free Edit")); action->setCheckable(true); action->setShortcut(tr("3")); action->setStatusTip(tr("Free Edit")); connect(action, SIGNAL(triggered()), this, SLOT(toolFreeEditSelected())); group->addAction(action); m_keyReference->registerShortcut(action); */ menu->addSeparator(); m_keyReference->setCategory(tr("Selection")); action = new QAction(tr("Select &All"), this); action->setShortcut(tr("Ctrl+A")); action->setStatusTip(tr("Select the whole duration of the current session")); connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("C&lear Selection"), this); action->setShortcuts(QList<QKeySequence>() << QKeySequence(tr("Esc")) << QKeySequence(tr("Ctrl+Esc"))); action->setStatusTip(tr("Clear the selection and abandon any pending pitch choices in it")); connect(action, SIGNAL(triggered()), this, SLOT(abandonSelection())); connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); m_keyReference->registerAlternativeShortcut(action, QKeySequence(tr("Ctrl+Esc"))); menu->addAction(action); m_rightButtonMenu->addAction(action); menu->addSeparator(); m_rightButtonMenu->addSeparator(); m_keyReference->setCategory(tr("Pitch Track")); action = new QAction(tr("Choose Higher Pitch"), this); action->setShortcut(tr("Ctrl+Up")); action->setStatusTip(tr("Move pitches up an octave, or to the next higher pitch candidate")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(switchPitchUp())); connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("Choose Lower Pitch"), this); action->setShortcut(tr("Ctrl+Down")); action->setStatusTip(tr("Move pitches down an octave, or to the next lower pitch candidate")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(switchPitchDown())); connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); m_showCandidatesAction = new QAction(tr("Show Pitch Candidates"), this); m_showCandidatesAction->setShortcut(tr("Ctrl+Return")); m_showCandidatesAction->setStatusTip(tr("Toggle the display of alternative pitch candidates for the selected region")); m_keyReference->registerShortcut(m_showCandidatesAction); connect(m_showCandidatesAction, SIGNAL(triggered()), this, SLOT(togglePitchCandidates())); connect(this, SIGNAL(canClearSelection(bool)), m_showCandidatesAction, SLOT(setEnabled(bool))); menu->addAction(m_showCandidatesAction); m_rightButtonMenu->addAction(m_showCandidatesAction); action = new QAction(tr("Remove Pitches"), this); action->setShortcut(tr("Ctrl+Backspace")); action->setStatusTip(tr("Remove all pitch estimates within the selected region, making it unvoiced")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(clearPitches())); connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); menu->addSeparator(); m_rightButtonMenu->addSeparator(); m_keyReference->setCategory(tr("Note Track")); action = new QAction(tr("Split Note"), this); action->setShortcut(tr("/")); action->setStatusTip(tr("Split the note at the current playback position into two")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(splitNote())); connect(this, SIGNAL(canExportNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("Merge Notes"), this); action->setShortcut(tr("\\")); action->setStatusTip(tr("Merge all notes within the selected region into a single note")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(mergeNotes())); connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("Delete Notes"), this); action->setShortcut(tr("Backspace")); action->setStatusTip(tr("Delete all notes within the selected region")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(deleteNotes())); connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("Form Note from Selection"), this); action->setShortcut(tr("=")); action->setStatusTip(tr("Form a note spanning the selected region, splitting any existing notes at its boundaries")); m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(formNoteFromSelection())); connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); action = new QAction(tr("Snap Notes to Pitch Track"), this); 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")); // m_keyReference->registerShortcut(action); connect(action, SIGNAL(triggered()), this, SLOT(snapNotesToPitches())); connect(this, SIGNAL(canSnapNotes(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); m_rightButtonMenu->addAction(action); } void MainWindow::setupViewMenu() { if (m_mainMenusCreated) return; IconLoader il; QAction *action = 0; m_keyReference->setCategory(tr("Panning and Navigation")); QMenu *menu = menuBar()->addMenu(tr("&View")); menu->setTearOffEnabled(true); action = new QAction(tr("Peek &Left"), this); action->setShortcut(tr("Alt+Left")); action->setStatusTip(tr("Scroll the current pane to the left without changing the play position")); connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); action = new QAction(tr("Peek &Right"), this); action->setShortcut(tr("Alt+Right")); action->setStatusTip(tr("Scroll the current pane to the right without changing the play position")); connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); menu->addSeparator(); m_keyReference->setCategory(tr("Zoom")); action = new QAction(il.load("zoom-in"), tr("Zoom &In"), this); action->setShortcut(tr("Up")); action->setStatusTip(tr("Increase the zoom level")); connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); action = new QAction(il.load("zoom-out"), tr("Zoom &Out"), this); action->setShortcut(tr("Down")); action->setStatusTip(tr("Decrease the zoom level")); connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); action = new QAction(tr("Restore &Default Zoom"), this); action->setStatusTip(tr("Restore the zoom level to the default")); connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); menu->addAction(action); action = new QAction(il.load("zoom-fit"), tr("Zoom to &Fit"), this); action->setShortcut(tr("F")); action->setStatusTip(tr("Zoom to show the whole file")); connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(action); menu->addAction(action); menu->addSeparator(); action = new QAction(tr("Set Displayed Fre&quency Range..."), this); action->setStatusTip(tr("Set the minimum and maximum frequencies in the visible display")); connect(action, SIGNAL(triggered()), this, SLOT(editDisplayExtents())); menu->addAction(action); } void MainWindow::setupAnalysisMenu() { if (m_mainMenusCreated) return; IconLoader il; QAction *action = 0; QMenu *menu = menuBar()->addMenu(tr("&Analysis")); menu->setTearOffEnabled(true); m_autoAnalyse = new QAction(tr("Auto-Analyse &New Audio"), this); m_autoAnalyse->setStatusTip(tr("Automatically trigger analysis upon opening of a new audio file.")); m_autoAnalyse->setCheckable(true); connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled())); menu->addAction(m_autoAnalyse); action = new QAction(tr("&Analyse Now!"), this); action->setStatusTip(tr("Trigger analysis of pitches and notes. (This will delete all existing pitches and notes.)")); connect(action, SIGNAL(triggered()), this, SLOT(analyseNow())); menu->addAction(action); m_keyReference->registerShortcut(action); menu->addSeparator(); m_precise = new QAction(tr("&Unbiased Timing (slow)"), this); m_precise->setStatusTip(tr("Use a symmetric window in YIN to remove frequency-dependent timing bias. (This is slow!)")); m_precise->setCheckable(true); connect(m_precise, SIGNAL(triggered()), this, SLOT(precisionAnalysisToggled())); menu->addAction(m_precise); m_lowamp = new QAction(tr("&Penalise Soft Pitches"), this); m_lowamp->setStatusTip(tr("Reduce the likelihood of detecting a pitch when the signal has low amplitude.")); m_lowamp->setCheckable(true); connect(m_lowamp, SIGNAL(triggered()), this, SLOT(lowampAnalysisToggled())); menu->addAction(m_lowamp); m_onset = new QAction(tr("&High Onset Sensitivity"), this); m_onset->setStatusTip(tr("Increase likelihood of separating notes, especially consecutive notes at the same pitch.")); m_onset->setCheckable(true); connect(m_onset, SIGNAL(triggered()), this, SLOT(onsetAnalysisToggled())); menu->addAction(m_onset); m_prune = new QAction(tr("&Drop Short Notes"), this); m_prune->setStatusTip(tr("Duration-based pruning: automatic note estimator will not output notes of less than 100ms duration.")); m_prune->setCheckable(true); connect(m_prune, SIGNAL(triggered()), this, SLOT(pruneAnalysisToggled())); menu->addAction(m_prune); menu->addSeparator(); action = new QAction(tr("Reset Options to Defaults"), this); action->setStatusTip(tr("Reset all of the Analyse menu options to their default settings.")); connect(action, SIGNAL(triggered()), this, SLOT(resetAnalyseOptions())); menu->addAction(action); updateAnalyseStates(); } void MainWindow::resetAnalyseOptions() { //!!! oh no, we need to update the menu states as well... QSettings settings; settings.beginGroup("Analyser"); settings.setValue("auto-analysis", true); settings.setValue("precision-analysis", false); settings.setValue("lowamp-analysis", true); settings.setValue("onset-analysis", true); settings.setValue("prune-analysis", true); settings.endGroup(); updateAnalyseStates(); } void MainWindow::updateAnalyseStates() { QSettings settings; settings.beginGroup("Analyser"); bool autoAnalyse = settings.value("auto-analysis", true).toBool(); bool precise = settings.value("precision-analysis", false).toBool(); bool lowamp = settings.value("lowamp-analysis", true).toBool(); bool onset = settings.value("onset-analysis", true).toBool(); bool prune = settings.value("prune-analysis", true).toBool(); settings.endGroup(); m_autoAnalyse->setChecked(autoAnalyse); m_precise->setChecked(precise); m_lowamp->setChecked(lowamp); m_onset->setChecked(onset); m_prune->setChecked(prune); } void MainWindow::autoAnalysisToggled() { QAction *a = qobject_cast<QAction *>(sender()); if (!a) return; bool set = a->isChecked(); QSettings settings; settings.beginGroup("Analyser"); settings.setValue("auto-analysis", set); settings.endGroup(); } void MainWindow::precisionAnalysisToggled() { QAction *a = qobject_cast<QAction *>(sender()); if (!a) return; bool set = a->isChecked(); QSettings settings; settings.beginGroup("Analyser"); settings.setValue("precision-analysis", set); settings.endGroup(); // don't run analyseNow() automatically -- it's a destructive operation } void MainWindow::lowampAnalysisToggled() { QAction *a = qobject_cast<QAction *>(sender()); if (!a) return; bool set = a->isChecked(); QSettings settings; settings.beginGroup("Analyser"); settings.setValue("lowamp-analysis", set); settings.endGroup(); // don't run analyseNow() automatically -- it's a destructive operation } void MainWindow::onsetAnalysisToggled() { QAction *a = qobject_cast<QAction *>(sender()); if (!a) return; bool set = a->isChecked(); QSettings settings; settings.beginGroup("Analyser"); settings.setValue("onset-analysis", set); settings.endGroup(); // don't run analyseNow() automatically -- it's a destructive operation } void MainWindow::pruneAnalysisToggled() { QAction *a = qobject_cast<QAction *>(sender()); if (!a) return; bool set = a->isChecked(); QSettings settings; settings.beginGroup("Analyser"); settings.setValue("prune-analysis", set); settings.endGroup(); // don't run analyseNow() automatically -- it's a destructive operation } void MainWindow::setupHelpMenu() { QMenu *menu = menuBar()->addMenu(tr("&Help")); menu->setTearOffEnabled(true); m_keyReference->setCategory(tr("Help")); IconLoader il; QString name = QApplication::applicationName(); QAction *action; action = new QAction(il.load("help"), tr("&Help Reference"), this); action->setShortcut(tr("F1")); action->setStatusTip(tr("Open the %1 reference manual").arg(name)); connect(action, SIGNAL(triggered()), this, SLOT(help())); m_keyReference->registerShortcut(action); menu->addAction(action); action = new QAction(tr("&Key and Mouse Reference"), this); action->setShortcut(tr("F2")); action->setStatusTip(tr("Open a window showing the keystrokes you can use in %1").arg(name)); connect(action, SIGNAL(triggered()), this, SLOT(keyReference())); m_keyReference->registerShortcut(action); menu->addAction(action); action = new QAction(tr("What's &New In This Release?"), this); action->setStatusTip(tr("List the changes in this release (and every previous release) of %1").arg(name)); connect(action, SIGNAL(triggered()), this, SLOT(whatsNew())); menu->addAction(action); action = new QAction(tr("&About %1").arg(name), this); action->setStatusTip(tr("Show information about %1").arg(name)); connect(action, SIGNAL(triggered()), this, SLOT(about())); menu->addAction(action); } void MainWindow::setupRecentFilesMenu() { m_recentFilesMenu->clear(); vector<QString> files = m_recentFiles.getRecent(); for (size_t i = 0; i < files.size(); ++i) { QAction *action = new QAction(files[i], this); connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); if (i == 0) { action->setShortcut(tr("Ctrl+R")); m_keyReference->registerShortcut (tr("Re-open"), action->shortcut().toString(), tr("Re-open the current or most recently opened file")); } m_recentFilesMenu->addAction(action); } } void MainWindow::setupToolbars() { m_keyReference->setCategory(tr("Playback and Transport Controls")); IconLoader il; QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back")); menu->setTearOffEnabled(true); m_rightButtonMenu->addSeparator(); m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback")); QToolBar *toolbar = addToolBar(tr("Playback Toolbar")); QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"), tr("Rewind to Start")); rwdStartAction->setShortcut(tr("Home")); rwdStartAction->setStatusTip(tr("Rewind to the start")); connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart())); connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool))); QAction *m_rwdAction = toolbar->addAction(il.load("rewind"), tr("Rewind")); m_rwdAction->setShortcut(tr("Left")); m_rwdAction->setStatusTip(tr("Rewind to the previous one-second boundary")); connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind())); connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool))); setDefaultFfwdRwdStep(RealTime(1, 0)); QAction *playAction = toolbar->addAction(il.load("playpause"), tr("Play / Pause")); playAction->setCheckable(true); playAction->setShortcut(tr("Space")); playAction->setStatusTip(tr("Start or stop playback from the current position")); connect(playAction, SIGNAL(triggered()), this, SLOT(play())); connect(m_playSource, SIGNAL(playStatusChanged(bool)), playAction, SLOT(setChecked(bool))); connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool))); m_ffwdAction = toolbar->addAction(il.load("ffwd"), tr("Fast Forward")); m_ffwdAction->setShortcut(tr("Right")); m_ffwdAction->setStatusTip(tr("Fast-forward to the next one-second boundary")); connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd())); connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool))); QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"), tr("Fast Forward to End")); ffwdEndAction->setShortcut(tr("End")); ffwdEndAction->setStatusTip(tr("Fast-forward to the end")); connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd())); connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool))); QAction *recordAction = toolbar->addAction(il.load("record"), tr("Record")); recordAction->setCheckable(true); recordAction->setShortcut(tr("Ctrl+Space")); recordAction->setStatusTip(tr("Record a new audio file")); connect(recordAction, SIGNAL(triggered()), this, SLOT(record())); connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)), recordAction, SLOT(setChecked(bool))); connect(m_recordTarget, SIGNAL(recordCompleted()), this, SLOT(analyseNow())); connect(this, SIGNAL(canRecord(bool)), recordAction, SLOT(setEnabled(bool))); toolbar = addToolBar(tr("Play Mode Toolbar")); QAction *psAction = toolbar->addAction(il.load("playselection"), tr("Constrain Playback to Selection")); psAction->setCheckable(true); psAction->setChecked(m_viewManager->getPlaySelectionMode()); psAction->setShortcut(tr("s")); psAction->setStatusTip(tr("Constrain playback to the selected regions")); connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), psAction, SLOT(setChecked(bool))); connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool))); QAction *plAction = toolbar->addAction(il.load("playloop"), tr("Loop Playback")); plAction->setCheckable(true); plAction->setChecked(m_viewManager->getPlayLoopMode()); plAction->setShortcut(tr("l")); plAction->setStatusTip(tr("Loop playback")); connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), plAction, SLOT(setChecked(bool))); connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled())); connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool))); QAction *oneLeftAction = new QAction(tr("&One Note Left"), this); oneLeftAction->setShortcut(tr("Ctrl+Left")); oneLeftAction->setStatusTip(tr("Move cursor to the preceding note (or silence) onset.")); connect(oneLeftAction, SIGNAL(triggered()), this, SLOT(moveOneNoteLeft())); connect(this, SIGNAL(canScroll(bool)), oneLeftAction, SLOT(setEnabled(bool))); QAction *oneRightAction = new QAction(tr("O&ne Note Right"), this); oneRightAction->setShortcut(tr("Ctrl+Right")); oneRightAction->setStatusTip(tr("Move cursor to the succeeding note (or silence).")); connect(oneRightAction, SIGNAL(triggered()), this, SLOT(moveOneNoteRight())); connect(this, SIGNAL(canScroll(bool)), oneRightAction, SLOT(setEnabled(bool))); QAction *selectOneLeftAction = new QAction(tr("&Select One Note Left"), this); selectOneLeftAction->setShortcut(tr("Ctrl+Shift+Left")); selectOneLeftAction->setStatusTip(tr("Select to the preceding note (or silence) onset.")); connect(selectOneLeftAction, SIGNAL(triggered()), this, SLOT(selectOneNoteLeft())); connect(this, SIGNAL(canScroll(bool)), selectOneLeftAction, SLOT(setEnabled(bool))); QAction *selectOneRightAction = new QAction(tr("S&elect One Note Right"), this); selectOneRightAction->setShortcut(tr("Ctrl+Shift+Right")); selectOneRightAction->setStatusTip(tr("Select to the succeeding note (or silence).")); connect(selectOneRightAction, SIGNAL(triggered()), this, SLOT(selectOneNoteRight())); connect(this, SIGNAL(canScroll(bool)), selectOneRightAction, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(psAction); m_keyReference->registerShortcut(plAction); m_keyReference->registerShortcut(playAction); m_keyReference->registerShortcut(recordAction); m_keyReference->registerShortcut(m_rwdAction); m_keyReference->registerShortcut(m_ffwdAction); m_keyReference->registerShortcut(rwdStartAction); m_keyReference->registerShortcut(ffwdEndAction); m_keyReference->registerShortcut(recordAction); m_keyReference->registerShortcut(oneLeftAction); m_keyReference->registerShortcut(oneRightAction); m_keyReference->registerShortcut(selectOneLeftAction); m_keyReference->registerShortcut(selectOneRightAction); menu->addAction(playAction); menu->addAction(psAction); menu->addAction(plAction); menu->addSeparator(); menu->addAction(m_rwdAction); menu->addAction(m_ffwdAction); menu->addSeparator(); menu->addAction(rwdStartAction); menu->addAction(ffwdEndAction); menu->addSeparator(); menu->addAction(oneLeftAction); menu->addAction(oneRightAction); menu->addAction(selectOneLeftAction); menu->addAction(selectOneRightAction); menu->addSeparator(); menu->addAction(recordAction); menu->addSeparator(); m_rightButtonPlaybackMenu->addAction(playAction); m_rightButtonPlaybackMenu->addAction(psAction); m_rightButtonPlaybackMenu->addAction(plAction); m_rightButtonPlaybackMenu->addSeparator(); m_rightButtonPlaybackMenu->addAction(m_rwdAction); m_rightButtonPlaybackMenu->addAction(m_ffwdAction); m_rightButtonPlaybackMenu->addSeparator(); m_rightButtonPlaybackMenu->addAction(rwdStartAction); m_rightButtonPlaybackMenu->addAction(ffwdEndAction); m_rightButtonPlaybackMenu->addSeparator(); m_rightButtonPlaybackMenu->addAction(oneLeftAction); m_rightButtonPlaybackMenu->addAction(oneRightAction); m_rightButtonPlaybackMenu->addAction(selectOneLeftAction); m_rightButtonPlaybackMenu->addAction(selectOneRightAction); m_rightButtonPlaybackMenu->addSeparator(); m_rightButtonPlaybackMenu->addAction(recordAction); m_rightButtonPlaybackMenu->addSeparator(); QAction *fastAction = menu->addAction(tr("Speed Up")); fastAction->setShortcut(tr("Ctrl+PgUp")); fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch")); connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback())); connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool))); QAction *slowAction = menu->addAction(tr("Slow Down")); slowAction->setShortcut(tr("Ctrl+PgDown")); slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch")); connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback())); connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool))); QAction *normalAction = menu->addAction(tr("Restore Normal Speed")); normalAction->setShortcut(tr("Ctrl+Home")); normalAction->setStatusTip(tr("Restore non-time-stretched playback")); connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback())); connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool))); m_keyReference->registerShortcut(fastAction); m_keyReference->registerShortcut(slowAction); m_keyReference->registerShortcut(normalAction); m_rightButtonPlaybackMenu->addAction(fastAction); m_rightButtonPlaybackMenu->addAction(slowAction); m_rightButtonPlaybackMenu->addAction(normalAction); toolbar = new QToolBar(tr("Playback Controls")); addToolBar(Qt::BottomToolBarArea, toolbar); toolbar->addWidget(m_playSpeed); toolbar->addWidget(m_fader); toolbar = addToolBar(tr("Show and Play")); addToolBar(Qt::BottomToolBarArea, toolbar); m_showAudio = toolbar->addAction(il.load("waveform"), tr("Show Audio")); m_showAudio->setCheckable(true); connect(m_showAudio, SIGNAL(triggered()), this, SLOT(showAudioToggled())); connect(this, SIGNAL(canPlay(bool)), m_showAudio, SLOT(setEnabled(bool))); m_playAudio = toolbar->addAction(il.load("speaker"), tr("Play Audio")); m_playAudio->setCheckable(true); connect(m_playAudio, SIGNAL(triggered()), this, SLOT(playAudioToggled())); connect(this, SIGNAL(canPlayWaveform(bool)), m_playAudio, SLOT(setEnabled(bool))); int lpwSize, bigLpwSize; #ifdef Q_OS_MAC lpwSize = m_viewManager->scalePixelSize(32); // Mac toolbars are fatter bigLpwSize = int(lpwSize * 2.2); #else lpwSize = m_viewManager->scalePixelSize(26); bigLpwSize = int(lpwSize * 2.8); #endif m_audioLPW->setImageSize(lpwSize); m_audioLPW->setBigImageSize(bigLpwSize); toolbar->addWidget(m_audioLPW); // Pitch (f0) QLabel *spacer = new QLabel; // blank spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); toolbar->addWidget(spacer); m_showPitch = toolbar->addAction(il.load("values"), tr("Show Pitch Track")); m_showPitch->setCheckable(true); connect(m_showPitch, SIGNAL(triggered()), this, SLOT(showPitchToggled())); connect(this, SIGNAL(canPlay(bool)), m_showPitch, SLOT(setEnabled(bool))); if (m_withSonification) { m_playPitch = toolbar->addAction(il.load("speaker"), tr("Play Pitch Track")); m_playPitch->setCheckable(true); connect(m_playPitch, SIGNAL(triggered()), this, SLOT(playPitchToggled())); connect(this, SIGNAL(canPlayPitch(bool)), m_playPitch, SLOT(setEnabled(bool))); m_pitchLPW->setImageSize(lpwSize); m_pitchLPW->setBigImageSize(bigLpwSize); toolbar->addWidget(m_pitchLPW); } else { m_playPitch = 0; } // Notes spacer = new QLabel; spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); toolbar->addWidget(spacer); m_showNotes = toolbar->addAction(il.load("notes"), tr("Show Notes")); m_showNotes->setCheckable(true); connect(m_showNotes, SIGNAL(triggered()), this, SLOT(showNotesToggled())); connect(this, SIGNAL(canPlay(bool)), m_showNotes, SLOT(setEnabled(bool))); if (m_withSonification) { m_playNotes = toolbar->addAction(il.load("speaker"), tr("Play Notes")); m_playNotes->setCheckable(true); connect(m_playNotes, SIGNAL(triggered()), this, SLOT(playNotesToggled())); connect(this, SIGNAL(canPlayNotes(bool)), m_playNotes, SLOT(setEnabled(bool))); m_notesLPW->setImageSize(lpwSize); m_notesLPW->setBigImageSize(bigLpwSize); toolbar->addWidget(m_notesLPW); } else { m_playNotes = 0; } // Spectrogram spacer = new QLabel; spacer->setFixedWidth(m_viewManager->scalePixelSize(30)); toolbar->addWidget(spacer); if (!m_withSpectrogram) { m_showSpect = new QAction(tr("Show Spectrogram"), this); } else { m_showSpect = toolbar->addAction(il.load("spectrogram"), tr("Show Spectrogram")); } m_showSpect->setCheckable(true); connect(m_showSpect, SIGNAL(triggered()), this, SLOT(showSpectToggled())); connect(this, SIGNAL(canPlay(bool)), m_showSpect, SLOT(setEnabled(bool))); Pane::registerShortcuts(*m_keyReference); updateLayerStatuses(); } void MainWindow::moveOneNoteRight() { // cerr << "MainWindow::moveOneNoteRight" << endl; moveByOneNote(true, false); } void MainWindow::moveOneNoteLeft() { // cerr << "MainWindow::moveOneNoteLeft" << endl; moveByOneNote(false, false); } void MainWindow::selectOneNoteRight() { moveByOneNote(true, true); } void MainWindow::selectOneNoteLeft() { moveByOneNote(false, true); } void MainWindow::moveByOneNote(bool right, bool doSelect) { sv_frame_t frame = m_viewManager->getPlaybackFrame(); cerr << "MainWindow::moveByOneNote startframe: " << frame << endl; bool isAtSelectionBoundary = false; MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { Selection sel = *selections.begin(); isAtSelectionBoundary = (frame == sel.getStartFrame()) || (frame == sel.getEndFrame()); } if (!doSelect || !isAtSelectionBoundary) { m_selectionAnchor = frame; } Layer *layer = m_analyser->getLayer(Analyser::Notes); if (!layer) return; auto model = ModelById::getAs<NoteModel>(layer->getModel()); if (!model) return; //!!! This seems like a strange and inefficient way to do this - //!!! there is almost certainly a better way making use of //!!! EventSeries api EventVector points = model->getAllEvents(); if (points.empty()) return; EventVector::iterator i = points.begin(); std::set<sv_frame_t> snapFrames; snapFrames.insert(0); while (i != points.end()) { snapFrames.insert(i->getFrame()); snapFrames.insert(i->getFrame() + i->getDuration() + 1); ++i; } std::set<sv_frame_t>::iterator i2; if (snapFrames.find(frame) == snapFrames.end()) { // we're not on an existing snap point, so go to previous snapFrames.insert(frame); } i2 = snapFrames.find(frame); if (right) { i2++; if (i2 == snapFrames.end()) i2--; } else { if (i2 != snapFrames.begin()) i2--; } frame = *i2; m_viewManager->setPlaybackFrame(frame); if (doSelect) { Selection sel; if (frame > m_selectionAnchor) { sel = Selection(m_selectionAnchor, frame); } else { sel = Selection(frame, m_selectionAnchor); } m_viewManager->setSelection(sel); } cerr << "MainWindow::moveByOneNote endframe: " << frame << endl; } void MainWindow::toolNavigateSelected() { m_viewManager->setToolMode(ViewManager::NavigateMode); m_intelligentActionOn = true; } void MainWindow::toolEditSelected() { cerr << "MainWindow::toolEditSelected" << endl; m_viewManager->setToolMode(ViewManager::NoteEditMode); m_intelligentActionOn = true; m_analyser->setIntelligentActions(m_intelligentActionOn); } void MainWindow::toolFreeEditSelected() { m_viewManager->setToolMode(ViewManager::NoteEditMode); m_intelligentActionOn = false; m_analyser->setIntelligentActions(m_intelligentActionOn); } void MainWindow::updateMenuStates() { MainWindowBase::updateMenuStates(); Pane *currentPane = 0; Layer *currentLayer = 0; if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); if (currentPane) currentLayer = currentPane->getSelectedLayer(); bool haveMainModel = (getMainModel() != 0); bool havePlayTarget = (m_playTarget != 0 || m_audioIO != 0); bool haveCurrentPane = (currentPane != 0); bool haveCurrentLayer = (haveCurrentPane && (currentLayer != 0)); bool haveSelection = (m_viewManager && !m_viewManager->getSelections().empty()); bool haveCurrentTimeInstantsLayer = (haveCurrentLayer && qobject_cast<TimeInstantLayer *>(currentLayer)); bool haveCurrentTimeValueLayer = (haveCurrentLayer && qobject_cast<TimeValueLayer *>(currentLayer)); bool pitchCandidatesVisible = m_analyser->arePitchCandidatesShown(); emit canChangePlaybackSpeed(true); int v = m_playSpeed->value(); emit canSpeedUpPlayback(v < m_playSpeed->maximum()); emit canSlowDownPlayback(v > m_playSpeed->minimum()); bool haveWaveform = m_analyser->isVisible(Analyser::Audio) && m_analyser->getLayer(Analyser::Audio); bool havePitchTrack = m_analyser->isVisible(Analyser::PitchTrack) && m_analyser->getLayer(Analyser::PitchTrack); bool haveNotes = m_analyser->isVisible(Analyser::Notes) && m_analyser->getLayer(Analyser::Notes); emit canExportPitchTrack(havePitchTrack); emit canExportNotes(haveNotes); emit canSnapNotes(haveSelection && haveNotes); emit canPlayWaveform(haveWaveform && haveMainModel && havePlayTarget); emit canPlayPitch(havePitchTrack && haveMainModel && havePlayTarget); emit canPlayNotes(haveNotes && haveMainModel && havePlayTarget); if (pitchCandidatesVisible) { m_showCandidatesAction->setText(tr("Hide Pitch Candidates")); m_showCandidatesAction->setStatusTip(tr("Remove the display of alternate pitch candidates for the selected region")); } else { m_showCandidatesAction->setText(tr("Show Pitch Candidates")); m_showCandidatesAction->setStatusTip(tr("Show alternate pitch candidates for the selected region")); } if (m_ffwdAction && m_rwdAction) { if (haveCurrentTimeInstantsLayer) { m_ffwdAction->setText(tr("Fast Forward to Next Instant")); m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer")); m_rwdAction->setText(tr("Rewind to Previous Instant")); m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer")); } else if (haveCurrentTimeValueLayer) { m_ffwdAction->setText(tr("Fast Forward to Next Point")); m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer")); m_rwdAction->setText(tr("Rewind to Previous Point")); m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer")); } else { m_ffwdAction->setText(tr("Fast Forward")); m_ffwdAction->setStatusTip(tr("Fast forward")); m_rwdAction->setText(tr("Rewind")); m_rwdAction->setStatusTip(tr("Rewind")); } } } void MainWindow::showAudioToggled() { m_analyser->toggleVisible(Analyser::Audio); QSettings settings; settings.beginGroup("MainWindow"); bool playOn = false; if (m_analyser->isVisible(Analyser::Audio)) { // just switched layer on; check whether playback was also on previously playOn = settings.value("playaudiowas", true).toBool(); } else { settings.setValue("playaudiowas", m_playAudio->isChecked()); } m_analyser->setAudible(Analyser::Audio, playOn); settings.endGroup(); updateMenuStates(); updateLayerStatuses(); } void MainWindow::showPitchToggled() { m_analyser->toggleVisible(Analyser::PitchTrack); QSettings settings; settings.beginGroup("MainWindow"); bool playOn = false; if (m_analyser->isVisible(Analyser::PitchTrack)) { // just switched layer on; check whether playback was also on previously playOn = settings.value("playpitchwas", true).toBool(); } else { settings.setValue("playpitchwas", m_playPitch->isChecked()); } m_analyser->setAudible(Analyser::PitchTrack, playOn); settings.endGroup(); updateMenuStates(); updateLayerStatuses(); } void MainWindow::showSpectToggled() { m_analyser->toggleVisible(Analyser::Spectrogram); } void MainWindow::showNotesToggled() { m_analyser->toggleVisible(Analyser::Notes); QSettings settings; settings.beginGroup("MainWindow"); bool playOn = false; if (m_analyser->isVisible(Analyser::Notes)) { // just switched layer on; check whether playback was also on previously playOn = settings.value("playnoteswas", true).toBool(); } else { settings.setValue("playnoteswas", m_playNotes->isChecked()); } m_analyser->setAudible(Analyser::Notes, playOn); settings.endGroup(); updateMenuStates(); updateLayerStatuses(); } void MainWindow::playAudioToggled() { m_analyser->toggleAudible(Analyser::Audio); updateLayerStatuses(); } void MainWindow::playPitchToggled() { m_analyser->toggleAudible(Analyser::PitchTrack); updateLayerStatuses(); } void MainWindow::playNotesToggled() { m_analyser->toggleAudible(Analyser::Notes); updateLayerStatuses(); } void MainWindow::updateLayerStatuses() { m_showAudio->setChecked(m_analyser->isVisible(Analyser::Audio)); m_playAudio->setChecked(m_analyser->isAudible(Analyser::Audio)); m_audioLPW->setEnabled(m_analyser->isAudible(Analyser::Audio)); m_audioLPW->setLevel(m_analyser->getGain(Analyser::Audio)); m_audioLPW->setPan(m_analyser->getPan(Analyser::Audio)); m_showPitch->setChecked(m_analyser->isVisible(Analyser::PitchTrack)); m_playPitch->setChecked(m_analyser->isAudible(Analyser::PitchTrack)); m_pitchLPW->setEnabled(m_analyser->isAudible(Analyser::PitchTrack)); m_pitchLPW->setLevel(m_analyser->getGain(Analyser::PitchTrack)); m_pitchLPW->setPan(m_analyser->getPan(Analyser::PitchTrack)); m_showNotes->setChecked(m_analyser->isVisible(Analyser::Notes)); m_playNotes->setChecked(m_analyser->isAudible(Analyser::Notes)); m_notesLPW->setEnabled(m_analyser->isAudible(Analyser::Notes)); m_notesLPW->setLevel(m_analyser->getGain(Analyser::Notes)); m_notesLPW->setPan(m_analyser->getPan(Analyser::Notes)); m_showSpect->setChecked(m_analyser->isVisible(Analyser::Spectrogram)); } void MainWindow::editDisplayExtents() { double min, max; double vmin = 0; double vmax = getMainModel()->getSampleRate() /2; if (!m_analyser->getDisplayFrequencyExtents(min, max)) { //!!! return; } RangeInputDialog dialog(tr("Set frequency range"), tr("Enter new frequency range, from %1 to %2 Hz.\nThese values will be rounded to the nearest spectrogram bin.") .arg(vmin).arg(vmax), "Hz", float(vmin), float(vmax), this); dialog.setRange(float(min), float(max)); if (dialog.exec() == QDialog::Accepted) { float fmin, fmax; dialog.getRange(fmin, fmax); min = fmin; max = fmax; if (min > max) { double tmp = max; max = min; min = tmp; } m_analyser->setDisplayFrequencyExtents(min, max); } } void MainWindow::updateDescriptionLabel() { // Nothing, we don't have one } void MainWindow::documentModified() { MainWindowBase::documentModified(); } void MainWindow::documentRestored() { MainWindowBase::documentRestored(); } void MainWindow::newSession() { if (!checkSaveModified()) return; closeSession(); createDocument(); m_document->setAutoAlignment(true); Pane *pane = m_paneStack->addPane(true); pane->setPlaybackFollow(PlaybackScrollPage); m_viewManager->setGlobalCentreFrame (pane->getFrameForX(width() / 2)); connect(pane, SIGNAL(contextHelpChanged(const QString &)), this, SLOT(contextHelpChanged(const QString &))); // Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); // m_document->addLayerToView(pane, waveform); m_overview->registerView(pane); CommandHistory::getInstance()->clear(); CommandHistory::getInstance()->documentSaved(); documentRestored(); updateMenuStates(); } void MainWindow::documentReplaced() { if (m_document) { connect(m_document, SIGNAL(activity(QString)), m_activityLog, SLOT(activityHappened(QString))); } } void MainWindow::closeSession() { if (!checkSaveModified()) return; m_analyser->fileClosed(); while (m_paneStack->getPaneCount() > 0) { Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); while (pane->getLayerCount() > 0) { m_document->removeLayerFromView (pane, pane->getLayer(pane->getLayerCount() - 1)); } m_overview->unregisterView(pane); m_paneStack->deletePane(pane); } while (m_paneStack->getHiddenPaneCount() > 0) { Pane *pane = m_paneStack->getHiddenPane (m_paneStack->getHiddenPaneCount() - 1); while (pane->getLayerCount() > 0) { m_document->removeLayerFromView (pane, pane->getLayer(pane->getLayerCount() - 1)); } m_overview->unregisterView(pane); m_paneStack->deletePane(pane); } delete m_document; m_document = 0; m_viewManager->clearSelections(); m_timeRulerLayer = 0; // document owned this m_sessionFile = ""; CommandHistory::getInstance()->clear(); CommandHistory::getInstance()->documentSaved(); documentRestored(); } void MainWindow::openFile() { QString orig = m_audioFile; if (orig == "") orig = "."; else orig = QFileInfo(orig).absoluteDir().canonicalPath(); QString path = getOpenFileName(FileFinder::AnyFile); if (path.isEmpty()) return; FileOpenStatus status = openPath(path, ReplaceSession); if (status == FileOpenFailed) { QMessageBox::critical(this, tr("Failed to open file"), tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path)); } else if (status == FileOpenWrongMode) { QMessageBox::critical(this, tr("Failed to open file"), tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); } } void MainWindow::openLocation() { QSettings settings; settings.beginGroup("MainWindow"); QString lastLocation = settings.value("lastremote", "").toString(); bool ok = false; QString text = QInputDialog::getText (this, tr("Open Location"), tr("Please enter the URL of the location to open:"), QLineEdit::Normal, lastLocation, &ok); if (!ok) return; settings.setValue("lastremote", text); if (text.isEmpty()) return; FileOpenStatus status = openPath(text, ReplaceSession); if (status == FileOpenFailed) { QMessageBox::critical(this, tr("Failed to open location"), tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text)); } else if (status == FileOpenWrongMode) { QMessageBox::critical(this, tr("Failed to open location"), tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); } } void MainWindow::openRecentFile() { QObject *obj = sender(); QAction *action = qobject_cast<QAction *>(obj); if (!action) { cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" << endl; return; } QString path = action->text(); if (path == "") return; FileOpenStatus status = openPath(path, ReplaceSession); if (status == FileOpenFailed) { QMessageBox::critical(this, tr("Failed to open location"), tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path)); } else if (status == FileOpenWrongMode) { QMessageBox::critical(this, tr("Failed to open location"), tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); } } void MainWindow::paneAdded(Pane *pane) { pane->setPlaybackFollow(PlaybackScrollPage); m_paneStack->sizePanesEqually(); if (m_overview) m_overview->registerView(pane); } void MainWindow::paneHidden(Pane *pane) { if (m_overview) m_overview->unregisterView(pane); } void MainWindow::paneAboutToBeDeleted(Pane *pane) { if (m_overview) m_overview->unregisterView(pane); } void MainWindow::paneDropAccepted(Pane *pane, QStringList uriList) { if (pane) m_paneStack->setCurrentPane(pane); for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) { FileOpenStatus status = openPath(*i, ReplaceSession); if (status == FileOpenFailed) { QMessageBox::critical(this, tr("Failed to open dropped URL"), tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i)); } else if (status == FileOpenWrongMode) { QMessageBox::critical(this, tr("Failed to open dropped URL"), tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); } } } void MainWindow::paneDropAccepted(Pane *pane, QString text) { if (pane) m_paneStack->setCurrentPane(pane); QUrl testUrl(text); if (testUrl.scheme() == "file" || testUrl.scheme() == "http" || testUrl.scheme() == "ftp") { QStringList list; list.push_back(text); paneDropAccepted(pane, list); return; } //!!! open as text -- but by importing as if a CSV, or just adding //to a text layer? } void MainWindow::closeEvent(QCloseEvent *e) { // cerr << "MainWindow::closeEvent" << endl; if (m_openingAudioFile) { // cerr << "Busy - ignoring close event" << endl; e->ignore(); return; } if (!m_abandoning && !checkSaveModified()) { // cerr << "Ignoring close event" << endl; e->ignore(); return; } QSettings settings; settings.beginGroup("MainWindow"); settings.setValue("size", size()); settings.setValue("position", pos()); settings.endGroup(); delete m_keyReference; m_keyReference = 0; closeSession(); e->accept(); return; } bool MainWindow::commitData(bool mayAskUser) { if (mayAskUser) { bool rv = checkSaveModified(); return rv; } else { if (!m_documentModified) return true; // If we can't check with the user first, then we can't save // to the original session file (even if we have it) -- have // to use a temporary file QString svDirBase = ".sv1"; QString svDir = QDir::home().filePath(svDirBase); if (!QFileInfo(svDir).exists()) { if (!QDir::home().mkdir(svDirBase)) return false; } else { if (!QFileInfo(svDir).isDir()) return false; } // This name doesn't have to be unguessable #ifndef _WIN32 QString fname = QString("tmp-%1-%2.sv") .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) .arg(QProcess().pid()); #else QString fname = QString("tmp-%1.sv") .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); #endif QString fpath = QDir(svDir).filePath(fname); if (saveSessionFile(fpath)) { m_recentFiles.addFile(fpath); return true; } else { return false; } } } bool MainWindow::checkSaveModified() { // Called before some destructive operation (e.g. new session, // exit program). Return true if we can safely proceed, false to // cancel. if (!m_documentModified) return true; int button = QMessageBox::warning(this, tr("Session modified"), tr("The current session has been modified.\nDo you want to save it?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); if (button == QMessageBox::Yes) { saveSession(); if (m_documentModified) { // save failed -- don't proceed! return false; } else { return true; // saved, so it's safe to continue now } } else if (button == QMessageBox::No) { m_documentModified = false; // so we know to abandon it return true; } // else cancel return false; } bool MainWindow::waitForInitialAnalysis() { // Called before saving a session. We can't safely save while the // initial analysis is happening, because then we end up with an // incomplete session on reload. There are certainly theoretically // better ways to handle this... QSettings settings; settings.beginGroup("Analyser"); bool autoAnalyse = settings.value("auto-analysis", true).toBool(); settings.endGroup(); if (!autoAnalyse) { return true; } if (!m_analyser || m_analyser->getInitialAnalysisCompletion() >= 100) { return true; } QMessageBox mb(QMessageBox::Information, tr("Waiting for analysis"), tr("Waiting for initial analysis to finish before loading or saving..."), QMessageBox::Cancel, this); connect(m_analyser, SIGNAL(initialAnalysisCompleted()), &mb, SLOT(accept())); if (mb.exec() == QDialog::Accepted) { return true; } else { return false; } } void MainWindow::saveSession() { // We do not want to save mid-analysis regions -- that would cause // confusion on reloading m_analyser->clearReAnalysis(); clearSelection(); if (m_sessionFile != "") { if (!saveSessionFile(m_sessionFile)) { QMessageBox::critical (this, tr("Failed to save file"), tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); } else { CommandHistory::getInstance()->documentSaved(); documentRestored(); } } else { saveSessionAs(); } } void MainWindow::saveSessionInAudioPath() { if (m_audioFile == "") return; if (!waitForInitialAnalysis()) return; // We do not want to save mid-analysis regions -- that would cause // confusion on reloading m_analyser->clearReAnalysis(); clearSelection(); QString filepath = QFileInfo(m_audioFile).absoluteDir().canonicalPath(); QString basename = QFileInfo(m_audioFile).completeBaseName(); QString path = QDir(filepath).filePath(basename + ".ton"); cerr << path << endl; // We don't want to overwrite an existing .ton file unless we put // it there in the first place bool shouldVerify = true; if (m_sessionFile == path) { shouldVerify = false; } if (shouldVerify && QFileInfo(path).exists()) { if (QMessageBox::question(0, tr("File exists"), tr("<b>File exists</b><p>The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) { return; } } if (!waitForInitialAnalysis()) { QMessageBox::warning(this, tr("File not saved"), tr("Wait cancelled: the session has not been saved.")); } if (!saveSessionFile(path)) { QMessageBox::critical(this, tr("Failed to save file"), tr("Session file \"%1\" could not be saved.").arg(path)); } else { setWindowTitle(tr("%1: %2") .arg(QApplication::applicationName()) .arg(QFileInfo(path).fileName())); m_sessionFile = path; CommandHistory::getInstance()->documentSaved(); documentRestored(); m_recentFiles.addFile(path); } } void MainWindow::saveSessionAs() { // We do not want to save mid-analysis regions -- that would cause // confusion on reloading m_analyser->clearReAnalysis(); clearSelection(); QString path = getSaveFileName(FileFinder::SessionFile); if (path == "") { return; } if (!waitForInitialAnalysis()) { QMessageBox::warning(this, tr("File not saved"), tr("Wait cancelled: the session has not been saved.")); return; } if (!saveSessionFile(path)) { QMessageBox::critical(this, tr("Failed to save file"), tr("Session file \"%1\" could not be saved.").arg(path)); } else { setWindowTitle(tr("%1: %2") .arg(QApplication::applicationName()) .arg(QFileInfo(path).fileName())); m_sessionFile = path; CommandHistory::getInstance()->documentSaved(); documentRestored(); m_recentFiles.addFile(path); } } QString MainWindow::exportToSVL(QString path, Layer *layer) { auto model = ModelById::get(layer->getModel()); if (!model) return "Internal error: No model in layer"; QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return tr("Failed to open file %1 for writing").arg(path); } else { QTextStream out(&file); out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" << "<!DOCTYPE sonic-visualiser>\n" << "<sv>\n" << " <data>\n"; model->toXml(out, " "); out << " </data>\n" << " <display>\n"; layer->toXml(out, " "); out << " </display>\n" << "</sv>\n"; return ""; } } void MainWindow::importPitchLayer() { QString path = getOpenFileName(FileFinder::LayerFileNoMidiNonSV); if (path == "") return; FileOpenStatus status = importPitchLayer(path); if (status == FileOpenFailed) { emit hideSplash(); QMessageBox::critical(this, tr("Failed to open file"), tr("<b>File open failed</b><p>Layer file %1 could not be opened.").arg(path)); return; } else if (status == FileOpenWrongMode) { emit hideSplash(); QMessageBox::critical(this, tr("Failed to open file"), 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)); } } MainWindow::FileOpenStatus MainWindow::importPitchLayer(FileSource source) { if (!source.isAvailable()) return FileOpenFailed; source.waitForData(); if (!waitForInitialAnalysis()) return FileOpenCancelled; QString path = source.getLocalFilename(); RDFImporter::RDFDocumentType rdfType = RDFImporter::identifyDocumentType(QUrl::fromLocalFile(path).toString()); if (rdfType != RDFImporter::NotRDF) { //!!! return FileOpenFailed; } else if (source.getExtension().toLower() == "svl" || (source.getExtension().toLower() == "xml" && (SVFileReader::identifyXmlFile(source.getLocalFilename()) == SVFileReader::SVLayerFile))) { //!!! return FileOpenFailed; } else { try { CSVFormat format(path); format.setSampleRate(getMainModel()->getSampleRate()); if (format.getModelType() != CSVFormat::TwoDimensionalModel) { //!!! error report return FileOpenFailed; } Model *model = DataFileReaderFactory::loadCSV (path, format, getMainModel()->getSampleRate()); if (model) { SVDEBUG << "MainWindow::importPitchLayer: Have model" << endl; ModelId modelId = ModelById::add (std::shared_ptr<Model>(model)); CommandHistory::getInstance()->startCompoundOperation (tr("Import Pitch Track"), true); Layer *newLayer = m_document->createImportedLayer(modelId); m_analyser->takePitchTrackFrom(newLayer); m_document->deleteLayer(newLayer); CommandHistory::getInstance()->endCompoundOperation(); if (!source.isRemote()) { registerLastOpenedFilePath (FileFinder::LayerFile, path); // for file dialog } return FileOpenSucceeded; } } catch (DataFileReaderFactory::Exception e) { if (e == DataFileReaderFactory::ImportCancelled) { return FileOpenCancelled; } } } return FileOpenFailed; } void MainWindow::exportPitchLayer() { Layer *layer = m_analyser->getLayer(Analyser::PitchTrack); if (!layer) return; auto model = ModelById::getAs<SparseTimeValueModel>(layer->getModel()); if (!model) return; FileFinder::FileType type = FileFinder::LayerFileNoMidiNonSV; QString path = getSaveFileName(type); if (path == "") return; if (!waitForInitialAnalysis()) return; if (QFileInfo(path).suffix() == "") path += ".svl"; QString suffix = QFileInfo(path).suffix().toLower(); QString error; if (suffix == "xml" || suffix == "svl") { error = exportToSVL(path, layer); } else if (suffix == "ttl" || suffix == "n3") { RDFExporter exporter(path, model.get()); exporter.write(); if (!exporter.isOK()) { error = exporter.getError(); } } else { DataExportOptions options = DataExportFillGaps; CSVFileWriter writer(path, model.get(), ((suffix == "csv") ? "," : "\t"), options); writer.write(); if (!writer.isOK()) { error = writer.getError(); } } if (error != "") { QMessageBox::critical(this, tr("Failed to write file"), error); } else { emit activity(tr("Export layer to \"%1\"").arg(path)); } } void MainWindow::exportNoteLayer() { Layer *layer = m_analyser->getLayer(Analyser::Notes); if (!layer) return; auto model = ModelById::getAs<NoteModel>(layer->getModel()); if (!model) return; FileFinder::FileType type = FileFinder::LayerFileNonSV; QString path = getSaveFileName(type); if (path == "") return; if (QFileInfo(path).suffix() == "") path += ".svl"; QString suffix = QFileInfo(path).suffix().toLower(); QString error; if (suffix == "xml" || suffix == "svl") { error = exportToSVL(path, layer); } else if (suffix == "mid" || suffix == "midi") { MIDIFileWriter writer(path, model.get(), model->getSampleRate()); writer.write(); if (!writer.isOK()) { error = writer.getError(); } } else if (suffix == "ttl" || suffix == "n3") { RDFExporter exporter(path, model.get()); exporter.write(); if (!exporter.isOK()) { error = exporter.getError(); } } else { DataExportOptions options = DataExportOmitLevels; CSVFileWriter writer(path, model.get(), ((suffix == "csv") ? "," : "\t"), options); writer.write(); if (!writer.isOK()) { error = writer.getError(); } } if (error != "") { QMessageBox::critical(this, tr("Failed to write file"), error); } else { emit activity(tr("Export layer to \"%1\"").arg(path)); } } void MainWindow::browseRecordedAudio() { if (!m_recordTarget) return; QString path = RecordDirectory::getRecordContainerDirectory(); if (path == "") path = RecordDirectory::getRecordDirectory(); if (path == "") return; openLocalFolder(path); } void MainWindow::doubleClickSelectInvoked(sv_frame_t frame) { sv_frame_t f0, f1; m_analyser->getEnclosingSelectionScope(frame, f0, f1); cerr << "MainWindow::doubleClickSelectInvoked(" << frame << "): [" << f0 << "," << f1 << "]" << endl; Selection sel(f0, f1); m_viewManager->setSelection(sel); } void MainWindow::abandonSelection() { // Named abandonSelection rather than clearSelection to indicate // that this is an active operation -- it restores the original // content of the pitch track in the selected region rather than // simply un-selecting. cerr << "MainWindow::abandonSelection()" << endl; CommandHistory::getInstance()->startCompoundOperation(tr("Abandon Selection"), true); MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { Selection sel = *selections.begin(); m_analyser->abandonReAnalysis(sel); auxSnapNotes(sel); } MainWindowBase::clearSelection(); CommandHistory::getInstance()->endCompoundOperation(); } void MainWindow::selectionChangedByUser() { if (!m_document) { // we're exiting, most likely return; } MultiSelection::SelectionList selections = m_viewManager->getSelections(); cerr << "MainWindow::selectionChangedByUser" << endl; m_analyser->showPitchCandidates(m_pendingConstraint.isConstrained()); if (!selections.empty()) { Selection sel = *selections.begin(); cerr << "MainWindow::selectionChangedByUser: have selection" << endl; QString error = m_analyser->reAnalyseSelection (sel, m_pendingConstraint); if (error != "") { QMessageBox::critical (this, tr("Failed to analyse selection"), tr("<b>Analysis failed</b><p>%2</p>").arg(error)); } } m_pendingConstraint = Analyser::FrequencyRange(); } void MainWindow::regionOutlined(QRect r) { cerr << "MainWindow::regionOutlined(" << r.x() << "," << r.y() << "," << r.width() << "," << r.height() << ")" << endl; Pane *pane = qobject_cast<Pane *>(sender()); if (!pane) { cerr << "MainWindow::regionOutlined: not sent by pane, ignoring" << endl; return; } if (!m_analyser) { cerr << "MainWindow::regionOutlined: no analyser, ignoring" << endl; return; } SpectrogramLayer *spectrogram = qobject_cast<SpectrogramLayer *> (m_analyser->getLayer(Analyser::Spectrogram)); if (!spectrogram) { cerr << "MainWindow::regionOutlined: no spectrogram layer, ignoring" << endl; return; } sv_frame_t f0 = pane->getFrameForX(r.x()); sv_frame_t f1 = pane->getFrameForX(r.x() + r.width()); double v0 = spectrogram->getFrequencyForY(pane, r.y() + r.height()); double v1 = spectrogram->getFrequencyForY(pane, r.y()); cerr << "MainWindow::regionOutlined: frame " << f0 << " -> " << f1 << ", frequency " << v0 << " -> " << v1 << endl; m_pendingConstraint = Analyser::FrequencyRange(v0, v1); Selection sel(f0, f1); m_viewManager->setSelection(sel); } void MainWindow::clearPitches() { MultiSelection::SelectionList selections = m_viewManager->getSelections(); CommandHistory::getInstance()->startCompoundOperation(tr("Clear Pitches"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { m_analyser->deletePitches(*k); auxSnapNotes(*k); } CommandHistory::getInstance()->endCompoundOperation(); } void MainWindow::octaveShift(bool up) { MultiSelection::SelectionList selections = m_viewManager->getSelections(); CommandHistory::getInstance()->startCompoundOperation (up ? tr("Choose Higher Octave") : tr("Choose Lower Octave"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { m_analyser->shiftOctave(*k, up); auxSnapNotes(*k); } CommandHistory::getInstance()->endCompoundOperation(); } void MainWindow::togglePitchCandidates() { CommandHistory::getInstance()->startCompoundOperation(tr("Toggle Pitch Candidates"), true); m_analyser->showPitchCandidates(!m_analyser->arePitchCandidatesShown()); CommandHistory::getInstance()->endCompoundOperation(); updateMenuStates(); } void MainWindow::switchPitchUp() { if (m_analyser->arePitchCandidatesShown()) { if (m_analyser->haveHigherPitchCandidate()) { CommandHistory::getInstance()->startCompoundOperation (tr("Choose Higher Pitch Candidate"), true); MultiSelection::SelectionList selections = m_viewManager->getSelections(); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { m_analyser->switchPitchCandidate(*k, true); auxSnapNotes(*k); } CommandHistory::getInstance()->endCompoundOperation(); } } else { octaveShift(true); } } void MainWindow::switchPitchDown() { if (m_analyser->arePitchCandidatesShown()) { if (m_analyser->haveLowerPitchCandidate()) { CommandHistory::getInstance()->startCompoundOperation (tr("Choose Lower Pitch Candidate"), true); MultiSelection::SelectionList selections = m_viewManager->getSelections(); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { m_analyser->switchPitchCandidate(*k, false); auxSnapNotes(*k); } CommandHistory::getInstance()->endCompoundOperation(); } } else { octaveShift(false); } } void MainWindow::snapNotesToPitches() { cerr << "in snapNotesToPitches" << endl; MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { CommandHistory::getInstance()->startCompoundOperation (tr("Snap Notes to Pitches"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { auxSnapNotes(*k); } CommandHistory::getInstance()->endCompoundOperation(); } } void MainWindow::auxSnapNotes(Selection s) { cerr << "in auxSnapNotes" << endl; FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); if (!layer) return; layer->snapSelectedNotesToPitchTrack(m_analyser->getPane(), s); } void MainWindow::splitNote() { FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); if (!layer) return; layer->splitNotesAt(m_analyser->getPane(), m_viewManager->getPlaybackFrame()); } void MainWindow::mergeNotes() { FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); if (!layer) return; MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { CommandHistory::getInstance()->startCompoundOperation (tr("Merge Notes"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { layer->mergeNotes(m_analyser->getPane(), *k, true); } CommandHistory::getInstance()->endCompoundOperation(); } } void MainWindow::deleteNotes() { FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(m_analyser->getLayer(Analyser::Notes)); if (!layer) return; MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { CommandHistory::getInstance()->startCompoundOperation (tr("Delete Notes"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { layer->deleteSelectionInclusive(*k); } CommandHistory::getInstance()->endCompoundOperation(); } } void MainWindow::formNoteFromSelection() { Pane *pane = m_analyser->getPane(); Layer *layer0 = m_analyser->getLayer(Analyser::Notes); auto model = ModelById::getAs<NoteModel>(layer0->getModel()); FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(layer0); if (!layer || !model) return; MultiSelection::SelectionList selections = m_viewManager->getSelections(); if (!selections.empty()) { CommandHistory::getInstance()->startCompoundOperation (tr("Form Note from Selection"), true); for (MultiSelection::SelectionList::iterator k = selections.begin(); k != selections.end(); ++k) { // Chop existing events at start and end frames; remember // the first starting pitch, to use as default for new // note; delete existing events; create new note; ask // layer to merge, just in order to adapt the note to the // existing pitch track if possible. This way we should // handle all the possible cases of existing notes that // may or may not overlap the start or end times sv_frame_t start = k->getStartFrame(); sv_frame_t end = k->getEndFrame(); EventVector existing = model->getEventsStartingWithin(start, end - start); int defaultPitch = 100; if (!existing.empty()) { defaultPitch = int(roundf(existing.begin()->getValue())); } layer->splitNotesAt(pane, start); layer->splitNotesAt(pane, end); layer->deleteSelection(*k); layer->addNoteOn(start, defaultPitch, 100); layer->addNoteOff(end, defaultPitch); layer->mergeNotes(pane, *k, false); } CommandHistory::getInstance()->endCompoundOperation(); } } void MainWindow::playSpeedChanged(int position) { PlaySpeedRangeMapper mapper; double percent = m_playSpeed->mappedValue(); double factor = mapper.getFactorForValue(percent); int centre = m_playSpeed->defaultValue(); // Percentage is shown to 0dp if >100, to 1dp if <100; factor is // shown to 3sf char pcbuf[30]; char facbuf[30]; if (position == centre) { contextHelpChanged(tr("Playback speed: Normal")); } else if (position < centre) { sprintf(pcbuf, "%.1f", percent); sprintf(facbuf, "%.3g", 1.0 / factor); contextHelpChanged(tr("Playback speed: %1% (%2x slower)") .arg(pcbuf) .arg(facbuf)); } else { sprintf(pcbuf, "%.0f", percent); sprintf(facbuf, "%.3g", factor); contextHelpChanged(tr("Playback speed: %1% (%2x faster)") .arg(pcbuf) .arg(facbuf)); } m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup updateMenuStates(); } void MainWindow::playSharpenToggled() { QSettings settings; settings.beginGroup("MainWindow"); settings.setValue("playsharpen", m_playSharpen->isChecked()); settings.endGroup(); playSpeedChanged(m_playSpeed->value()); // TODO: pitch gain? } void MainWindow::playMonoToggled() { QSettings settings; settings.beginGroup("MainWindow"); settings.setValue("playmono", m_playMono->isChecked()); settings.endGroup(); playSpeedChanged(m_playSpeed->value()); // TODO: pitch gain? } void MainWindow::speedUpPlayback() { int value = m_playSpeed->value(); value = value + m_playSpeed->pageStep(); if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum(); m_playSpeed->setValue(value); } void MainWindow::slowDownPlayback() { int value = m_playSpeed->value(); value = value - m_playSpeed->pageStep(); if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum(); m_playSpeed->setValue(value); } void MainWindow::restoreNormalPlayback() { m_playSpeed->setValue(m_playSpeed->defaultValue()); } void MainWindow::audioGainChanged(float gain) { double db = AudioLevel::multiplier_to_dB(gain); cerr << "gain = " << gain << " (" << db << " dB)" << endl; contextHelpChanged(tr("Audio Gain: %1 dB").arg(db)); if (gain == 0.f) { m_analyser->setAudible(Analyser::Audio, false); } else { m_analyser->setAudible(Analyser::Audio, true); m_analyser->setGain(Analyser::Audio, gain); } updateMenuStates(); } void MainWindow::pitchGainChanged(float gain) { double db = AudioLevel::multiplier_to_dB(gain); cerr << "gain = " << gain << " (" << db << " dB)" << endl; contextHelpChanged(tr("Pitch Gain: %1 dB").arg(db)); if (gain == 0.f) { m_analyser->setAudible(Analyser::PitchTrack, false); } else { m_analyser->setAudible(Analyser::PitchTrack, true); m_analyser->setGain(Analyser::PitchTrack, gain); } updateMenuStates(); } void MainWindow::notesGainChanged(float gain) { double db = AudioLevel::multiplier_to_dB(gain); cerr << "gain = " << gain << " (" << db << " dB)" << endl; contextHelpChanged(tr("Notes Gain: %1 dB").arg(db)); if (gain == 0.f) { m_analyser->setAudible(Analyser::Notes, false); } else { m_analyser->setAudible(Analyser::Notes, true); m_analyser->setGain(Analyser::Notes, gain); } updateMenuStates(); } void MainWindow::audioPanChanged(float pan) { contextHelpChanged(tr("Audio Pan: %1").arg(pan)); m_analyser->setPan(Analyser::Audio, pan); updateMenuStates(); } void MainWindow::pitchPanChanged(float pan) { contextHelpChanged(tr("Pitch Pan: %1").arg(pan)); m_analyser->setPan(Analyser::PitchTrack, pan); updateMenuStates(); } void MainWindow::notesPanChanged(float pan) { contextHelpChanged(tr("Notes Pan: %1").arg(pan)); m_analyser->setPan(Analyser::Notes, pan); updateMenuStates(); } void MainWindow::updateVisibleRangeDisplay(Pane *p) const { if (!getMainModel() || !p) { return; } bool haveSelection = false; sv_frame_t startFrame = 0, endFrame = 0; if (m_viewManager && m_viewManager->haveInProgressSelection()) { bool exclusive = false; Selection s = m_viewManager->getInProgressSelection(exclusive); if (!s.isEmpty()) { haveSelection = true; startFrame = s.getStartFrame(); endFrame = s.getEndFrame(); } } if (!haveSelection) { startFrame = p->getFirstVisibleFrame(); endFrame = p->getLastVisibleFrame(); } RealTime start = RealTime::frame2RealTime (startFrame, getMainModel()->getSampleRate()); RealTime end = RealTime::frame2RealTime (endFrame, getMainModel()->getSampleRate()); RealTime duration = end - start; QString startStr, endStr, durationStr; startStr = start.toText(true).c_str(); endStr = end.toText(true).c_str(); durationStr = duration.toText(true).c_str(); if (haveSelection) { m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") .arg(startStr).arg(endStr).arg(durationStr); } else { m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") .arg(startStr).arg(endStr).arg(durationStr); } getStatusLabel()->setText(m_myStatusMessage); } void MainWindow::updatePositionStatusDisplays() const { if (!statusBar()->isVisible()) return; } void MainWindow::monitoringLevelsChanged(float left, float right) { m_fader->setPeakLeft(left); m_fader->setPeakRight(right); } void MainWindow::sampleRateMismatch(sv_samplerate_t , sv_samplerate_t , bool ) { updateDescriptionLabel(); } void MainWindow::audioOverloadPluginDisabled() { QMessageBox::information (this, tr("Audio processing overload"), tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload.")); } void MainWindow::audioTimeStretchMultiChannelDisabled() { static bool shownOnce = false; if (shownOnce) return; QMessageBox::information (this, tr("Audio processing overload"), tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload.")); shownOnce = true; } void MainWindow::layerRemoved(Layer *layer) { MainWindowBase::layerRemoved(layer); } void MainWindow::layerInAView(Layer *layer, bool inAView) { MainWindowBase::layerInAView(layer, inAView); } void MainWindow::modelAdded(ModelId model) { MainWindowBase::modelAdded(model); auto dtvm = ModelById::getAs<DenseTimeValueModel>(model); if (dtvm) { cerr << "A dense time-value model (such as an audio file) has been loaded" << endl; } } void MainWindow::mainModelChanged(ModelId model) { m_panLayer->setModel(model); MainWindowBase::mainModelChanged(model); if (m_playTarget || m_audioIO) { connect(m_fader, SIGNAL(valueChanged(float)), this, SLOT(mainModelGainChanged(float))); } } void MainWindow::mainModelGainChanged(float gain) { if (m_playTarget) { m_playTarget->setOutputGain(gain); } else if (m_audioIO) { m_audioIO->setOutputGain(gain); } } void MainWindow::analyseNow() { cerr << "analyseNow called" << endl; if (!m_analyser) return; CommandHistory::getInstance()->startCompoundOperation (tr("Analyse Audio"), true); QString error = m_analyser->analyseExistingFile(); CommandHistory::getInstance()->endCompoundOperation(); if (error != "") { QMessageBox::warning (this, tr("Failed to analyse audio"), tr("<b>Analysis failed</b><p>%1</p>").arg(error), QMessageBox::Ok); } } void MainWindow::analyseNewMainModel() { auto model = getMainModel(); cerr << "MainWindow::analyseNewMainModel: main model is " << model << endl; cerr << "(document is " << m_document << ", it says main model is " << m_document->getMainModel() << ")" << endl; if (!model) { cerr << "no main model!" << endl; return; } if (!m_paneStack) { cerr << "no pane stack!" << endl; return; } int pc = m_paneStack->getPaneCount(); Pane *pane = 0; Pane *selectionStrip = 0; if (pc < 2) { pane = m_paneStack->addPane(true); selectionStrip = m_paneStack->addPane(true); m_document->addLayerToView (selectionStrip, m_document->createMainModelLayer(LayerFactory::TimeRuler)); } else { pane = m_paneStack->getPane(0); selectionStrip = m_paneStack->getPane(1); } pane->setPlaybackFollow(PlaybackScrollPage); if (selectionStrip) { selectionStrip->setPlaybackFollow(PlaybackScrollPage); selectionStrip->setFixedHeight(26); m_paneStack->sizePanesEqually(); m_viewManager->clearToolModeOverrides(); m_viewManager->setToolModeFor(selectionStrip, ViewManager::SelectMode); } if (pane) { disconnect(pane, SIGNAL(regionOutlined(QRect)), pane, SLOT(zoomToRegion(QRect))); connect(pane, SIGNAL(regionOutlined(QRect)), this, SLOT(regionOutlined(QRect))); QString error = m_analyser->newFileLoaded (m_document, getMainModelId(), m_paneStack, pane); if (error != "") { QMessageBox::warning (this, tr("Failed to analyse audio"), tr("<b>Analysis failed</b><p>%1</p>").arg(error), QMessageBox::Ok); } } if (!m_withSpectrogram) { m_analyser->setVisible(Analyser::Spectrogram, false); } if (!m_withSonification) { m_analyser->setAudible(Analyser::PitchTrack, false); m_analyser->setAudible(Analyser::Notes, false); } updateLayerStatuses(); documentRestored(); } void MainWindow::modelGenerationFailed(QString transformName, QString message) { if (message != "") { QMessageBox::warning (this, tr("Failed to generate layer"), tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2") .arg(transformName).arg(message), QMessageBox::Ok); } else { QMessageBox::warning (this, tr("Failed to generate layer"), 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.") .arg(transformName), QMessageBox::Ok); } } void MainWindow::modelGenerationWarning(QString /* transformName */, QString message) { QMessageBox::warning (this, tr("Warning"), message, QMessageBox::Ok); } void MainWindow::modelRegenerationFailed(QString layerName, QString transformName, QString message) { if (message != "") { QMessageBox::warning (this, tr("Failed to regenerate layer"), 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") .arg(layerName).arg(transformName).arg(message), QMessageBox::Ok); } else { QMessageBox::warning (this, tr("Failed to regenerate layer"), 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.") .arg(layerName).arg(transformName), QMessageBox::Ok); } } void MainWindow::modelRegenerationWarning(QString layerName, QString /* transformName */, QString message) { QMessageBox::warning (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); } void MainWindow::alignmentFailed(QString message) { QMessageBox::warning (this, tr("Failed to calculate alignment"), tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1") .arg(message), QMessageBox::Ok); } void MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) { // cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl; m_paneStack->setCurrentPane(pane); m_rightButtonMenu->popup(position); } void MainWindow::handleOSCMessage(const OSCMessage &) { cerr << "MainWindow::handleOSCMessage: Not implemented" << endl; } void MainWindow::mouseEnteredWidget() { QWidget *w = qobject_cast<QWidget *>(sender()); if (!w) return; if (w == m_fader) { contextHelpChanged(tr("Adjust the master playback level")); } else if (w == m_playSpeed) { contextHelpChanged(tr("Adjust the master playback speed")); } else if (w == m_playSharpen && w->isEnabled()) { contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); } else if (w == m_playMono && w->isEnabled()) { contextHelpChanged(tr("Toggle mono mode for playback time scaling")); } } void MainWindow::mouseLeftWidget() { contextHelpChanged(""); } void MainWindow::help() { //!!! todo: help URL! openHelpUrl(tr("http://code.soundsoftware.ac.uk/projects/tony/wiki/Reference")); } void MainWindow::whatsNew() { QFile changelog(":CHANGELOG"); changelog.open(QFile::ReadOnly); QByteArray content = changelog.readAll(); QString text = QString::fromUtf8(content); QDialog *d = new QDialog(this); d->setWindowTitle(tr("What's New")); QGridLayout *layout = new QGridLayout; d->setLayout(layout); int row = 0; QLabel *iconLabel = new QLabel; iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); layout->addWidget(iconLabel, row, 0); layout->addWidget (new QLabel(tr("<h3>What's New in %1</h3>") .arg(QApplication::applicationName())), row++, 1); layout->setColumnStretch(2, 10); QTextEdit *textEdit = new QTextEdit; layout->addWidget(textEdit, row++, 1, 1, 2); if (m_newerVersionIs != "") { layout->addWidget(new QLabel(tr("<b>Note:</b> A newer version of %1 is available.<br>(Version %2 is available; you are using version %3)").arg(QApplication::applicationName()).arg(m_newerVersionIs).arg(TONY_VERSION)), row++, 1, 1, 2); } QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(bb, row++, 0, 1, 3); connect(bb, SIGNAL(accepted()), d, SLOT(accept())); text.replace('\r', ""); text.replace(QRegExp("(.)\n +(.)"), "\\1 \\2"); text.replace(QRegExp("\n - ([^\n]+)"), "\n<li>\\1</li>"); text.replace(QRegExp(": *\n"), ":\n<ul>\n"); text.replace(QRegExp("</li>\n\\s*\n"), "</li>\n</ul>\n\n"); text.replace(QRegExp("\n(\\w[^:\n]+:)"), "\n<p><b>\\1</b></p>"); // text.replace(QRegExp("<li>([^,.\n]+)([,.] +\\w)"), "<li><b>\\1</b>\\2"); textEdit->setHtml(text); textEdit->setReadOnly(true); d->setMinimumSize(m_viewManager->scalePixelSize(520), m_viewManager->scalePixelSize(450)); d->exec(); delete d; } QString MainWindow::getReleaseText() const { bool debug = false; QString version = "(unknown version)"; #ifdef BUILD_DEBUG debug = true; #endif // BUILD_DEBUG #ifdef TONY_VERSION #ifdef SVNREV version = tr("Release %1 : Revision %2").arg(TONY_VERSION).arg(SVNREV); #else // !SVNREV version = tr("Release %1").arg(TONY_VERSION); #endif // SVNREV #else // !TONY_VERSION #ifdef SVNREV version = tr("Unreleased : Revision %1").arg(SVNREV); #endif // SVNREV #endif // TONY_VERSION return tr("%1 : %2 configuration, %3-bit build") .arg(version) .arg(debug ? tr("Debug") : tr("Release")) .arg(sizeof(void *) * 8); } void MainWindow::about() { bool debug = false; QString version = "(unknown version)"; #ifdef BUILD_DEBUG debug = true; #endif version = tr("Release %1").arg(TONY_VERSION); QString aboutText; aboutText += tr("<h3>About Tony</h3>"); aboutText += tr("<p>Tony is a program for interactive note and pitch analysis and annotation.</p>"); aboutText += QString("<p><small>%1</small></p>").arg(getReleaseText()); aboutText += tr("<p>Using Qt framework version %1.</p>") .arg(QT_VERSION_STR); aboutText += "<p>Copyright © 2005–2019 Chris Cannam, Queen Mary University of London, and the Tony project authors: Matthias Mauch, George Fazekas, Justin Salamon, and Rachel Bittner.</p>" "<p>pYIN analysis plugin written by Matthias Mauch.</p>" "<p>This program is free software; you can redistribute it and/or " "modify it under the terms of the GNU General Public License as " "published by the Free Software Foundation; either version 2 of the " "License, or (at your option) any later version.<br>See the file " "COPYING included with this distribution for more information.</p>"; // use our own dialog so we can influence the size QDialog *d = new QDialog(this); d->setWindowTitle(tr("About %1").arg(QApplication::applicationName())); QGridLayout *layout = new QGridLayout; d->setLayout(layout); int row = 0; QLabel *iconLabel = new QLabel; iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64)); layout->addWidget(iconLabel, row, 0, Qt::AlignTop); QLabel *mainText = new QLabel(); layout->addWidget(mainText, row, 1, 1, 2); layout->setRowStretch(row, 10); layout->setColumnStretch(1, 10); ++row; QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(bb, row++, 0, 1, 3); connect(bb, SIGNAL(accepted()), d, SLOT(accept())); mainText->setWordWrap(true); mainText->setOpenExternalLinks(true); mainText->setText(aboutText); d->setMinimumSize(m_viewManager->scalePixelSize(420), m_viewManager->scalePixelSize(200)); d->exec(); delete d; } void MainWindow::keyReference() { m_keyReference->show(); } void MainWindow::newerVersionAvailable(QString version) { m_newerVersionIs = version; //!!! nicer URL would be nicer QSettings settings; settings.beginGroup("NewerVersionWarning"); QString tag = QString("version-%1-available-show").arg(version); if (settings.value(tag, true).toBool()) { QString title(tr("Newer version available")); 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)); QMessageBox::information(this, title, text); settings.setValue(tag, false); } settings.endGroup(); } void MainWindow::ffwd() { if (!getMainModel()) return; sv_frame_t frame = m_viewManager->getPlaybackFrame(); ++frame; sv_samplerate_t sr = getMainModel()->getSampleRate(); // The step is supposed to scale and be as wide as a step of // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 ZoomLevel zoom = m_viewManager->getGlobalZoom(); double framesPerPixel = 1.0; if (zoom.zone == ZoomLevel::FramesPerPixel) { framesPerPixel = zoom.level; } else { framesPerPixel = 1.0 / zoom.level; } double defaultFramesPerPixel = (720 * 44100) / sr; double scaler = framesPerPixel / defaultFramesPerPixel; RealTime step = m_defaultFfwdRwdStep * scaler; frame = RealTime::realTime2Frame (RealTime::frame2RealTime(frame, sr) + step, sr); if (frame > getMainModel()->getEndFrame()) { frame = getMainModel()->getEndFrame(); } if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); if (frame == getMainModel()->getEndFrame() && m_playSource && m_playSource->isPlaying() && !m_viewManager->getPlayLoopMode()) { stop(); } } void MainWindow::rewind() { if (!getMainModel()) return; sv_frame_t frame = m_viewManager->getPlaybackFrame(); if (frame > 0) --frame; sv_samplerate_t sr = getMainModel()->getSampleRate(); // The step is supposed to scale and be as wide as a step of // m_defaultFfwdRwdStep seconds at zoom level 720 and sr = 44100 ZoomLevel zoom = m_viewManager->getGlobalZoom(); double framesPerPixel = 1.0; if (zoom.zone == ZoomLevel::FramesPerPixel) { framesPerPixel = zoom.level; } else { framesPerPixel = 1.0 / zoom.level; } double defaultFramesPerPixel = (720 * 44100) / sr; double scaler = framesPerPixel / defaultFramesPerPixel; RealTime step = m_defaultFfwdRwdStep * scaler; frame = RealTime::realTime2Frame (RealTime::frame2RealTime(frame, sr) - step, sr); if (frame < getMainModel()->getStartFrame()) { frame = getMainModel()->getStartFrame(); } if (frame < 0) frame = 0; if (m_viewManager->getPlaySelectionMode()) { frame = m_viewManager->constrainFrameToSelection(frame); } m_viewManager->setPlaybackFrame(frame); }