changeset 481:52c0aff69478 3.0-integration

Merge from branch recording
author Chris Cannam
date Thu, 20 Aug 2015 13:15:19 +0100
parents 6ec35c1690c0 (current diff) 1d4cb8befcfd (diff)
children 01669adb0956 111e976f9ed4
files framework/MainWindowBase.cpp
diffstat 7 files changed, 415 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.h	Wed Aug 05 17:47:12 2015 +0100
+++ b/audio/AudioCallbackPlaySource.h	Thu Aug 20 13:15:19 2015 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _AUDIO_CALLBACK_PLAY_SOURCE_H_
-#define _AUDIO_CALLBACK_PLAY_SOURCE_H_
+#ifndef AUDIO_CALLBACK_PLAY_SOURCE_H
+#define AUDIO_CALLBACK_PLAY_SOURCE_H
 
 #include "base/RingBuffer.h"
 #include "base/AudioPlaySource.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioRecordTarget.cpp	Thu Aug 20 13:15:19 2015 +0100
@@ -0,0 +1,145 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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 "AudioRecordTarget.h"
+
+#include "base/ViewManagerBase.h"
+#include "base/TempDirectory.h"
+
+#include "data/model/WritableWaveFileModel.h"
+
+#include <QDir>
+
+AudioRecordTarget::AudioRecordTarget(ViewManagerBase *manager,
+				     QString clientName) :
+    m_viewManager(manager),
+    m_clientName(clientName.toUtf8().data()),
+    m_recording(false),
+    m_recordSampleRate(44100),
+    m_model(0)
+{
+}
+
+AudioRecordTarget::~AudioRecordTarget()
+{
+    QMutexLocker locker(&m_mutex);
+}
+
+void
+AudioRecordTarget::setSystemRecordBlockSize(int sz)
+{
+}
+
+void
+AudioRecordTarget::setSystemRecordSampleRate(int n)
+{
+    m_recordSampleRate = n;
+}
+
+void
+AudioRecordTarget::setSystemRecordLatency(int sz)
+{
+}
+
+void
+AudioRecordTarget::putSamples(int nframes, float **samples)
+{
+    QMutexLocker locker(&m_mutex); //!!! bad here
+    if (!m_recording) return;
+    m_model->addSamples(samples, nframes);
+}
+
+void
+AudioRecordTarget::setInputLevels(float peakLeft, float peakRight)
+{
+}
+
+void
+AudioRecordTarget::modelAboutToBeDeleted()
+{
+    QMutexLocker locker(&m_mutex);
+    if (sender() == m_model) {
+        m_model = 0;
+        m_recording = false;
+    }
+}
+
+WritableWaveFileModel *
+AudioRecordTarget::startRecording()
+{
+    {
+    QMutexLocker locker(&m_mutex);
+    if (m_recording) {
+        cerr << "WARNING: AudioRecordTarget::startRecording: We are already recording" << endl;
+        return 0;
+    }
+
+    m_model = 0;
+
+    QDir parent(TempDirectory::getInstance()->getContainingPath());
+    QDir recordedDir;
+    QString subdirname = "recorded"; //!!! tr?
+    if (!parent.mkpath(subdirname)) {
+        cerr << "ERROR: AudioRecordTarget::startRecording: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return 0;
+    } else {
+        recordedDir = parent.filePath(subdirname);
+    }
+
+    QDateTime now = QDateTime::currentDateTime();
+
+    // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
+    // isn't permitted in filenames on Windows
+    QString filename = QString("recorded-%1.wav")
+        .arg(now.toString("yyyyMMdd-HHmmss-zzz"));
+
+    m_audioFileName = recordedDir.filePath(filename);
+
+    m_model = new WritableWaveFileModel(m_recordSampleRate, 2, m_audioFileName);
+
+    if (!m_model->isOK()) {
+        cerr << "ERROR: AudioRecordTarget::startRecording: Recording failed"
+             << endl;
+        //!!! and throw?
+        delete m_model;
+        m_model = 0;
+        return 0;
+    }
+
+    m_recording = true;
+    }
+
+    emit recordStatusChanged(true);
+    return m_model;
+}
+
+void
+AudioRecordTarget::stopRecording()
+{
+    {
+    QMutexLocker locker(&m_mutex);
+    if (!m_recording) {
+        cerr << "WARNING: AudioRecordTarget::startRecording: Not recording" << endl;
+        return;
+    }
+
+    m_model->setCompletion(100);
+    m_model = 0;
+    m_recording = false;
+    }
+
+    emit recordStatusChanged(false);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioRecordTarget.h	Thu Aug 20 13:15:19 2015 +0100
@@ -0,0 +1,74 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.
+*/
+
+#ifndef AUDIO_RECORD_TARGET_H
+#define AUDIO_RECORD_TARGET_H
+
+#include <bqaudioio/ApplicationRecordTarget.h>
+
+#include <string>
+
+#include <QObject>
+#include <QMutex>
+
+#include "base/BaseTypes.h"
+
+class ViewManagerBase;
+class WritableWaveFileModel;
+
+class AudioRecordTarget : public QObject,
+			  public breakfastquay::ApplicationRecordTarget
+{
+    Q_OBJECT
+
+public:
+    AudioRecordTarget(ViewManagerBase *, QString clientName);
+    virtual ~AudioRecordTarget();
+
+    virtual std::string getClientName() const { return m_clientName; }
+    
+    virtual int getApplicationSampleRate() const { return 0; } // don't care
+    virtual int getApplicationChannelCount() const { return 2; }
+
+    virtual void setSystemRecordBlockSize(int);
+    virtual void setSystemRecordSampleRate(int);
+    virtual void setSystemRecordLatency(int);
+
+    virtual void putSamples(int nframes, float **samples);
+    
+    virtual void setInputLevels(float peakLeft, float peakRight);
+
+    virtual void audioProcessingOverload() { }
+
+    bool isRecording() const { return m_recording; }
+    WritableWaveFileModel *startRecording(); // caller takes ownership
+    void stopRecording();
+
+signals:
+    void recordStatusChanged(bool recording);
+
+protected slots:
+    void modelAboutToBeDeleted();
+    
+private:
+    ViewManagerBase *m_viewManager;
+    std::string m_clientName;
+    bool m_recording;
+    sv_samplerate_t m_recordSampleRate;
+    QString m_audioFileName;
+    WritableWaveFileModel *m_model;
+    QMutex m_mutex;
+};
+
+#endif
--- a/framework/MainWindowBase.cpp	Wed Aug 05 17:47:12 2015 +0100
+++ b/framework/MainWindowBase.cpp	Thu Aug 20 13:15:19 2015 +0100
@@ -16,10 +16,10 @@
 #include "MainWindowBase.h"
 #include "Document.h"
 
-
 #include "view/Pane.h"
 #include "view/PaneStack.h"
-#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
+#include "data/model/WritableWaveFileModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/NoteModel.h"
 #include "data/model/FlexiNoteModel.h"
@@ -48,7 +48,9 @@
 #include "widgets/InteractiveFileFinder.h"
 
 #include "audio/AudioCallbackPlaySource.h"
+#include "audio/AudioRecordTarget.h"
 #include "audio/PlaySpeedRangeMapper.h"
+
 #include "data/fileio/DataFileReaderFactory.h"
 #include "data/fileio/PlaylistFileReader.h"
 #include "data/fileio/WavFileWriter.h"
@@ -72,6 +74,7 @@
 #include "data/midi/MIDIInput.h"
 
 #include <bqaudioio/SystemPlaybackTarget.h>
+#include <bqaudioio/SystemAudioIO.h>
 #include <bqaudioio/AudioFactory.h>
 
 #include <QApplication>
@@ -130,15 +133,16 @@
 #undef Window
 #endif
 
-MainWindowBase::MainWindowBase(bool withAudioOutput,
-                               bool withMIDIInput) :
+MainWindowBase::MainWindowBase(SoundOptions options) :
     m_document(0),
     m_paneStack(0),
     m_viewManager(0),
     m_timeRulerLayer(0),
-    m_audioOutput(withAudioOutput),
+    m_soundOptions(options),
     m_playSource(0),
+    m_recordTarget(0),
     m_playTarget(0),
+    m_audioIO(0),
     m_oscQueue(0),
     m_oscQueueStarter(0),
     m_midiInput(0),
@@ -156,6 +160,12 @@
 {
     Profiler profiler("MainWindowBase::MainWindowBase");
 
+    if (options & WithAudioInput) {
+        if (!(options & WithAudioOutput)) {
+            cerr << "WARNING: MainWindowBase: WithAudioInput requires WithAudioOutput -- recording will not work" << endl;
+        }
+    }
+    
     qRegisterMetaType<sv_frame_t>("sv_frame_t");
     qRegisterMetaType<sv_samplerate_t>("sv_samplerate_t");
 
@@ -217,6 +227,10 @@
 
     m_playSource = new AudioCallbackPlaySource(m_viewManager,
                                                QApplication::applicationName());
+    if (m_soundOptions & WithAudioInput) {
+        m_recordTarget = new AudioRecordTarget(m_viewManager,
+                                               QApplication::applicationName());
+    }
 
     connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)),
 	    this,           SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)));
@@ -257,7 +271,7 @@
     m_labeller = new Labeller(labellerType);
     m_labeller->setCounterCycleSize(cycle);
 
-    if (withMIDIInput) {
+    if (m_soundOptions & WithMIDIInput) {
         m_midiInput = new MIDIInput(QApplication::applicationName(), this);
     }
 
@@ -269,6 +283,8 @@
     SVDEBUG << "MainWindowBase::~MainWindowBase" << endl;
     delete m_playTarget;
     delete m_playSource;
+    delete m_audioIO;
+    delete m_recordTarget;
     delete m_viewManager;
     delete m_oscQueue;
     delete m_oscQueueStarter;
@@ -550,7 +566,7 @@
     bool haveMainModel =
 	(getMainModel() != 0);
     bool havePlayTarget =
-	(m_playTarget != 0);
+	(m_playTarget != 0 || m_audioIO != 0);
     bool haveSelection = 
 	(m_viewManager &&
 	 !m_viewManager->getSelections().empty());
@@ -595,6 +611,7 @@
     emit canMeasureLayer(haveCurrentLayer);
     emit canSelect(haveMainModel && haveCurrentPane);
     emit canPlay(haveMainModel && havePlayTarget);
+    emit canRecord(m_soundOptions & WithAudioInput); // always possible then
     emit canFfwd(haveMainModel);
     emit canRewind(haveMainModel);
     emit canPaste(haveClipboardContents);
@@ -1311,7 +1328,7 @@
         rate = m_playSource->getSourceSampleRate();
     }
 
-    WaveFileModel *newModel = new WaveFileModel(source, rate);
+    ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(source, rate);
 
     if (!newModel->isOK()) {
 	delete newModel;
@@ -2158,9 +2175,11 @@
 }
 
 void
-MainWindowBase::createPlayTarget()
+MainWindowBase::createAudioIO()
 {
-    if (m_playTarget) return;
+    if (m_playTarget || m_audioIO) return;
+
+    if (!(m_soundOptions & WithAudioOutput)) return;
 
     //!!! how to handle preferences
 /*    
@@ -2172,13 +2191,18 @@
 
     factory->setDefaultCallbackTarget(targetName);
 */
-    
-    m_playTarget =
-        breakfastquay::AudioFactory::createCallbackPlayTarget(m_playSource);
-
-    m_playSource->setSystemPlaybackTarget(m_playTarget);
-
-    if (!m_playTarget) {
+
+    if (m_soundOptions & WithAudioInput) {
+        m_audioIO = breakfastquay::AudioFactory::
+            createCallbackIO(m_recordTarget, m_playSource);
+        m_playSource->setSystemPlaybackTarget(m_audioIO);
+    } else {
+        m_playTarget = breakfastquay::AudioFactory::
+            createCallbackPlayTarget(m_playSource);
+        m_playSource->setSystemPlaybackTarget(m_playTarget);
+    }
+
+    if (!m_playTarget && !m_audioIO) {
         emit hideSplash();
 
 //        if (factory->isAutoCallbackTarget(targetName)) {
@@ -2195,6 +2219,7 @@
                  QMessageBox::Ok);
         }
 */
+            return;
     }
 }
 
