changeset 441:7180bdbcf7cb

Merge from branch pitch-align
author Chris Cannam
date Fri, 26 Jun 2020 13:52:26 +0100
parents d975dfc25e06 (current diff) 4469d30fc1bc (diff)
children 49b88195961c
files repoint-lock.json repoint-project.json
diffstat 7 files changed, 364 insertions(+), 218 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu May 14 16:59:51 2020 +0100
+++ b/.hgignore	Fri Jun 26 13:52:26 2020 +0100
@@ -17,9 +17,10 @@
 *.orig
 *.rej
 re:^autom4te\.cache/
-re:^qrc_vect\.cpp$
+re:^qrc_.*\.cpp$
 re:^vect$
 re:^sonic-lineup$
+re:^sonic-vector$
 re:^aclocal\.m4$
 re:^config\.log$
 re:^config\.pri$
@@ -62,3 +63,6 @@
 ..*
 deploy/linux/docker/output
 *.AppImage
+packages/
+pitch-track-align/pitch-track-align
+pitch-track-align/pitch-track-align.deps
--- a/base.pri	Thu May 14 16:59:51 2020 +0100
+++ b/base.pri	Fri Jun 26 13:52:26 2020 +0100
@@ -31,6 +31,8 @@
 win*:     DEFINES += __WINDOWS_MM__
 solaris*: DEFINES += __RTMIDI_DUMMY_ONLY__
 
+DEFINES += QT_DEPRECATED_WARNINGS_SINCE=0x050A00
+
 # Defines for Dataquay
 DEFINES += USE_SORD
 
--- a/main/MainWindow.cpp	Thu May 14 16:59:51 2020 +0100
+++ b/main/MainWindow.cpp	Fri Jun 26 13:52:26 2020 +0100
@@ -17,6 +17,7 @@
 #include "MainWindow.h"
 #include "framework/Document.h"
 #include "framework/VersionTester.h"
+#include "align/Align.h"
 
 #include "PreferencesDialog.h"
 #include "NetworkPermissionTester.h"
@@ -54,6 +55,7 @@
 #include "widgets/SubdividingMenu.h"
 #include "widgets/NotifyingPushButton.h"
 #include "widgets/KeyReference.h"
+#include "widgets/MenuTitle.h"
 #include "audio/AudioCallbackPlaySource.h"
 #include "audio/AudioCallbackRecordTarget.h"
 #include "audio/PlaySpeedRangeMapper.h"
@@ -113,6 +115,7 @@
 #include <QCloseEvent>
 #include <QDialogButtonBox>
 #include <QTextEdit>
+#include <QFileDialog>
 
 #include <iostream>
 #include <cstdio>
@@ -135,11 +138,12 @@
                    int(PaneStack::Option::ShowAlignmentViews) |
                    int(PaneStack::Option::NoCloseOnFirstPane)),
     m_mainMenusCreated(false),
-    m_playbackMenu(nullptr),
+    m_playbackToolBar(nullptr),
     m_recentSessionsMenu(nullptr),
     m_deleteSelectedAction(nullptr),
     m_ffwdAction(nullptr),
     m_rwdAction(nullptr),
+    m_previousActiveAlignmentType(Align::NoAlignment),
     m_recentSessions("RecentSessions", 20),
     m_exiting(false),
     m_preferencesDialog(nullptr),
@@ -380,8 +384,6 @@
     mainFrame->setLayout(mainLayout);
 
     setupMenus();
-    setupToolbars();
-    setupHelpMenu();
 
     statusBar()->hide();
 
@@ -416,11 +418,19 @@
         // the system menubar integration altogether. Like this:
 	menuBar()->setNativeMenuBar(false);
 #endif
+
+        setupFileMenu();
+        setupViewMenu();
+        setupPlaybackMenu();
+        setupAlignmentMenu();
+        setupHelpMenu();
+
+        Pane::registerShortcuts(*m_keyReference);
+
+    } else {
+        setupRecentSessionsMenu();
     }
 
