diff audio/AudioCallbackRecordTarget.cpp @ 582:b2d49e7c4149

Merge from branch 3.0-integration
author Chris Cannam
date Fri, 13 Jan 2017 10:29:55 +0000
parents 8cc291b13f2b
children 96b605673585
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audio/AudioCallbackRecordTarget.cpp	Fri Jan 13 10:29:55 2017 +0000
@@ -0,0 +1,322 @@
+/* -*- 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_levelsSet(false)
+{
+    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;
+    m_levelsSet = true;
+}
+
+bool
+AudioCallbackRecordTarget::getInputLevels(float &left, float &right)
+{
+    left = m_inputLeft;
+    right = m_inputRight;
+    bool valid = m_levelsSet;
+    m_inputLeft = 0.f;
+    m_inputRight = 0.f;
+    m_levelsSet = false;
+    return valid;
+}
+
+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();
+}
+
+