@@ -2626,8 +2651,10 @@
 void
 MainWindowBase::play()
 {
-    if (m_playSource->isPlaying()) {
+    if (m_recordTarget->isRecording() || m_playSource->isPlaying()) {
         stop();
+        QAction *action = qobject_cast<QAction *>(sender());
+        if (action) action->setChecked(false);
     } else {
         playbackFrameChanged(m_viewManager->getPlaybackFrame());
 	m_playSource->play(m_viewManager->getPlaybackFrame());
@@ -2635,6 +2662,124 @@
 }
 
 void
+MainWindowBase::record()
+{
+    if (!(m_soundOptions & WithAudioInput)) {
+        return;
+    }
+
+    if (!m_recordTarget) {
+        //!!! report
+        return;
+    }
+
+    if (!m_audioIO) {
+        createAudioIO();
+    }
+    
+    if (m_recordTarget->isRecording()) {
+        m_recordTarget->stopRecording();
+        emit audioFileLoaded();
+        return;
+    }
+
+    WritableWaveFileModel *model = m_recordTarget->startRecording();
+    if (!model) {
+        cerr << "ERROR: MainWindowBase::record: Recording failed" << endl;
+        //!!! report
+        return;
+    }
+
+    if (!model->isOK()) {
+        m_recordTarget->stopRecording();
+        delete model;
+        //!!! ???
+        return;
+    }
+
+    PlayParameterRepository::getInstance()->addPlayable(model);
+    
+    if (!getMainModel()) {
+
+        //!!! duplication with openAudio here
+        
+        QString templateName = getDefaultSessionTemplate();
+        bool loadedTemplate = false;
+        
+        if (templateName != "") {
+            FileOpenStatus tplStatus = openSessionTemplate(templateName);
+            if (tplStatus == FileOpenCancelled) {
+                return;
+            }
+            if (tplStatus != FileOpenFailed) {
+                loadedTemplate = true;
+            }
+        }
+
+        if (!loadedTemplate) {
+            closeSession();
+            createDocument();
+        }
+        
+        Model *prevMain = getMainModel();
+        if (prevMain) {
+            m_playSource->removeModel(prevMain);
+            PlayParameterRepository::getInstance()->removePlayable(prevMain);
+        }
+        
+        m_document->setMainModel(model);
+        setupMenus();
+
+	if (loadedTemplate || (m_sessionFile == "")) {
+            //!!! shouldn't be dealing directly with title from here -- call a method
+	    setWindowTitle(tr("%1: %2")
+                           .arg(QApplication::applicationName())
+                           .arg(model->getLocation()));
+	    CommandHistory::getInstance()->clear();
+	    CommandHistory::getInstance()->documentSaved();
+	    m_documentModified = false;
+	} else {
+	    setWindowTitle(tr("%1: %2 [%3]")
+                           .arg(QApplication::applicationName())
+			   .arg(QFileInfo(m_sessionFile).fileName())
+			   .arg(model->getLocation()));
+	    if (m_documentModified) {
+		m_documentModified = false;
+		documentModified(); // so as to restore "(modified)" window title
+	    }
+	}
+
+    } else {
+
+        CommandHistory::getInstance()->startCompoundOperation
+            (tr("Import Recorded Audio"), true);
+
+        m_document->addImportedModel(model);
+
+        AddPaneCommand *command = new AddPaneCommand(this);
+        CommandHistory::getInstance()->addCommand(command);
+
+        Pane *pane = command->getPane();
+
+        if (m_timeRulerLayer) {
+            m_document->addLayerToView(pane, m_timeRulerLayer);
+        }
+
+        Layer *newLayer = m_document->createImportedLayer(model);
+
+        if (newLayer) {
+            m_document->addLayerToView(pane, newLayer);
+        }
+	
+        CommandHistory::getInstance()->endCompoundOperation();
+    }
+
+    updateMenuStates();
+    m_recentFiles.addFile(model->getLocation());
+    currentPaneChanged(m_paneStack->getCurrentPane());
+}
+
+void
 MainWindowBase::ffwd()
 {
     if (!getMainModel()) return;
@@ -2862,6 +3007,11 @@
 void
 MainWindowBase::stop()
 {
+    if (m_recordTarget->isRecording()) {
+        m_recordTarget->stopRecording();
+        emit audioFileLoaded();
+    }
+        
     m_playSource->stop();
 
     if (m_paneStack && m_paneStack->getCurrentPane()) {
@@ -3332,8 +3482,9 @@
 //    SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl;
     updateDescriptionLabel();
     if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
-    if (model && !m_playTarget && m_audioOutput) {
-        createPlayTarget();
+    if (model && !(m_playTarget || m_audioIO) &&
+        (m_soundOptions & WithAudioOutput)) {
+        createAudioIO();
     }
 }
 
--- a/framework/MainWindowBase.h	Wed Aug 05 17:47:12 2015 +0100
+++ b/framework/MainWindowBase.h	Thu Aug 20 13:15:19 2015 +0100
@@ -46,6 +46,7 @@
 class WaveformLayer;
 class WaveFileModel;
 class AudioCallbackPlaySource;
+class AudioRecordTarget;
 class CommandHistory;
 class QMenu;
 class AudioDial;
@@ -64,6 +65,7 @@
 
 namespace breakfastquay {
 class SystemPlaybackTarget;
+class SystemAudioIO;
 }
 
 /**
@@ -80,7 +82,15 @@
     Q_OBJECT
 
 public:
-    MainWindowBase(bool withAudioOutput, bool withMIDIInput);
+    enum SoundOption {
+        WithAudioOutput = 0x01,
+        WithAudioInput  = 0x02,
+        WithMIDIInput   = 0x04,
+        WithEverything  = 0xff
+    };
+    typedef int SoundOptions;
+    
+    MainWindowBase(SoundOptions options = WithEverything);
     virtual ~MainWindowBase();
     
     enum AudioFileOpenMode {
@@ -149,6 +159,7 @@
     void canZoom(bool);
     void canScroll(bool);
     void canPlay(bool);
+    void canRecord(bool);
     void canFfwd(bool);
     void canRewind(bool);
     void canPlaySelection(bool);
@@ -199,6 +210,7 @@
     virtual void ffwdEnd();
     virtual void rewind();
     virtual void rewindStart();
+    virtual void record();
     virtual void stop();
 
     virtual void ffwdSimilar();
@@ -307,9 +319,12 @@
     ViewManager             *m_viewManager;
     Layer                   *m_timeRulerLayer;
 
-    bool                     m_audioOutput;
+    SoundOptions             m_soundOptions;
+
     AudioCallbackPlaySource *m_playSource;
-    breakfastquay::SystemPlaybackTarget *m_playTarget;
+    AudioRecordTarget       *m_recordTarget;
+    breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this...
+    breakfastquay::SystemAudioIO *m_audioIO;           // ... and this exists
 
     class OSCQueueStarter : public QThread
     {
@@ -424,7 +439,7 @@
     virtual QString getDefaultSessionTemplate() const;
     virtual void setDefaultSessionTemplate(QString);
 
-    virtual void createPlayTarget();
+    virtual void createAudioIO();
     virtual void openHelpUrl(QString url);
 
     virtual void setupMenus() = 0;
--- a/framework/SVFileReader.cpp	Wed Aug 05 17:47:12 2015 +0100
+++ b/framework/SVFileReader.cpp	Thu Aug 20 13:15:19 2015 +0100
@@ -26,7 +26,7 @@
 
 #include "data/fileio/FileFinder.h"
 
-#include "data/model/WaveFileModel.h"
+#include "data/model/ReadOnlyWaveFileModel.h"
 #include "data/model/EditableDenseThreeDimensionalModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/SparseTimeValueModel.h"
@@ -489,7 +489,7 @@
                 if (mm) rate = mm->getSampleRate();
             }
 
-            model = new WaveFileModel(file, rate);
+            model = new ReadOnlyWaveFileModel(file, rate);
             if (!model->isOK()) {
                 delete model;
                 model = 0;
--- a/svapp.pro	Wed Aug 05 17:47:12 2015 +0100
+++ b/svapp.pro	Thu Aug 20 13:15:19 2015 +0100
@@ -41,12 +41,14 @@
 MOC_DIR = o
 
 HEADERS += audio/AudioCallbackPlaySource.h \
+           audio/AudioRecordTarget.h \
            audio/AudioGenerator.h \
            audio/ClipMixer.h \
            audio/ContinuousSynth.h \
            audio/PlaySpeedRangeMapper.h
 
 SOURCES += audio/AudioCallbackPlaySource.cpp \
+           audio/AudioRecordTarget.cpp \
            audio/AudioGenerator.cpp \
            audio/ClipMixer.cpp \
            audio/ContinuousSynth.cpp \