-    setupFileMenu();
-    setupViewMenu();
-
     m_mainMenusCreated = true;
 }
 
@@ -545,6 +555,28 @@
 }
 
 void
+MainWindow::setupRecentSessionsMenu()
+{
+    m_recentSessionsMenu->clear();
+    vector<pair<QString, QString>> sessions = m_recentSessions.getRecentEntries();
+    for (size_t i = 0; i < sessions.size(); ++i) {
+        QString path = sessions[i].first;
+        QString label = sessions[i].second;
+        if (label == "") label = path;
+        QAction *action = m_recentSessionsMenu->addAction(label);
+        action->setObjectName(path);
+	connect(action, SIGNAL(triggered()), this, SLOT(openRecentSession()));
+        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 session"));
+        }
+    }
+}
+
+void
 MainWindow::setupViewMenu()
 {
     if (m_mainMenusCreated) return;
@@ -716,6 +748,175 @@
 }
 
 void
+MainWindow::setupAlignmentMenu()
+{
+    m_keyReference->setCategory(tr("Alignment"));
+
+    IconLoader il;
+
+    QMenu *menu = menuBar()->addMenu(tr("&Alignment"));
+    menu->setTearOffEnabled(false);
+
+    QActionGroup *alignmentGroup = new QActionGroup(this);
+
+//!!! + explanatory status bar texts
+
+    map<Align::AlignmentType, QString> alignmentLabels {
+        { Align::NoAlignment, tr("No Alignment") },
+        { Align::LinearAlignment, tr("Linear") },
+        { Align::TrimmedLinearAlignment, tr("Linear Trimmed") },
+        { Align::MATCHAlignment, tr("MATCH Aligner") },
+        { Align::MATCHAlignmentWithPitchCompare, tr("MATCH with Tuning Compensation") },
+        { Align::SungNoteContourAlignment, tr("Sung Note Contour") },
+    };
+
+    QAction *action = nullptr;
+    Align::AlignmentType preference = Align::getAlignmentPreference();
+
+    for (auto al: alignmentLabels) {
+        action = menu->addAction(al.second);
+        action->setObjectName(Align::getAlignmentTypeTag(al.first));
+        action->setActionGroup(alignmentGroup);
+        action->setCheckable(true);
+        action->setChecked(al.first == preference);
+        connect(action, SIGNAL(triggered()), this, SLOT(alignmentTypeChanged()));
+        if (al.first == Align::NoAlignment) {
+            menu->addSeparator();
+        }
+    }
+
+    QString program = Align::getPreferredAlignmentProgram();
+    if (program == "") {
+        action = menu->addAction(tr("External Alignment Program"));
+        action->setEnabled(false);
+    } else {
+        QString filename = QFileInfo(program).fileName();
+        action = menu->addAction(tr("External Program: %1").arg(filename));
+    }
+
+    m_externalAlignmentAction = action;
+    
+    action->setObjectName
+        (Align::getAlignmentTypeTag(Align::ExternalProgramAlignment));
+    action->setActionGroup(alignmentGroup);
+    action->setCheckable(true);
+    action->setChecked(preference == Align::ExternalProgramAlignment);
+    connect(action, SIGNAL(triggered()), this, SLOT(alignmentTypeChanged()));
+
+    menu->addSeparator();
+    
+    action = menu->addAction(tr("Choose External Alignment Program..."));
+    connect(action, SIGNAL(triggered()), this, SLOT(chooseAlignmentProgram()));
+}
+
+void
+MainWindow::setupPlaybackMenu()
+{
+    m_keyReference->setCategory(tr("Playback and Transport Controls"));
+
+    IconLoader il;
+
+    QMenu *menu = menuBar()->addMenu(tr("Play&back"));
+    menu->setTearOffEnabled(false);
+
+    QToolBar *toolbar = nullptr;
+    if (m_playbackToolBar) {
+        toolbar = m_playbackToolBar;
+    } else {
+        toolbar = m_playbackToolBar = 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("PgUp"));
+    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
+    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
+    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
+
+    m_playAction = toolbar->addAction(il.load("playpause"),
+                                      tr("Play / Pause"));
+    m_playAction->setCheckable(true);
+    m_playAction->setShortcut(tr("Space"));
+    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
+    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
+    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
+	    m_playAction, SLOT(setChecked(bool)));
+    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
+
+    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
+                                      tr("Fast Forward"));
+    m_ffwdAction->setShortcut(tr("PgDown"));
+    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
+    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(this, SIGNAL(canRecord(bool)),
+            recordAction, SLOT(setEnabled(bool)));
+
+    m_keyReference->registerShortcut(m_playAction);
+    m_keyReference->registerShortcut(m_rwdAction);
+    m_keyReference->registerShortcut(m_ffwdAction);
+    m_keyReference->registerShortcut(rwdStartAction);
+    m_keyReference->registerShortcut(ffwdEndAction);
+    m_keyReference->registerShortcut(recordAction);
+
+    menu->addAction(m_playAction);
+    menu->addSeparator();
+    menu->addAction(m_rwdAction);
+    menu->addAction(m_ffwdAction);
+    menu->addSeparator();
+    menu->addAction(rwdStartAction);
+    menu->addAction(ffwdEndAction);
+    menu->addSeparator();
+    menu->addAction(recordAction);
+    menu->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);
+}
+
+void
 MainWindow::setupHelpMenu()
 {
     QMenu *menu = menuBar()->addMenu(tr("&Help"));
@@ -754,158 +955,6 @@
 }
 
 void
