Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion main/Analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

#include "Analyser.h"
#include "RecordingPreview.h"

#include "transform/TransformFactory.h"
#include "transform/ModelTransformer.h"
Expand Down Expand Up @@ -47,10 +48,14 @@ Analyser::Analyser() :
m_document(0),
m_paneStack(0),
m_pane(0),
m_recordingPreview(new RecordingPreview(this)),
m_currentCandidate(-1),
m_candidatesVisible(false),
m_currentAsyncHandle(0)
{
connect(m_recordingPreview, SIGNAL(previewUpdated()),
this, SIGNAL(layersChanged()));

QSettings settings;
settings.beginGroup("LayerDefaults");
settings.setValue
Expand All @@ -77,7 +82,8 @@ Analyser::getAnalysisSettings()
return { { "precision-analysis", false },
{ "lowamp-analysis", true },
{ "onset-analysis", true },
{ "prune-analysis", true }
{ "prune-analysis", true },
{ "record-preview", false }
};
}

Expand Down Expand Up @@ -126,6 +132,53 @@ Analyser::analyseExistingFile()
return doAllAnalyses(true);
}

QString
Analyser::beginRecordingPreview()
{
if (!m_document) return "Internal error: Analyser::beginRecordingPreview() called with no document present";

if (!m_pane) return "Internal error: Analyser::beginRecordingPreview() called with no pane present";

if (m_fileModel.isNone()) return "Internal error: Analyser::beginRecordingPreview() called with no model present";

// The preview draws into the pitch track, so we need one. If
// auto-analysis is switched off there won't be one yet, but asking
// to see analysis while recording is a clear enough request for it.
if (!m_layers[PitchTrack]) {
QString error = addAnalyses();
if (error != "") return error;
}

TimeValueLayer *pitchLayer =
qobject_cast<TimeValueLayer *>(m_layers[PitchTrack]);
if (!pitchLayer) {
return "Internal error: Analyser::beginRecordingPreview() has no pitch track layer";
}

FlexiNoteLayer *noteLayer =
qobject_cast<FlexiNoteLayer *>(m_layers[Notes]);

return m_recordingPreview->begin(m_fileModel, pitchLayer, noteLayer);
}

void
Analyser::recordingPreviewReachedFrame(sv_frame_t frame)
{
m_recordingPreview->recordedTo(frame);
}

bool
Analyser::isRecordingPreviewActive() const
{
return m_recordingPreview->isActive();
}

void
Analyser::endRecordingPreview()
{
m_recordingPreview->end();
}

QString
Analyser::doAllAnalyses(bool withPitchTrack)
{
Expand Down Expand Up @@ -170,6 +223,7 @@ void
Analyser::fileClosed()
{
cerr << "Analyser::fileClosed" << endl;
m_recordingPreview->abandon();
m_layers.clear();
m_reAnalysisCandidates.clear();
m_currentCandidate = -1;
Expand Down
29 changes: 29 additions & 0 deletions main/Analyser.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class TimeValueLayer;
class Layer;
}

class RecordingPreview;

class Analyser : public QObject,
public sv::Document::LayerCreationHandler
{
Expand All @@ -56,6 +58,31 @@ class Analyser : public QObject,
// layers; return "" on success or error string on failure
QString analyseExistingFile();

/**
* Start filling in the pitch track as a recording is made, so that
* the performer can see something as they go. Everything this adds
* is removed again by endRecordingPreview(), because the pitch and
* note layers are regenerated in full when recording stops. Returns
* "" on success or an error string on failure.
*/
QString beginRecordingPreview();

/**
* Note that the recording being previewed has reached the given
* frame.
*/
void recordingPreviewReachedFrame(sv::sv_frame_t frame);

/**
* Return true if a recording preview is currently running.
*/
bool isRecordingPreviewActive() const;

/**
* Stop previewing and remove everything the preview added.
*/
void endRecordingPreview();

// Discard any layers etc associated with the current document
void fileClosed();

Expand Down Expand Up @@ -245,6 +272,8 @@ protected slots:

mutable std::map<Component, sv::Layer *> m_layers;

RecordingPreview *m_recordingPreview;

sv::Clipboard m_preAnalysis;
sv::Selection m_reAnalysingSelection;
FrequencyRange m_reAnalysingRange;
Expand Down
77 changes: 75 additions & 2 deletions main/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ MainWindow::MainWindow(AudioMode audioMode,
m_keyReference(new KeyReference()),
m_selectionAnchor(0),
m_withSonification(withSonification),
m_withSpectrogram(withSpectrogram)
m_withSpectrogram(withSpectrogram),
m_recordPreviewAttempted(false)
{
setWindowTitle(QApplication::applicationName());

Expand Down Expand Up @@ -774,6 +775,12 @@ MainWindow::setupAnalysisMenu()
connect(m_autoAnalyse, SIGNAL(triggered()), this, SLOT(autoAnalysisToggled()));
menu->addAction(m_autoAnalyse);

m_recordPreview = new QAction(tr("Show Pitch Track While &Recording"), this);
m_recordPreview->setStatusTip(tr("Fill in the pitch track as a recording is made. The analysis is redone in full when recording stops."));
m_recordPreview->setCheckable(true);
connect(m_recordPreview, SIGNAL(triggered()), this, SLOT(recordPreviewToggled()));
menu->addAction(m_recordPreview);

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()));
Expand Down Expand Up @@ -846,7 +853,8 @@ MainWindow::updateAnalyseStates()
{ "precision-analysis", m_precise },
{ "lowamp-analysis", m_lowamp },
{ "onset-analysis", m_onset },
{ "prune-analysis", m_prune }
{ "prune-analysis", m_prune },
{ "record-preview", m_recordPreview }
};

auto keyMap = Analyser::getAnalysisSettings();
Expand Down Expand Up @@ -881,6 +889,69 @@ MainWindow::autoAnalysisToggled()
updateAnalyseStates();
}

