Mercurial > hg > svapp
view audio/AudioCallbackRecordTarget.cpp @ 588:d122d3595a32
Store aggregate models in the document and release them when they are invalidated (because their components have been released). They're no longer leaked, but we still don't save them in the session file.
author | Chris Cannam |
---|---|
date | Mon, 27 Feb 2017 16:26:37 +0000 |
parents | 8cc291b13f2b |
children | 96b605673585 |
line wrap: on
line source
/* -*- 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(); }