-MainWindow::setupRecentSessionsMenu()
-{
-    m_recentSessionsMenu->clear();
-    vector<pair<QString, QString>> sessions = m_recentSessions.getRecentEntries();
-    for (size_t i = 0; i < sessions.size(); ++i) {
-        QString path = sessions[i].first;
-        QString label = sessions[i].second;
-        if (label == "") label = path;
-        QAction *action = new QAction(label, this);
-        action->setObjectName(path);
-	connect(action, SIGNAL(triggered()), this, SLOT(openRecentSession()));
-        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 session"));
-        }
-	m_recentSessionsMenu->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(false);
-
-    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("PgUp"));
-    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
-    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
-    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
-
-    m_playAction = toolbar->addAction(il.load("playpause"),
-                                      tr("Play / Pause"));
-    m_playAction->setCheckable(true);
-    m_playAction->setShortcut(tr("Space"));
-    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
-    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
-    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
-	    m_playAction, SLOT(setChecked(bool)));
-    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
-
-    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
-                                      tr("Fast Forward"));
-    m_ffwdAction->setShortcut(tr("PgDown"));
-    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
-    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(this, SIGNAL(canRecord(bool)),
-            recordAction, SLOT(setEnabled(bool)));
-
-    m_keyReference->registerShortcut(m_playAction);
-    m_keyReference->registerShortcut(m_rwdAction);
-    m_keyReference->registerShortcut(m_ffwdAction);
-    m_keyReference->registerShortcut(rwdStartAction);
-    m_keyReference->registerShortcut(ffwdEndAction);
-    m_keyReference->registerShortcut(recordAction);
-
-    menu->addAction(m_playAction);
-    menu->addSeparator();
-    menu->addAction(m_rwdAction);
-    menu->addAction(m_ffwdAction);
-    menu->addSeparator();
-    menu->addAction(rwdStartAction);
-    menu->addAction(ffwdEndAction);
-    menu->addSeparator();
-    menu->addAction(recordAction);
-    menu->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);
-
-    QAction *alAction = 0;
-    alAction = toolbar->addAction(il.load("align"),
-                                  tr("Align File Timelines"));
-    alAction->setCheckable(true);
-    alAction->setChecked(m_viewManager->getAlignMode());
-    alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
-    connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
-            alAction, SLOT(setChecked(bool)));
-    connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
-
-    QSettings settings;
-
-    QAction *tdAction = 0;
-    tdAction = new QAction(tr("Allow for Pitch Difference when Aligning"), this);
-    tdAction->setCheckable(true);
-    settings.beginGroup("Alignment");
-    tdAction->setChecked(settings.value("align-pitch-aware", false).toBool());
-    settings.endGroup();
-    tdAction->setStatusTip(tr("Compare relative pitch content of audio files before aligning, in order to correctly align recordings of the same material at different tuning pitches"));
-    connect(tdAction, SIGNAL(triggered()), this, SLOT(tuningDifferenceToggled()));
-
-    menu->addSeparator();
-    menu->addAction(alAction);
-    menu->addAction(tdAction);
-
-    Pane::registerShortcuts(*m_keyReference);
-}
-
-void
 MainWindow::updateMenuStates()
 {
     MainWindowBase::updateMenuStates();
@@ -1414,6 +1463,22 @@
     }
 }
 
+void
+MainWindow::mapAllSalientFeatureLayers()
+{
+    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
+        Pane *p = m_paneStack->getPane(i);
+        for (int j = 0; j < p->getLayerCount(); ++j) {
+            auto modelId = p->getLayer(j)->getModel();
+            if (auto wfm = ModelById::getAs<WaveFileModel>(modelId)) {
+                SVDEBUG << "MainWindow::mapAllSalientFeatureLayers: calling mapSalientFeatureLayer for modelId " << modelId << " in pane " << i << ", layer " << j << endl;
+                mapSalientFeatureLayer(modelId);
+                break; // but only from inner loop, go on to next pane
+            }
+        }
+    }
+}
+
 TimeInstantLayer *
 MainWindow::findSalientFeatureLayer(Pane *pane)
 {
@@ -1496,17 +1561,13 @@
 }
 
 void
-MainWindow::mapSalientFeatureLayer(ModelId amId)
+MainWindow::mapSalientFeatureLayer(ModelId modelId)
 {
-    auto am = ModelById::getAs<AlignmentModel>(amId);
-    if (!am) {
-        SVCERR << "MainWindow::mapSalientFeatureLayer: AlignmentModel is absent!"
-               << endl;
-        return;
-    }
+    SVDEBUG << "MainWindow::mapSalientFeatureLayer(" << modelId << ")" << endl;
     
     if (m_salientCalculating) {
-        m_salientPending.insert(amId);
+        SVDEBUG << "MainWindow::mapSalientFeatureLayer(" << modelId << "): salient still calculating, adding to pending list" << endl;
+        m_salientPending.insert(modelId);
         return;
     }
 
@@ -1514,14 +1575,13 @@
     if (!salient) {
         SVCERR << "MainWindow::mapSalientFeatureLayer: No salient layer found"
                << endl;
-        m_salientPending.insert(amId);
+        m_salientPending.insert(modelId);
         return;
     }
     
-    ModelId modelId = am->getAlignedModel();
     auto model = ModelById::get(modelId);
     if (!model) {
-        SVCERR << "MainWindow::mapSalientFeatureLayer: No aligned model in AlignmentModel" << endl;
+        SVCERR << "MainWindow::mapSalientFeatureLayer: Aligned model is absent" << endl;
         return;
     }
 
@@ -1580,14 +1640,24 @@
     auto toId = ModelById::add(to);
 
     EventVector pp = from->getAllEvents();
-    for (const auto &p: pp) {
-        Event aligned = p
-            .withFrame(model->alignFromReference(p.getFrame()))
-            .withLabel(""); // remove label, as the analysis was not
-                            // conducted on the audio we're mapping to
-        to->add(aligned);
+
+    if (Align::getAlignmentPreference() != Align::NoAlignment) {
+        for (const auto &p: pp) {
+            Event aligned = p
+                .withFrame(model->alignFromReference(p.getFrame()))
+                .withLabel(""); // remove label, as the analysis was not
+                                // conducted on the audio we're mapping to
+            to->add(aligned);
+        }
+    } else {
+        for (const auto &p: pp) {
+            to->add(p.withLabel(""));
+        }
     }
 
+    SVDEBUG << "MainWindow::mapSalientFeatureLayer for model " << modelId
+            << ": have " << pp.size() << " events" << endl;
+    
     Layer *newLayer = m_document->createImportedLayer(toId);
 
     if (newLayer) {
@@ -2183,6 +2253,10 @@
 
     zoomToFit();
     reselectMode();
+
+    if (Align::getAlignmentPreference() == Align::NoAlignment) {
+        mapAllSalientFeatureLayers();
+    }
 }
 
 void
@@ -2286,23 +2360,40 @@
 }
 
 void
-MainWindow::alignToggled()
+MainWindow::alignmentTypeChanged()
 {
     QAction *action = dynamic_cast<QAction *>(sender());
     
-    if (!m_viewManager) return;
-
-    if (action) {
-	m_viewManager->setAlignMode(action->isChecked());
+    if (!action || !m_viewManager) return;
+
+    Align::AlignmentType alignmentType =
+        Align::getAlignmentTypeForTag(action->objectName());
+    
+    if (alignmentType == Align::NoAlignment) {
+
+        Align::setAlignmentPreference(alignmentType);
+        m_viewManager->setAlignMode(false);
+        m_document->setAutoAlignment(false);
+
+        SVDEBUG << "MainWindow::alignmentTypeChanged: type is now NoAlignment, so salient feature layer won't be automatically mapped - doing it by hand" << endl;
+
+        mapAllSalientFeatureLayers();
+        checkpointSession();
+
     } else {
-	m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
-    }
-
-    if (m_viewManager->getAlignMode()) {
-        m_document->alignModels();
+
+        Align::setAlignmentPreference(alignmentType);
+
+        m_viewManager->setAlignMode(true);
+        
+        if (alignmentType == m_previousActiveAlignmentType) {
+            m_document->alignModels();
+        } else {
+            m_document->realignModels();
+        }
+
         m_document->setAutoAlignment(true);
-    } else {
-        m_document->setAutoAlignment(false);
+        m_previousActiveAlignmentType = alignmentType;
     }
 
     for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
@@ -2313,18 +2404,25 @@
 }
 
 void
-MainWindow::tuningDifferenceToggled()
+MainWindow::chooseAlignmentProgram()
 {
-    QSettings settings;
-    settings.beginGroup("Alignment");
-    bool on = settings.value("align-pitch-aware", false).toBool();
-    settings.setValue("align-pitch-aware", !on);
-    settings.endGroup();
-
-    if (m_viewManager->getAlignMode()) {
-        m_document->realignModels();
+    QString formerProgram = Align::getPreferredAlignmentProgram();
+    QString newProgram =
+        QFileDialog::getOpenFileName(this,
+                                     tr("External Alignment Program"),
+                                     formerProgram);
+    if (newProgram != "") {
+        SVCERR << "Setting alignment preference to ExternalProgramAlignment "
+               << "with program " << newProgram << endl;
+        Align::setAlignmentPreference(Align::ExternalProgramAlignment);
+        Align::setPreferredAlignmentProgram(newProgram);
+        QString filename = QFileInfo(newProgram).fileName();
+        m_externalAlignmentAction->setText
+            (tr("External Program: %1").arg(filename));
+        m_externalAlignmentAction->setEnabled(true);
+        m_externalAlignmentAction->activate(QAction::Trigger);
     }
-}    
+}
     
 void
 MainWindow::playSpeedChanged(int position)
@@ -2877,9 +2975,17 @@
 }
 
 void
-MainWindow::alignmentComplete(ModelId modelId)
+MainWindow::alignmentComplete(ModelId amId)
 {
-    cerr << "MainWindow::alignmentComplete(" << modelId << ")" << endl;
+    SVCERR << "MainWindow::alignmentComplete(" << amId << ")" << endl;
+    auto am = ModelById::getAs<AlignmentModel>(amId);
+    if (!am) {
+        SVCERR << "MainWindow::alignmentComplete: AlignmentModel is absent!"
+               << endl;
+        return;
+    }
+    
+    ModelId modelId = am->getAlignedModel();
     mapSalientFeatureLayer(modelId);
     checkpointSession();
 }
--- a/main/MainWindow.h	Thu May 14 16:59:51 2020 +0100
+++ b/main/MainWindow.h	Fri Jun 26 13:52:26 2020 +0100
@@ -30,6 +30,7 @@
 #include "transform/Transform.h"
 #include "framework/SVFileReader.h"
 #include "widgets/InteractiveFileFinder.h"
+#include "align/Align.h"
 
 #include "SmallSession.h"
 
@@ -122,14 +123,14 @@
     void paneDropAccepted(Pane *, QString) override;
 
     void record() override;
-    
-    virtual void alignToggled();
-    virtual void tuningDifferenceToggled();
-    virtual void playSpeedChanged(int);
 
-    virtual void speedUpPlayback();
-    virtual void slowDownPlayback();
-    virtual void restoreNormalPlayback();
+    void alignmentTypeChanged();
+    void chooseAlignmentProgram();
+
+    void playSpeedChanged(int);
+    void speedUpPlayback();
+    void slowDownPlayback();
+    void restoreNormalPlayback();
 
     void monitoringLevelsChanged(float, float) override;
     
@@ -192,7 +193,7 @@
     QScrollArea             *m_mainScroll;
 
     bool                     m_mainMenusCreated;
-    QMenu                   *m_playbackMenu;
+    QToolBar                *m_playbackToolBar;
     QMenu                   *m_recentSessionsMenu;
 
     QAction                 *m_deleteSelectedAction;
@@ -211,6 +212,9 @@
     QAction                 *m_selectPreviousDisplayModeAction;
     QAction                 *m_selectNextDisplayModeAction;
 
+    QAction                 *m_externalAlignmentAction;
+    Align::AlignmentType     m_previousActiveAlignmentType;
+    
     RecentFiles              m_recentSessions;
     
     bool                     m_exiting;
@@ -232,8 +236,9 @@
     
     virtual void setupFileMenu();
     virtual void setupViewMenu();
+    virtual void setupAlignmentMenu();
+    virtual void setupPlaybackMenu();
     virtual void setupHelpMenu();
-    virtual void setupToolbars();
 
     enum DisplayMode {
         OutlineWaveformMode,
@@ -265,7 +270,9 @@
                                             ModelId *createFrom);
 
     virtual void addSalientFeatureLayer(Pane *, ModelId); // a WaveFileModel
-    virtual void mapSalientFeatureLayer(ModelId); // an AlignmentModel
+    virtual void mapSalientFeatureLayer(ModelId); // a WaveFileModel
+
+    void mapAllSalientFeatureLayers();
 
     // Return the salient-feature layer in the given pane. If pane is
     // unspecified, return the main salient-feature layer, i.e. the
@@ -274,7 +281,7 @@
     virtual TimeInstantLayer *findSalientFeatureLayer(Pane *pane = nullptr);
     
     bool m_salientCalculating;
-    std::set<ModelId> m_salientPending; // AlignmentModels
+    std::set<ModelId> m_salientPending; // Aligned WaveFileModels
     int m_salientColour;
     
     void updateVisibleRangeDisplay(Pane *p) const override;
--- a/pitch-track-align/pitch-track-align.sml	Thu May 14 16:59:51 2020 +0100
+++ b/pitch-track-align/pitch-track-align.sml	Fri Jun 26 13:52:26 2020 +0100
@@ -70,6 +70,16 @@
 
 fun alignSeries s1 s2 =
     let val cumulativeCosts = costSeries s1 s2
+(*        val _ = let open TextIO in
+                    output (stdErr, "Cost matrix:\n");
+                    Vector.app
+                        (fn v =>
+                            (Vector.app
+                                 (fn x => output (stdErr, Real.toString x ^ " ")) v;
+                             output (stdErr, "\n")))
+                        cumulativeCosts
+                end
+*)
         fun cost (j, i) = Vector.sub (Vector.sub (cumulativeCosts, j), i)
         fun trace (j, i) acc =
             if i = 0
@@ -123,7 +133,7 @@
             in
                 rev (#1 acc)
             end
-        val _ =
+(*        val _ =
             app (fn (text, p) =>
                     TextIO.output (TextIO.stdErr, ("[" ^ text ^ "] -> " ^
                                                    Real.toString p ^ "\n")))
@@ -131,6 +141,18 @@
                                 | (PITCH_UP d, p) => ("+", p)
                                 | (PITCH_DOWN d, p) => ("-", p))
                               (values, pitches))
+        val _ = TextIO.output (TextIO.stdErr, "(end)\n");
+ *)
+        val _ =
+            app (fn v =>
+                    TextIO.output (TextIO.stdErr,
+                                   (case v of
+                                        PITCH_NONE => "=0"
+                                      | PITCH_UP d => "+" ^ Real.toString d
+                                      | PITCH_DOWN d => "-" ^ Real.toString d)
+                                   ^ " "))
+                values
+        val _ = TextIO.output (TextIO.stdErr, " (end)\n");
     in
         (Vector.fromList times,
          Vector.fromList values,
@@ -181,6 +203,11 @@
         (* raw alignment returns the index into pitches2 for each
            element in pitches1 *)
         val raw = alignSeries values1 values2
+        val _ = TextIO.output (TextIO.stdErr, "DTW output:\n")
+        val _ = Vector.app
+                    (fn i => TextIO.output (TextIO.stdErr, Int.toString i ^ " "))
+                    raw
+        val _ = TextIO.output (TextIO.stdErr, "\n")
         val _ = TextIO.output (TextIO.stdErr,
                                "Mean pitch difference: reference " ^
                                Real.toString (meanDiff pitches1 pitches2 raw)
--- a/repoint-lock.json	Thu May 14 16:59:51 2020 +0100
+++ b/repoint-lock.json	Fri Jun 26 13:52:26 2020 +0100
@@ -1,16 +1,16 @@
 {
   "libraries": {
     "vamp-plugin-sdk": {
-      "pin": "8ffb8985ae8f"
+      "pin": "0e32c328b02a"
     },
     "svcore": {
-      "pin": "14747f24ad04"
+      "pin": "38be2fa29efd"
     },
     "svgui": {
-      "pin": "bd1a7c84da8c"
+      "pin": "fe9a643b83bf"
     },
     "svapp": {
-      "pin": "cf4e0f3c2406"
+      "pin": "83a7b10b7415"
     },
     "checker": {
       "pin": "e839338d3869"
@@ -70,7 +70,7 @@
       "pin": "e34a3cc188332ed7c33cd9257ef164de5b587191"
     },
     "azi": {
-      "pin": "d490c1c7e1ae"
+      "pin": "299df1b44eff"
     }
   }
 }
--- a/version.h	Thu May 14 16:59:51 2020 +0100
+++ b/version.h	Fri Jun 26 13:52:26 2020 +0100
@@ -1,1 +1,1 @@
-#define VECT_VERSION "1.0.1"
+#define VECT_VERSION "1.1-pre2"