void
MainWindow::recordPreviewToggled()
{
QAction *a = qobject_cast<QAction *>(sender());
if (!a) return;

QSettings settings;
settings.beginGroup("Analyser");
settings.setValue("record-preview", a->isChecked());
settings.endGroup();

updateAnalyseStates();
}

void
MainWindow::recordStatusChanged(bool recording)
{
if (recording) {
m_recordPreviewAttempted = false;
return;
}

// Everything the preview added is removed here. The pitch and note
// layers are then regenerated in full from the completed recording,
// exactly as they are without this feature.
m_analyser->endRecordingPreview();
}

void
MainWindow::recordDurationChanged(sv_frame_t frame, sv_samplerate_t rate)
{
MainWindowBase::recordDurationChanged(frame, rate);

if (!m_analyser->isRecordingPreviewActive()) {

if (m_recordPreviewAttempted) return;
m_recordPreviewAttempted = true;

// Not started on recordStatusChanged(true): that is emitted from
// startRecording(), before record() has closed the old session,
// created the new document, set the main model and built the
// analysis layers. Starting there would attach the preview to
// the session that is about to be discarded. By the time the
// first duration update arrives all of that has happened.

QSettings settings;
settings.beginGroup("Analyser");
bool preview = settings.value("record-preview", false).toBool();
settings.endGroup();

if (!preview) return;

QString error = m_analyser->beginRecordingPreview();
if (error != "") {
SVCERR << "MainWindow::recordDurationChanged: unable to preview: "
<< error << endl;
return;
}
}

m_analyser->recordingPreviewReachedFrame(frame);
}

void
MainWindow::precisionAnalysisToggled()
{
Expand Down Expand Up @@ -1078,6 +1149,8 @@ MainWindow::setupToolbars()
connect(recordAction, SIGNAL(triggered()), this, SLOT(record()));
connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)),
recordAction, SLOT(setChecked(bool)));
connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)),
this, SLOT(recordStatusChanged(bool)));
connect(m_recordTarget, SIGNAL(recordCompleted()),
this, SLOT(analyseNow()));
connect(this, SIGNAL(canRecord(bool)),
Expand Down
9 changes: 9 additions & 0 deletions main/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ protected slots:
virtual void analyseNow();
virtual void resetAnalyseOptions();
virtual void autoAnalysisToggled();
virtual void recordPreviewToggled();
virtual void recordStatusChanged(bool);
virtual void precisionAnalysisToggled();
virtual void lowampAnalysisToggled();
virtual void onsetAnalysisToggled();
Expand Down Expand Up @@ -205,6 +207,7 @@ protected slots:
bool m_intelligentActionOn; // GF: !!! temporary

QAction *m_autoAnalyse;
QAction *m_recordPreview;
QAction *m_precise;
QAction *m_lowamp;
QAction *m_onset;
Expand All @@ -231,6 +234,10 @@ protected slots:
bool m_withSonification;
bool m_withSpectrogram;

// Preview is started on the first duration update rather than when
// recording starts; this stops us retrying every update if it fails
bool m_recordPreviewAttempted;

Analyser::FrequencyRange m_pendingConstraint;

QString exportToSVL(QString path, sv::Layer *layer);
Expand All @@ -254,6 +261,8 @@ protected slots:
bool checkSaveModified();
bool waitForInitialAnalysis();

virtual void recordDurationChanged(sv::sv_frame_t, sv::sv_samplerate_t);

virtual void updateVisibleRangeDisplay(sv::Pane *p) const;
virtual void updatePositionStatusDisplays() const;

Expand Down
66 changes: 66 additions & 0 deletions main/PreviewChunk.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* -*- 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 "PreviewChunk.h"

using namespace sv;

namespace PreviewChunk {

std::optional<Range>
nextRange(sv_frame_t analysedTo,
sv_frame_t recordedTo,
sv_frame_t minFrames,
sv_frame_t maxFrames,
sv_frame_t revisitFrames)
{
if (analysedTo < 0) analysedTo = 0;

if (recordedTo <= analysedTo) {
return {};
}

if (recordedTo - analysedTo < minFrames) {
return {};
}

sv_frame_t to = recordedTo;

if (maxFrames > 0 && to - analysedTo > maxFrames) {
to = analysedTo + maxFrames;
}

sv_frame_t from = analysedTo - revisitFrames;
if (from < 0) from = 0;

return Range { from, to };
}

EventVector
withinRange(const EventVector &events, const Range &range)
{
EventVector result;
result.reserve(events.size());

for (const auto &e: events) {
if (e.getFrame() >= range.from && e.getFrame() < range.to) {
result.push_back(e);
}
}

return result;
}

}
Loading