Chris@476: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@476: Chris@476: /* Chris@476: Sonic Visualiser Chris@476: An audio file viewer and annotation editor. Chris@476: Centre for Digital Music, Queen Mary, University of London. Chris@476: Chris@476: This program is free software; you can redistribute it and/or Chris@476: modify it under the terms of the GNU General Public License as Chris@476: published by the Free Software Foundation; either version 2 of the Chris@476: License, or (at your option) any later version. See the file Chris@476: COPYING included with this distribution for more information. Chris@476: */ Chris@476: Chris@574: #include "AudioCallbackRecordTarget.h" Chris@476: Chris@476: #include "base/ViewManagerBase.h" Chris@476: #include "base/TempDirectory.h" Chris@476: Chris@477: #include "data/model/WritableWaveFileModel.h" Chris@476: Chris@476: #include Chris@575: #include Chris@476: Chris@574: AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager, Chris@574: QString clientName) : Chris@476: m_viewManager(manager), Chris@476: m_clientName(clientName.toUtf8().data()), Chris@476: m_recording(false), Chris@476: m_recordSampleRate(44100), Chris@546: m_recordChannelCount(2), Chris@485: m_frameCount(0), Chris@574: m_model(0), Chris@575: m_buffers(0), Chris@575: m_bufferCount(0), Chris@574: m_inputLeft(0.f), Chris@580: m_inputRight(0.f), Chris@580: m_levelsSet(false) Chris@476: { Chris@574: m_viewManager->setAudioRecordTarget(this); Chris@574: Chris@574: connect(this, SIGNAL(recordStatusChanged(bool)), Chris@574: m_viewManager, SLOT(recordStatusChanged(bool))); Chris@575: Chris@575: recreateBuffers(); Chris@476: } Chris@476: Chris@574: AudioCallbackRecordTarget::~AudioCallbackRecordTarget() Chris@476: { Chris@574: m_viewManager->setAudioRecordTarget(0); Chris@575: Chris@575: QMutexLocker locker(&m_bufPtrMutex); Chris@575: for (int c = 0; c < m_bufferCount; ++c) { Chris@575: delete m_buffers[c]; Chris@575: } Chris@575: delete[] m_buffers; Chris@476: } Chris@476: Chris@575: void Chris@575: AudioCallbackRecordTarget::recreateBuffers() Chris@575: { Chris@575: static int bufferSize = 441000; Chris@575: Chris@575: int count = m_recordChannelCount; Chris@575: Chris@575: if (count > m_bufferCount) { Chris@575: Chris@575: RingBuffer **newBuffers = new RingBuffer *[count]; Chris@575: for (int c = 0; c < m_bufferCount; ++c) { Chris@575: newBuffers[c] = m_buffers[c]; Chris@575: } Chris@575: for (int c = m_bufferCount; c < count; ++c) { Chris@575: newBuffers[c] = new RingBuffer(bufferSize); Chris@575: } Chris@575: Chris@575: // This is the only place where m_buffers is rewritten and Chris@575: // should be the only possible source of contention against Chris@575: // putSamples for this mutex (as the model-updating code is Chris@575: // supposed to run in the same thread as this) Chris@575: QMutexLocker locker(&m_bufPtrMutex); Chris@575: delete[] m_buffers; Chris@575: m_buffers = newBuffers; Chris@575: m_bufferCount = count; Chris@575: } Chris@575: } Chris@575: Chris@559: int Chris@574: AudioCallbackRecordTarget::getApplicationSampleRate() const Chris@559: { Chris@559: return 0; // don't care Chris@559: } Chris@559: Chris@559: int Chris@574: AudioCallbackRecordTarget::getApplicationChannelCount() const Chris@559: { Chris@559: return m_recordChannelCount; Chris@559: } Chris@559: Chris@476: void Chris@574: AudioCallbackRecordTarget::setSystemRecordBlockSize(int) Chris@476: { Chris@476: } Chris@476: Chris@476: void Chris@574: AudioCallbackRecordTarget::setSystemRecordSampleRate(int n) Chris@476: { Chris@476: m_recordSampleRate = n; Chris@476: } Chris@476: Chris@476: void Chris@574: AudioCallbackRecordTarget::setSystemRecordLatency(int) Chris@476: { Chris@476: } Chris@476: Chris@476: void Chris@574: AudioCallbackRecordTarget::setSystemRecordChannelCount(int c) Chris@546: { Chris@546: m_recordChannelCount = c; Chris@575: recreateBuffers(); Chris@546: } Chris@546: Chris@546: void Chris@574: AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes) Chris@476: { Chris@575: // This may be called from RT context, and in a different thread Chris@575: // from everything else in this class. It takes a mutex that Chris@575: // should almost never be contended (see recreateBuffers()) Chris@575: if (!m_recording) return; Chris@575: Chris@575: QMutexLocker locker(&m_bufPtrMutex); Chris@575: if (m_buffers && m_bufferCount >= m_recordChannelCount) { Chris@575: for (int c = 0; c < m_recordChannelCount; ++c) { Chris@575: m_buffers[c]->write(samples[c], nframes); Chris@575: } Chris@575: } Chris@575: } Chris@575: Chris@575: void Chris@575: AudioCallbackRecordTarget::updateModel() Chris@575: { Chris@485: bool secChanged = false; Chris@485: sv_frame_t frameToEmit = 0; Chris@485: Chris@575: int nframes = 0; Chris@575: for (int c = 0; c < m_recordChannelCount; ++c) { Chris@575: if (c == 0 || m_buffers[c]->getReadSpace() < nframes) { Chris@575: nframes = m_buffers[c]->getReadSpace(); Chris@575: } Chris@575: } Chris@485: Chris@575: if (nframes == 0) { Chris@575: return; Chris@575: } Chris@485: Chris@575: float **samples = new float *[m_recordChannelCount]; Chris@575: for (int c = 0; c < m_recordChannelCount; ++c) { Chris@575: samples[c] = new float[nframes]; Chris@575: m_buffers[c]->read(samples[c], nframes); Chris@575: } Chris@485: Chris@575: m_model->addSamples(samples, nframes); Chris@485: Chris@575: for (int c = 0; c < m_recordChannelCount; ++c) { Chris@575: delete[] samples[c]; Chris@575: } Chris@575: delete[] samples; Chris@575: Chris@575: sv_frame_t priorFrameCount = m_frameCount; Chris@575: m_frameCount += nframes; Chris@575: Chris@575: RealTime priorRT = Chris@575: RealTime::frame2RealTime(priorFrameCount, m_recordSampleRate); Chris@575: Chris@575: RealTime postRT = Chris@575: RealTime::frame2RealTime(m_frameCount, m_recordSampleRate); Chris@575: Chris@575: secChanged = (postRT.sec > priorRT.sec); Chris@575: if (secChanged) { Chris@575: m_model->updateModel(); Chris@575: frameToEmit = m_frameCount; Chris@485: } Chris@485: Chris@485: if (secChanged) { Chris@485: emit recordDurationChanged(frameToEmit, m_recordSampleRate); Chris@485: } Chris@575: Chris@575: if (m_recording) { Chris@575: QTimer::singleShot(1000, this, SLOT(updateModel())); Chris@575: } Chris@476: } Chris@476: Chris@476: void Chris@574: AudioCallbackRecordTarget::setInputLevels(float left, float right) Chris@476: { Chris@574: if (left > m_inputLeft) m_inputLeft = left; Chris@574: if (right > m_inputRight) m_inputRight = right; Chris@580: m_levelsSet = true; Chris@574: } Chris@574: Chris@574: bool Chris@574: AudioCallbackRecordTarget::getInputLevels(float &left, float &right) Chris@574: { Chris@574: left = m_inputLeft; Chris@574: right = m_inputRight; Chris@581: bool valid = m_levelsSet; Chris@574: m_inputLeft = 0.f; Chris@574: m_inputRight = 0.f; Chris@581: m_levelsSet = false; Chris@581: return valid; Chris@476: } Chris@476: Chris@477: void Chris@574: AudioCallbackRecordTarget::modelAboutToBeDeleted() Chris@477: { Chris@477: if (sender() == m_model) { Chris@477: m_model = 0; Chris@477: m_recording = false; Chris@477: } Chris@477: } Chris@477: Chris@483: QString Chris@574: AudioCallbackRecordTarget::getRecordContainerFolder() Chris@508: { Chris@508: QDir parent(TempDirectory::getInstance()->getContainingPath()); Chris@508: QString subdirname("recorded"); Chris@508: Chris@508: if (!parent.mkpath(subdirname)) { Chris@574: SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl; Chris@508: return ""; Chris@508: } else { Chris@508: return parent.filePath(subdirname); Chris@508: } Chris@508: } Chris@508: Chris@508: QString Chris@574: AudioCallbackRecordTarget::getRecordFolder() Chris@483: { Chris@508: QDir parent(getRecordContainerFolder()); Chris@508: QDateTime now = QDateTime::currentDateTime(); Chris@508: QString subdirname = QString("%1").arg(now.toString("yyyyMMdd")); Chris@508: Chris@483: if (!parent.mkpath(subdirname)) { Chris@574: SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl; Chris@483: return ""; Chris@483: } else { Chris@483: return parent.filePath(subdirname); Chris@483: } Chris@483: } Chris@483: Chris@477: WritableWaveFileModel * Chris@574: AudioCallbackRecordTarget::startRecording() Chris@476: { Chris@575: if (m_recording) { Chris@575: SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl; Chris@575: return 0; Chris@477: } Chris@477: Chris@575: m_model = 0; Chris@575: m_frameCount = 0; Chris@575: Chris@575: QString folder = getRecordFolder(); Chris@575: if (folder == "") return 0; Chris@575: QDir recordedDir(folder); Chris@575: Chris@575: QDateTime now = QDateTime::currentDateTime(); Chris@575: Chris@575: // Don't use QDateTime::toString(Qt::ISODate) as the ":" character Chris@575: // isn't permitted in filenames on Windows Chris@575: QString nowString = now.toString("yyyyMMdd-HHmmss-zzz"); Chris@575: Chris@575: QString filename = tr("recorded-%1.wav").arg(nowString); Chris@575: QString label = tr("Recorded %1").arg(nowString); Chris@575: Chris@575: m_audioFileName = recordedDir.filePath(filename); Chris@575: Chris@575: m_model = new WritableWaveFileModel(m_recordSampleRate, Chris@575: m_recordChannelCount, Chris@575: m_audioFileName); Chris@575: Chris@575: if (!m_model->isOK()) { Chris@575: SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed" Chris@575: << endl; Chris@575: //!!! and throw? Chris@575: delete m_model; Chris@575: m_model = 0; Chris@575: return 0; Chris@575: } Chris@575: Chris@575: m_model->setObjectName(label); Chris@575: m_recording = true; Chris@575: Chris@477: emit recordStatusChanged(true); Chris@575: Chris@575: QTimer::singleShot(1000, this, SLOT(updateModel())); Chris@575: Chris@477: return m_model; Chris@476: } Chris@476: Chris@476: void Chris@574: AudioCallbackRecordTarget::stopRecording() Chris@476: { Chris@575: if (!m_recording) { Chris@575: SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl; Chris@575: return; Chris@477: } Chris@477: Chris@575: m_recording = false; Chris@575: Chris@575: m_bufPtrMutex.lock(); Chris@575: m_bufPtrMutex.unlock(); Chris@575: Chris@575: // buffers should now be up-to-date Chris@575: updateModel(); Chris@575: Chris@575: m_model->writeComplete(); Chris@575: m_model = 0; Chris@575: Chris@477: emit recordStatusChanged(false); Chris@497: emit recordCompleted(); Chris@476: } Chris@476: Chris@476: