Mercurial > hg > svapp
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 \