changeset 578:702272b78bbe 3.0-integration

Merge
author Chris Cannam
date Wed, 04 Jan 2017 19:10:32 +0000
parents 58354f2934ec (current diff) c2e27ad7f408 (diff)
children 1a8a8980f39a
files audio/AudioRecordTarget.cpp audio/AudioRecordTarget.h framework/MainWindowBase.cpp framework/MainWindowBase.h
diffstat 9 files changed, 503 insertions(+), 338 deletions(-) [+]
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp	Wed Jan 04 18:54:50 2017 +0000
+++ b/audio/AudioCallbackPlaySource.cpp	Wed Jan 04 19:10:32 2017 +0000
@@ -235,6 +235,8 @@
     }
 
     if (!m_writeBuffers || (int)m_writeBuffers->size() < getTargetChannelCount()) {
+        cerr << "m_writeBuffers size = " << (m_writeBuffers ? m_writeBuffers->size() : 0) << endl;
+        cerr << "target channel count = " << (getTargetChannelCount()) << endl;
 	clearRingBuffers(true, getTargetChannelCount());
 	buffersIncreased = true;
     } else {
@@ -270,8 +272,13 @@
     m_audioGenerator->setTargetChannelCount(getTargetChannelCount());
 
     if (buffersIncreased) {
-        SVDEBUG << "AudioCallbackPlaySource::addModel: Number of buffers increased, signalling channelCountIncreased" << endl;
-        emit channelCountIncreased();
+        SVDEBUG << "AudioCallbackPlaySource::addModel: Number of buffers increased to " << getTargetChannelCount() << endl;
+        if (getTargetChannelCount() > getDeviceChannelCount()) {
+            SVDEBUG << "AudioCallbackPlaySource::addModel: This is more than the device channel count, signalling channelCountIncreased" << endl;
+            emit channelCountIncreased(getTargetChannelCount());
+        } else {
+            SVDEBUG << "AudioCallbackPlaySource::addModel: This is no more than the device channel count (" << getDeviceChannelCount() << "), so taking no action" << endl;
+        }
     }
     
     if (!m_fillThread) {
@@ -958,8 +965,8 @@
 void
 AudioCallbackPlaySource::setOutputLevels(float left, float right)
 {
-    m_outputLeft = left;
-    m_outputRight = right;
+    if (left > m_outputLeft) m_outputLeft = left;
+    if (right > m_outputRight) m_outputRight = right;
 }
 
 bool
@@ -967,6 +974,8 @@
 {
     left = m_outputLeft;
     right = m_outputRight;
+    m_outputLeft = 0.f;
+    m_outputRight = 0.f;
     return true;
 }
 
--- a/audio/AudioCallbackPlaySource.h	Wed Jan 04 18:54:50 2017 +0000
+++ b/audio/AudioCallbackPlaySource.h	Wed Jan 04 19:10:32 2017 +0000
@@ -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 SV_AUDIO_CALLBACK_PLAY_SOURCE_H
+#define SV_AUDIO_CALLBACK_PLAY_SOURCE_H
 
 #include "base/RingBuffer.h"
 #include "base/AudioPlaySource.h"
@@ -304,7 +304,7 @@
                             sv_samplerate_t available,
                             bool willResample);
 
-    void channelCountIncreased();
+    void channelCountIncreased(int count); // target channel count (see getTargetChannelCount())
 
     void audioOverloadPluginDisabled();
     void audioTimeStretchMultiChannelDisabled();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioCallbackRecordTarget.cpp	Wed Jan 04 19:10:32 2017 +0000
@@ -0,0 +1,318 @@
+/* -*- 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 "AudioCallbackRecordTarget.h"
+
+#include "base/ViewManagerBase.h"
+#include "base/TempDirectory.h"
+
+#include "data/model/WritableWaveFileModel.h"
+
+#include <QDir>
+#include <QTimer>
+
+AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager,
+                                                     QString clientName) :
+    m_viewManager(manager),
+    m_clientName(clientName.toUtf8().data()),
+    m_recording(false),
+    m_recordSampleRate(44100),
+    m_recordChannelCount(2),
+    m_frameCount(0),
+    m_model(0),
+    m_buffers(0),
+    m_bufferCount(0),
+    m_inputLeft(0.f),
+    m_inputRight(0.f)
+{
+    m_viewManager->setAudioRecordTarget(this);
+
+    connect(this, SIGNAL(recordStatusChanged(bool)),
+            m_viewManager, SLOT(recordStatusChanged(bool)));
+
+    recreateBuffers();
+}
+
+AudioCallbackRecordTarget::~AudioCallbackRecordTarget()
+{
+    m_viewManager->setAudioRecordTarget(0);
+
+    QMutexLocker locker(&m_bufPtrMutex);
+    for (int c = 0; c < m_bufferCount; ++c) {
+        delete m_buffers[c];
+    }
+    delete[] m_buffers;
+}
+
+void
+AudioCallbackRecordTarget::recreateBuffers()
+{
+    static int bufferSize = 441000;
+    
+    int count = m_recordChannelCount;
+
+    if (count > m_bufferCount) {
+
+        RingBuffer<float> **newBuffers = new RingBuffer<float> *[count];
+        for (int c = 0; c < m_bufferCount; ++c) {
+            newBuffers[c] = m_buffers[c];
+        }
+        for (int c = m_bufferCount; c < count; ++c) {
+            newBuffers[c] = new RingBuffer<float>(bufferSize);
+        }
+
+        // This is the only place where m_buffers is rewritten and
+        // should be the only possible source of contention against
+        // putSamples for this mutex (as the model-updating code is
+        // supposed to run in the same thread as this)
+        QMutexLocker locker(&m_bufPtrMutex);
+        delete[] m_buffers;
+        m_buffers = newBuffers;
+        m_bufferCount = count;
+    }
+}    
+    
+int
+AudioCallbackRecordTarget::getApplicationSampleRate() const
+{
+    return 0; // don't care
+}
+
+int
+AudioCallbackRecordTarget::getApplicationChannelCount() const
+{
+    return m_recordChannelCount;
+}
+
+void
+AudioCallbackRecordTarget::setSystemRecordBlockSize(int)
+{
+}
+
+void
+AudioCallbackRecordTarget::setSystemRecordSampleRate(int n)
+{
+    m_recordSampleRate = n;
+}
+
+void
+AudioCallbackRecordTarget::setSystemRecordLatency(int)
+{
+}
+
+void
+AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
+{
+    m_recordChannelCount = c;
+    recreateBuffers();
+}
+
+void
+AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
+{
+    // This may be called from RT context, and in a different thread
+    // from everything else in this class. It takes a mutex that
+    // should almost never be contended (see recreateBuffers())
+    if (!m_recording) return;
+
+    QMutexLocker locker(&m_bufPtrMutex);
+    if (m_buffers && m_bufferCount >= m_recordChannelCount) {
+        for (int c = 0; c < m_recordChannelCount; ++c) {
+            m_buffers[c]->write(samples[c], nframes);
+        }
+    }
+}
+
+void
+AudioCallbackRecordTarget::updateModel()
+{
+    bool secChanged = false;
+    sv_frame_t frameToEmit = 0;
+
+    int nframes = 0;
+    for (int c = 0; c < m_recordChannelCount; ++c) {
+        if (c == 0 || m_buffers[c]->getReadSpace() < nframes) {
+            nframes = m_buffers[c]->getReadSpace();
+        }
+    }
+
+    if (nframes == 0) {
+        return;
+    }
+
+    float **samples = new float *[m_recordChannelCount];
+    for (int c = 0; c < m_recordChannelCount; ++c) {
+        samples[c] = new float[nframes];
+        m_buffers[c]->read(samples[c], nframes);
+    }
+
+    m_model->addSamples(samples, nframes);
+
+    for (int c = 0; c < m_recordChannelCount; ++c) {
+        delete[] samples[c];
+    }
+    delete[] samples;
+    
+    sv_frame_t priorFrameCount = m_frameCount;
+    m_frameCount += nframes;
+
+    RealTime priorRT =
+        RealTime::frame2RealTime(priorFrameCount, m_recordSampleRate);
+    
+    RealTime postRT =
+        RealTime::frame2RealTime(m_frameCount, m_recordSampleRate);
+
+    secChanged = (postRT.sec > priorRT.sec);
+    if (secChanged) {
+        m_model->updateModel();
+        frameToEmit = m_frameCount;
+    }
+
+    if (secChanged) {
+        emit recordDurationChanged(frameToEmit, m_recordSampleRate);
+    }
+
+    if (m_recording) {
+        QTimer::singleShot(1000, this, SLOT(updateModel()));
+    }    
+}
+
+void
+AudioCallbackRecordTarget::setInputLevels(float left, float right)
+{
+    if (left > m_inputLeft) m_inputLeft = left;
+    if (right > m_inputRight) m_inputRight = right;
+}
+
+bool
+AudioCallbackRecordTarget::getInputLevels(float &left, float &right)
+{
+    left = m_inputLeft;
+    right = m_inputRight;
+    m_inputLeft = 0.f;
+    m_inputRight = 0.f;
+    return true;
+}
+
+void
+AudioCallbackRecordTarget::modelAboutToBeDeleted()
+{
+    if (sender() == m_model) {
+        m_model = 0;
+        m_recording = false;
+    }
+}
+
+QString
+AudioCallbackRecordTarget::getRecordContainerFolder()
+{
+    QDir parent(TempDirectory::getInstance()->getContainingPath());
+    QString subdirname("recorded");
+
+    if (!parent.mkpath(subdirname)) {
+        SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+QString
+AudioCallbackRecordTarget::getRecordFolder()
+{
+    QDir parent(getRecordContainerFolder());
+    QDateTime now = QDateTime::currentDateTime();
+    QString subdirname = QString("%1").arg(now.toString("yyyyMMdd"));
+
+    if (!parent.mkpath(subdirname)) {
+        SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
+        return "";
+    } else {
+        return parent.filePath(subdirname);
+    }
+}
+
+WritableWaveFileModel *
+AudioCallbackRecordTarget::startRecording()
+{
+    if (m_recording) {
+        SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
+        return 0;
+    }
+
+    m_model = 0;
+    m_frameCount = 0;
+
+    QString folder = getRecordFolder();
+    if (folder == "") return 0;
+    QDir recordedDir(folder);
+
+    QDateTime now = QDateTime::currentDateTime();
+
+    // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
+    // isn't permitted in filenames on Windows
+    QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
+    
+    QString filename = tr("recorded-%1.wav").arg(nowString);
+    QString label = tr("Recorded %1").arg(nowString);
+
+    m_audioFileName = recordedDir.filePath(filename);
+
+    m_model = new WritableWaveFileModel(m_recordSampleRate,
+                                        m_recordChannelCount,
+                                        m_audioFileName);
+
+    if (!m_model->isOK()) {
+        SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
+               << endl;
+        //!!! and throw?
+        delete m_model;
+        m_model = 0;
+        return 0;
+    }
+
+    m_model->setObjectName(label);
+    m_recording = true;
+
+    emit recordStatusChanged(true);
+
+    QTimer::singleShot(1000, this, SLOT(updateModel()));
+    
+    return m_model;
+}
+
+void
+AudioCallbackRecordTarget::stopRecording()
+{
+    if (!m_recording) {
+        SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl;
+        return;
+    }
+
+    m_recording = false;
+
+    m_bufPtrMutex.lock();
+    m_bufPtrMutex.unlock();
+
+    // buffers should now be up-to-date
+    updateModel();
+
+    m_model->writeComplete();
+    m_model = 0;
+    
+    emit recordStatusChanged(false);
+    emit recordCompleted();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioCallbackRecordTarget.h	Wed Jan 04 19:10:32 2017 +0000
@@ -0,0 +1,98 @@
+/* -*- 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 SV_AUDIO_CALLBACK_RECORD_TARGET_H
+#define SV_AUDIO_CALLBACK_RECORD_TARGET_H
+
+#include "base/AudioRecordTarget.h"
+
+#include <bqaudioio/ApplicationRecordTarget.h>
+
+#include <string>
+#include <atomic>
+
+#include <QObject>
+#include <QMutex>
+
+#include "base/BaseTypes.h"
+#include "base/RingBuffer.h"
+
+class ViewManagerBase;
+class WritableWaveFileModel;
+
+class AudioCallbackRecordTarget : public QObject,
+                                  public AudioRecordTarget,
+                                  public breakfastquay::ApplicationRecordTarget
+{
+    Q_OBJECT
+
+public:
+    AudioCallbackRecordTarget(ViewManagerBase *, QString clientName);
+    virtual ~AudioCallbackRecordTarget();
+
+    virtual std::string getClientName() const override { return m_clientName; }
+    
+    virtual int getApplicationSampleRate() const override;
+    virtual int getApplicationChannelCount() const override;
+
+    virtual void setSystemRecordBlockSize(int) override;
+    virtual void setSystemRecordSampleRate(int) override;
+    virtual void setSystemRecordLatency(int) override;
+    virtual void setSystemRecordChannelCount(int) override;
+
+    virtual void putSamples(const float *const *samples, int nchannels, int nframes) override;
+    
+    virtual void setInputLevels(float peakLeft, float peakRight) override;
+
+    virtual void audioProcessingOverload() override { }
+
+    QString getRecordContainerFolder();
+    QString getRecordFolder();
+    
+    virtual bool isRecording() const override { return m_recording; }
+    virtual sv_frame_t getRecordDuration() const override { return m_frameCount; }
+
+    virtual bool getInputLevels(float &left, float &right) override;
+
+    WritableWaveFileModel *startRecording(); // caller takes ownership of model
+    void stopRecording();
+
+signals:
+    void recordStatusChanged(bool recording);
+    void recordDurationChanged(sv_frame_t, sv_samplerate_t); // emitted occasionally
+    void recordCompleted();
+
+protected slots:
+    void modelAboutToBeDeleted();
+    void updateModel();
+    
+private:
+    ViewManagerBase *m_viewManager;
+    std::string m_clientName;
+    std::atomic_bool m_recording;
+    sv_samplerate_t m_recordSampleRate;
+    int m_recordChannelCount;
+    sv_frame_t m_frameCount;
+    QString m_audioFileName;
+    WritableWaveFileModel *m_model;
+    RingBuffer<float> **m_buffers;
+    QMutex m_bufPtrMutex;
+    int m_bufferCount;
+    float m_inputLeft;
+    float m_inputRight;
+
+    void recreateBuffers();
+};
+
+#endif
--- a/audio/AudioRecordTarget.cpp	Wed Jan 04 18:54:50 2017 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-/* -*- 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_recordChannelCount(2),
-    m_frameCount(0),
-    m_model(0)
-{
-}
-
-AudioRecordTarget::~AudioRecordTarget()
-{
-    QMutexLocker locker(&m_mutex);
-}
-
-int
-AudioRecordTarget::getApplicationSampleRate() const
-{
-    return 0; // don't care
-}
-
-int
-AudioRecordTarget::getApplicationChannelCount() const
-{
-    return m_recordChannelCount;
-}
-
-void
-AudioRecordTarget::setSystemRecordBlockSize(int)
-{
-}
-
-void
-AudioRecordTarget::setSystemRecordSampleRate(int n)
-{
-    m_recordSampleRate = n;
-}
-
-void
-AudioRecordTarget::setSystemRecordLatency(int)
-{
-}
-
-void
-AudioRecordTarget::setSystemRecordChannelCount(int c)
-{
-    m_recordChannelCount = c;
-}
-
-void
-AudioRecordTarget::putSamples(const float *const *samples, int, int nframes)
-{
-    bool secChanged = false;
-    sv_frame_t frameToEmit = 0;
-
-    {
-        QMutexLocker locker(&m_mutex); //!!! bad here
-        if (!m_recording) return;
-
-        m_model->addSamples(samples, nframes);
-
-        sv_frame_t priorFrameCount = m_frameCount;
-        m_frameCount += nframes;
-
-        RealTime priorRT = RealTime::frame2RealTime
-            (priorFrameCount, m_recordSampleRate);
-        RealTime postRT = RealTime::frame2RealTime
-            (m_frameCount, m_recordSampleRate);
-
-        secChanged = (postRT.sec > priorRT.sec);
-        if (secChanged) frameToEmit = m_frameCount;
-    }
-
-    if (secChanged) {
-        emit recordDurationChanged(frameToEmit, m_recordSampleRate);
-    }
-}
-
-void
-AudioRecordTarget::setInputLevels(float, float)
-{
-}
-
-void
-AudioRecordTarget::modelAboutToBeDeleted()
-{
-    QMutexLocker locker(&m_mutex);
-    if (sender() == m_model) {
-        m_model = 0;
-        m_recording = false;
-    }
-}
-
-QString
-AudioRecordTarget::getRecordContainerFolder()
-{
-    QDir parent(TempDirectory::getInstance()->getContainingPath());
-    QString subdirname("recorded");
-
-    if (!parent.mkpath(subdirname)) {
-        cerr << "ERROR: AudioRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
-        return "";
-    } else {
-        return parent.filePath(subdirname);
-    }
-}
-
-QString
-AudioRecordTarget::getRecordFolder()
-{
-    QDir parent(getRecordContainerFolder());
-    QDateTime now = QDateTime::currentDateTime();
-    QString subdirname = QString("%1").arg(now.toString("yyyyMMdd"));
-
-    if (!parent.mkpath(subdirname)) {
-        cerr << "ERROR: AudioRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
-        return "";
-    } else {
-        return parent.filePath(subdirname);
-    }
-}
-
-WritableWaveFileModel *
-AudioRecordTarget::startRecording()
-{
-    {
-    QMutexLocker locker(&m_mutex);
-    
-    if (m_recording) {
-        cerr << "WARNING: AudioRecordTarget::startRecording: We are already recording" << endl;
-        return 0;
-    }
-
-    m_model = 0;
-    m_frameCount = 0;
-
-    QString folder = getRecordFolder();
-    if (folder == "") return 0;
-    QDir recordedDir(folder);
-
-    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,
-                                        m_recordChannelCount,
-                                        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->writeComplete();
-    m_model = 0;
-    m_recording = false;
-    }
-
-    emit recordStatusChanged(false);
-    emit recordCompleted();
-}
-
-
--- a/audio/AudioRecordTarget.h	Wed Jan 04 18:54:50 2017 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/* -*- 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 override { return m_clientName; }
-    
-    virtual int getApplicationSampleRate() const override;
-    virtual int getApplicationChannelCount() const override;
-
-    virtual void setSystemRecordBlockSize(int) override;
-    virtual void setSystemRecordSampleRate(int) override;
-    virtual void setSystemRecordLatency(int) override;
-    virtual void setSystemRecordChannelCount(int) override;
-
-    virtual void putSamples(const float *const *samples, int nchannels, int nframes) override;
-    
-    virtual void setInputLevels(float peakLeft, float peakRight) override;
-
-    virtual void audioProcessingOverload() override { }
-
-    QString getRecordContainerFolder();
-    QString getRecordFolder();
-    
-    bool isRecording() const { return m_recording; }
-    WritableWaveFileModel *startRecording(); // caller takes ownership
-    void stopRecording();
-
-signals:
-    void recordStatusChanged(bool recording);
-    void recordDurationChanged(sv_frame_t, sv_samplerate_t); // emitted occasionally
-    void recordCompleted();
-
-protected slots:
-    void modelAboutToBeDeleted();
-    
-private:
-    ViewManagerBase *m_viewManager;
-    std::string m_clientName;
-    bool m_recording;
-    sv_samplerate_t m_recordSampleRate;
-    int m_recordChannelCount;
-    sv_frame_t m_frameCount;
-    QString m_audioFileName;
-    WritableWaveFileModel *m_model;
-    QMutex m_mutex;
-};
-
-#endif
--- a/files.pri	Wed Jan 04 18:54:50 2017 +0000
+++ b/files.pri	Wed Jan 04 19:10:32 2017 +0000
@@ -1,7 +1,7 @@
 
 SVAPP_HEADERS += \
            audio/AudioCallbackPlaySource.h \
-           audio/AudioRecordTarget.h \
+           audio/AudioCallbackRecordTarget.h \
            audio/AudioGenerator.h \
            audio/ClipMixer.h \
            audio/ContinuousSynth.h \
@@ -15,7 +15,7 @@
 
 SVAPP_SOURCES += \
            audio/AudioCallbackPlaySource.cpp \
-           audio/AudioRecordTarget.cpp \
+           audio/AudioCallbackRecordTarget.cpp \
            audio/AudioGenerator.cpp \
            audio/ClipMixer.cpp \
            audio/ContinuousSynth.cpp \
--- a/framework/MainWindowBase.cpp	Wed Jan 04 18:54:50 2017 +0000
+++ b/framework/MainWindowBase.cpp	Wed Jan 04 19:10:32 2017 +0000
@@ -48,7 +48,7 @@
 #include "widgets/InteractiveFileFinder.h"
 
 #include "audio/AudioCallbackPlaySource.h"
-#include "audio/AudioRecordTarget.h"
+#include "audio/AudioCallbackRecordTarget.h"
 #include "audio/PlaySpeedRangeMapper.h"
 
 #include "data/fileio/DataFileReaderFactory.h"
@@ -230,27 +230,30 @@
             this, SLOT(paneDropAccepted(Pane *, QString)));
     connect(m_paneStack, SIGNAL(paneDeleteButtonClicked(Pane *)),
             this, SLOT(paneDeleteButtonClicked(Pane *)));
-
-    m_playSource = new AudioCallbackPlaySource(m_viewManager,
-                                               QApplication::applicationName());
+    
+    m_playSource = new AudioCallbackPlaySource
+        (m_viewManager, QApplication::applicationName());
+
     if (m_soundOptions & WithAudioInput) {
-        m_recordTarget = new AudioRecordTarget(m_viewManager,
-                                               QApplication::applicationName());
-        connect(m_recordTarget, SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)),
-                this, SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t)));
+        m_recordTarget = new AudioCallbackRecordTarget
+            (m_viewManager, QApplication::applicationName());
+        connect(m_recordTarget,
+                SIGNAL(recordDurationChanged(sv_frame_t, sv_samplerate_t)),
+                this,
+                SLOT(recordDurationChanged(sv_frame_t, sv_samplerate_t)));
     }
 
     connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)),
 	    this,           SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)));
-    connect(m_playSource, SIGNAL(channelCountIncreased()),
-            this,           SLOT(recreateAudioIO()));
+    connect(m_playSource, SIGNAL(channelCountIncreased(int)),
+            this,           SLOT(audioChannelCountIncreased(int)));
     connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()),
             this,           SLOT(audioOverloadPluginDisabled()));
     connect(m_playSource, SIGNAL(audioTimeStretchMultiChannelDisabled()),
             this,           SLOT(audioTimeStretchMultiChannelDisabled()));
 
-    connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)),
-	    this, SLOT(outputLevelsChanged(float, float)));
+    connect(m_viewManager, SIGNAL(monitoringLevelsChanged(float, float)),
+	    this, SLOT(monitoringLevelsChanged(float, float)));
 
     connect(m_viewManager, SIGNAL(playbackFrameChanged(sv_frame_t)),
             this, SLOT(playbackFrameChanged(sv_frame_t)));
@@ -480,7 +483,7 @@
         QTimer *oscTimer = new QTimer(this);
         connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC()));
         oscTimer->start(1000);
-        cerr << "Finished setting up OSC interface" << endl;
+        SVCERR << "Finished setting up OSC interface" << endl;
     }
 }
 
@@ -2343,17 +2346,21 @@
         m_resamplerWrapper = new breakfastquay::ResamplerWrapper(m_playSource);
         m_playSource->setResamplerWrapper(m_resamplerWrapper);
     }
+
+    std::string errorString;
     
     if (m_soundOptions & WithAudioInput) {
         m_audioIO = breakfastquay::AudioFactory::
-            createCallbackIO(m_recordTarget, m_resamplerWrapper, preference);
+            createCallbackIO(m_recordTarget, m_resamplerWrapper,
+                             preference, errorString);
         if (m_audioIO) {
             m_audioIO->suspend(); // start in suspended state
             m_playSource->setSystemPlaybackTarget(m_audioIO);
         }
     } else {
         m_playTarget = breakfastquay::AudioFactory::
-            createCallbackPlayTarget(m_resamplerWrapper, preference);
+            createCallbackPlayTarget(m_resamplerWrapper,
+                                     preference, errorString);
         if (m_playTarget) {
             m_playTarget->suspend(); // start in suspended state
             m_playSource->setSystemPlaybackTarget(m_playTarget);
@@ -2362,20 +2369,39 @@
 
     if (!m_playTarget && !m_audioIO) {
         emit hideSplash();
+        QString message;
+        QString error = errorString.c_str();
+        QString firstBit, secondBit;
         if (implementation == "") {
-            QMessageBox::warning
-	    (this, tr("Couldn't open audio device"),
-	     tr("<b>No audio available</b><p>Could not open an audio device for playback.<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>"),
-	     QMessageBox::Ok);
+            if (error == "") {
+                firstBit = tr("<b>No audio available</b><p>Could not open an audio device.</p>");
+            } else {
+                firstBit = tr("<b>No audio available</b><p>Could not open audio device: %1</p>").arg(error);
+            }
+            if (m_soundOptions & WithAudioInput) {
+                secondBit = tr("<p>Automatic audio device detection failed. Audio playback and recording will not be available during this session.</p>");
+            } else {
+                secondBit = tr("<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>");
+            }
         } else {
-            QMessageBox::warning
-                (this, tr("Couldn't open audio device"),
-                 tr("<b>No audio available</b><p>Failed to open your preferred audio device (\"%1\").<p>Audio playback will not be available during this session.</p>")
-                 .arg(breakfastquay::AudioFactory::
-                      getImplementationDescription(implementation.toStdString())
-                      .c_str()),
-                 QMessageBox::Ok);
+            QString driverName = breakfastquay::AudioFactory::
+                getImplementationDescription(implementation.toStdString())
+                .c_str();
+            if (error == "") {
+                firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\").</p>").arg(driverName);
+            } else {
+                firstBit = tr("<b>No audio available</b><p>Failed to open your preferred audio driver (\"%1\"): %2.</p>").arg(driverName).arg(error);
+            }
+            if (m_soundOptions & WithAudioInput) {
+                secondBit = tr("<p>Audio playback and recording will not be available during this session.</p>");
+            } else {
+                secondBit = tr("<p>Audio playback will not be available during this session.</p>");
+            }
         }
+        SVDEBUG << "createAudioIO: ERROR: Failed to open audio device \""
+                << implementation << "\": error is: " << error << endl;
+        QMessageBox::warning(this, tr("Couldn't open audio device"),
+                             firstBit + secondBit, QMessageBox::Ok);
     }
 }
 
@@ -2410,6 +2436,12 @@
     createAudioIO();
 }
 
+void
+MainWindowBase::audioChannelCountIncreased(int)
+{
+    recreateAudioIO();
+}
+
 WaveFileModel *
 MainWindowBase::getMainModel()
 {
@@ -2886,11 +2918,12 @@
     }
 
     if (!m_audioIO) {
+        cerr << "MainWindowBase::record: about to create audio IO" << endl;
         createAudioIO();
     }
 
     if (!m_audioIO) {
-        //!!! report
+        // don't need to report this, createAudioIO already should have
         return;
     }
     
@@ -2910,6 +2943,7 @@
 
     if (m_viewManager) m_viewManager->setGlobalCentreFrame(0);
     
+    cerr << "MainWindowBase::record: about to resume" << endl;
     m_audioIO->resume();
 
     WritableWaveFileModel *model = m_recordTarget->startRecording();
--- a/framework/MainWindowBase.h	Wed Jan 04 18:54:50 2017 +0000
+++ b/framework/MainWindowBase.h	Wed Jan 04 19:10:32 2017 +0000
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MAIN_WINDOW_BASE_H_
-#define _MAIN_WINDOW_BASE_H_
+#ifndef SV_MAIN_WINDOW_BASE_H
+#define SV_MAIN_WINDOW_BASE_H
 
 #include <QFrame>
 #include <QString>
@@ -46,7 +46,7 @@
 class WaveformLayer;
 class WaveFileModel;
 class AudioCallbackPlaySource;
-class AudioRecordTarget;
+class AudioCallbackRecordTarget;
 class CommandHistory;
 class QMenu;
 class AudioDial;
@@ -248,6 +248,8 @@
     virtual void playSelectionToggled();
     virtual void playSoloToggled();
 
+    virtual void audioChannelCountIncreased(int count);
+
     virtual void sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool) = 0;
     virtual void audioOverloadPluginDisabled() = 0;
     virtual void audioTimeStretchMultiChannelDisabled() = 0;
@@ -256,7 +258,7 @@
     virtual void globalCentreFrameChanged(sv_frame_t);
     virtual void viewCentreFrameChanged(View *, sv_frame_t);
     virtual void viewZoomLevelChanged(View *, int, bool);
-    virtual void outputLevelsChanged(float, float) = 0;
+    virtual void monitoringLevelsChanged(float, float) = 0;
     virtual void recordDurationChanged(sv_frame_t, sv_samplerate_t);
 
     virtual void currentPaneChanged(Pane *);
@@ -346,7 +348,7 @@
     SoundOptions             m_soundOptions;
 
     AudioCallbackPlaySource *m_playSource;
-    AudioRecordTarget       *m_recordTarget;
+    AudioCallbackRecordTarget *m_recordTarget;
     breakfastquay::ResamplerWrapper *m_resamplerWrapper;
     breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this...
     breakfastquay::SystemAudioIO *m_audioIO;           // ... and this exists
@@ -386,7 +388,7 @@
     RealTime                 m_defaultFfwdRwdStep;
 
     AudioRecordMode          m_audioRecordMode;
-
+    
     mutable QLabel *m_statusLabel;
     QLabel *getStatusLabel() const;