annotate audio/AudioCallbackRecordTarget.cpp @ 775:56a81812c131 smoother-recording

Use ModelId for recording model; add mix-to-mono option
author Chris Cannam
date Tue, 16 Jun 2020 15:17:50 +0100
parents aee03ad6d3f6
children
rev   line source
Chris@476 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@476 2
Chris@476 3 /*
Chris@476 4 Sonic Visualiser
Chris@476 5 An audio file viewer and annotation editor.
Chris@476 6 Centre for Digital Music, Queen Mary, University of London.
Chris@476 7
Chris@476 8 This program is free software; you can redistribute it and/or
Chris@476 9 modify it under the terms of the GNU General Public License as
Chris@476 10 published by the Free Software Foundation; either version 2 of the
Chris@476 11 License, or (at your option) any later version. See the file
Chris@476 12 COPYING included with this distribution for more information.
Chris@476 13 */
Chris@476 14
Chris@574 15 #include "AudioCallbackRecordTarget.h"
Chris@476 16
Chris@476 17 #include "base/ViewManagerBase.h"
Chris@620 18 #include "base/RecordDirectory.h"
Chris@641 19 #include "base/Debug.h"
Chris@775 20 #include "base/Preferences.h"
Chris@476 21
Chris@477 22 #include "data/model/WritableWaveFileModel.h"
Chris@476 23
Chris@476 24 #include <QDir>
Chris@575 25 #include <QTimer>
Chris@711 26 #include <QDateTime>
Chris@476 27
Chris@609 28 //#define DEBUG_AUDIO_CALLBACK_RECORD_TARGET 1
Chris@609 29
Chris@609 30 static const int recordUpdateTimeout = 200; // ms
Chris@609 31
Chris@574 32 AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager,
Chris@574 33 QString clientName) :
Chris@476 34 m_viewManager(manager),
Chris@476 35 m_clientName(clientName.toUtf8().data()),
Chris@476 36 m_recording(false),
Chris@476 37 m_recordSampleRate(44100),
Chris@775 38 m_systemRecordChannelCount(2),
Chris@775 39 m_recordMono(false),
Chris@485 40 m_frameCount(0),
Chris@636 41 m_buffers(nullptr),
Chris@575 42 m_bufferCount(0),
Chris@574 43 m_inputLeft(0.f),
Chris@580 44 m_inputRight(0.f),
Chris@580 45 m_levelsSet(false)
Chris@476 46 {
Chris@574 47 m_viewManager->setAudioRecordTarget(this);
Chris@574 48
Chris@574 49 connect(this, SIGNAL(recordStatusChanged(bool)),
Chris@574 50 m_viewManager, SLOT(recordStatusChanged(bool)));
Chris@575 51
Chris@575 52 recreateBuffers();
Chris@476 53 }
Chris@476 54
Chris@574 55 AudioCallbackRecordTarget::~AudioCallbackRecordTarget()
Chris@476 56 {
Chris@641 57 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@641 58 cerr << "AudioCallbackRecordTarget dtor" << endl;
Chris@641 59 #endif
Chris@641 60
Chris@636 61 m_viewManager->setAudioRecordTarget(nullptr);
Chris@575 62
Chris@575 63 QMutexLocker locker(&m_bufPtrMutex);
Chris@575 64 for (int c = 0; c < m_bufferCount; ++c) {
Chris@575 65 delete m_buffers[c];
Chris@575 66 }
Chris@575 67 delete[] m_buffers;
Chris@476 68 }
Chris@476 69
Chris@575 70 void
Chris@575 71 AudioCallbackRecordTarget::recreateBuffers()
Chris@575 72 {
Chris@575 73 static int bufferSize = 441000;
Chris@575 74
Chris@775 75 int count = m_systemRecordChannelCount;
Chris@575 76
Chris@575 77 if (count > m_bufferCount) {
Chris@575 78
Chris@575 79 RingBuffer<float> **newBuffers = new RingBuffer<float> *[count];
Chris@575 80 for (int c = 0; c < m_bufferCount; ++c) {
Chris@575 81 newBuffers[c] = m_buffers[c];
Chris@575 82 }
Chris@575 83 for (int c = m_bufferCount; c < count; ++c) {
Chris@575 84 newBuffers[c] = new RingBuffer<float>(bufferSize);
Chris@575 85 }
Chris@575 86
Chris@575 87 // This is the only place where m_buffers is rewritten and
Chris@575 88 // should be the only possible source of contention against
Chris@575 89 // putSamples for this mutex (as the model-updating code is
Chris@575 90 // supposed to run in the same thread as this)
Chris@575 91 QMutexLocker locker(&m_bufPtrMutex);
Chris@575 92 delete[] m_buffers;
Chris@575 93 m_buffers = newBuffers;
Chris@575 94 m_bufferCount = count;
Chris@575 95 }
Chris@575 96 }
Chris@575 97
Chris@559 98 int
Chris@574 99 AudioCallbackRecordTarget::getApplicationSampleRate() const
Chris@559 100 {
Chris@559 101 return 0; // don't care
Chris@559 102 }
Chris@559 103
Chris@559 104 int
Chris@574 105 AudioCallbackRecordTarget::getApplicationChannelCount() const
Chris@559 106 {
Chris@775 107 // Pretend to just have as many as the system expects - we do our
Chris@775 108 // own mixing-down optionally in the m_recordMono case
Chris@775 109 return m_systemRecordChannelCount;
Chris@559 110 }
Chris@559 111
Chris@476 112 void
Chris@574 113 AudioCallbackRecordTarget::setSystemRecordBlockSize(int)
Chris@476 114 {
Chris@476 115 }
Chris@476 116
Chris@476 117 void
Chris@574 118 AudioCallbackRecordTarget::setSystemRecordSampleRate(int n)
Chris@476 119 {
Chris@775 120 SVCERR << "AudioCallbackRecordTarget: system sample rate is " << n << endl;
Chris@476 121 m_recordSampleRate = n;
Chris@476 122 }
Chris@476 123
Chris@476 124 void
Chris@574 125 AudioCallbackRecordTarget::setSystemRecordLatency(int)
Chris@476 126 {
Chris@476 127 }
Chris@476 128
Chris@476 129 void
Chris@574 130 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
Chris@546 131 {
Chris@775 132 SVCERR << "AudioCallbackRecordTarget: system channel count is " << c << endl;
Chris@775 133 m_systemRecordChannelCount = c;
Chris@575 134 recreateBuffers();
Chris@546 135 }
Chris@546 136
Chris@546 137 void
Chris@574 138 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
Chris@476 139 {
Chris@575 140 // This may be called from RT context, and in a different thread
Chris@575 141 // from everything else in this class. It takes a mutex that
Chris@575 142 // should almost never be contended (see recreateBuffers())
Chris@575 143 if (!m_recording) return;
Chris@575 144
Chris@575 145 QMutexLocker locker(&m_bufPtrMutex);
Chris@775 146 if (m_buffers && m_bufferCount >= m_systemRecordChannelCount) {
Chris@775 147 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
Chris@575 148 m_buffers[c]->write(samples[c], nframes);
Chris@575 149 }
Chris@575 150 }
Chris@575 151 }
Chris@575 152
Chris@575 153 void
Chris@575 154 AudioCallbackRecordTarget::updateModel()
Chris@575 155 {
Chris@611 156 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@611 157 cerr << "AudioCallbackRecordTarget::updateModel" << endl;
Chris@611 158 #endif
Chris@611 159
Chris@485 160 sv_frame_t frameToEmit = 0;
Chris@485 161
Chris@575 162 int nframes = 0;
Chris@775 163 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
Chris@575 164 if (c == 0 || m_buffers[c]->getReadSpace() < nframes) {
Chris@575 165 nframes = m_buffers[c]->getReadSpace();
Chris@575 166 }
Chris@575 167 }
Chris@485 168
Chris@575 169 if (nframes == 0) {
Chris@609 170 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@609 171 cerr << "AudioCallbackRecordTarget::updateModel: no frames available" << endl;
Chris@609 172 #endif
Chris@609 173 if (m_recording) {
Chris@609 174 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@609 175 }
Chris@575 176 return;
Chris@575 177 }
Chris@485 178
Chris@611 179 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@611 180 cerr << "AudioCallbackRecordTarget::updateModel: have " << nframes << " frames" << endl;
Chris@611 181 #endif
Chris@611 182
Chris@775 183 auto model = ModelById::getAs<WritableWaveFileModel>(m_modelId);
Chris@775 184 if (!model) {
Chris@641 185 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@641 186 cerr << "AudioCallbackRecordTarget::updateModel: have no model to update; I am hoping there is a good reason for this" << endl;
Chris@641 187 #endif
Chris@641 188 return;
Chris@641 189 }
Chris@641 190
Chris@775 191 float **samples = new float *[m_systemRecordChannelCount];
Chris@775 192 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
Chris@575 193 samples[c] = new float[nframes];
Chris@575 194 m_buffers[c]->read(samples[c], nframes);
Chris@575 195 }
Chris@485 196
Chris@775 197 if (m_recordMono) {
Chris@775 198 breakfastquay::v_reconfigure_channels_inplace(samples, 1,
Chris@775 199 m_systemRecordChannelCount,
Chris@775 200 nframes);
Chris@775 201 }
Chris@775 202
Chris@775 203 model->addSamples(samples, nframes);
Chris@485 204
Chris@775 205 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
Chris@575 206 delete[] samples[c];
Chris@575 207 }
Chris@575 208 delete[] samples;
Chris@575 209
Chris@575 210 m_frameCount += nframes;
Chris@575 211
Chris@775 212 model->updateModel();
Chris@611 213 frameToEmit = m_frameCount;
Chris@611 214 emit recordDurationChanged(frameToEmit, m_recordSampleRate);
Chris@575 215
Chris@575 216 if (m_recording) {
Chris@611 217 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@575 218 }
Chris@476 219 }
Chris@476 220
Chris@476 221 void
Chris@574 222 AudioCallbackRecordTarget::setInputLevels(float left, float right)
Chris@476 223 {
Chris@574 224 if (left > m_inputLeft) m_inputLeft = left;
Chris@574 225 if (right > m_inputRight) m_inputRight = right;
Chris@580 226 m_levelsSet = true;
Chris@574 227 }
Chris@574 228
Chris@574 229 bool
Chris@574 230 AudioCallbackRecordTarget::getInputLevels(float &left, float &right)
Chris@574 231 {
Chris@574 232 left = m_inputLeft;
Chris@574 233 right = m_inputRight;
Chris@581 234 bool valid = m_levelsSet;
Chris@574 235 m_inputLeft = 0.f;
Chris@574 236 m_inputRight = 0.f;
Chris@581 237 m_levelsSet = false;
Chris@581 238 return valid;
Chris@476 239 }
Chris@476 240
Chris@775 241 ModelId
Chris@574 242 AudioCallbackRecordTarget::startRecording()
Chris@476 243 {
Chris@575 244 if (m_recording) {
Chris@575 245 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
Chris@775 246 return {};
Chris@477 247 }
Chris@477 248
Chris@775 249 m_modelId = {};
Chris@575 250 m_frameCount = 0;
Chris@575 251
Chris@620 252 QString folder = RecordDirectory::getRecordDirectory();
Chris@775 253 if (folder == "") return {};
Chris@575 254 QDir recordedDir(folder);
Chris@575 255
Chris@575 256 QDateTime now = QDateTime::currentDateTime();
Chris@575 257
Chris@575 258 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
Chris@575 259 // isn't permitted in filenames on Windows
Chris@575 260 QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
Chris@575 261
Chris@575 262 QString filename = tr("recorded-%1.wav").arg(nowString);
Chris@575 263 QString label = tr("Recorded %1").arg(nowString);
Chris@575 264
Chris@575 265 m_audioFileName = recordedDir.filePath(filename);
Chris@575 266
Chris@775 267 m_recordMono = Preferences::getInstance()->getRecordMono();
Chris@775 268
Chris@775 269 int modelChannelCount = m_systemRecordChannelCount;
Chris@775 270 if (m_recordMono) {
Chris@775 271 modelChannelCount = 1;
Chris@775 272 }
Chris@775 273
Chris@775 274 SVCERR << "AudioCallbackRecordTarget::startRecording: Recording to \""
Chris@775 275 << m_audioFileName << "\", sample rate " << m_recordSampleRate
Chris@775 276 << ", system channel count " << m_systemRecordChannelCount
Chris@775 277 << ", model channel count " << modelChannelCount
Chris@775 278 << " (recordMono = " << m_recordMono << ")" << endl;
Chris@775 279
Chris@775 280 auto model = std::make_shared<WritableWaveFileModel>
Chris@621 281 (m_audioFileName,
Chris@621 282 m_recordSampleRate,
Chris@775 283 modelChannelCount,
Chris@621 284 WritableWaveFileModel::Normalisation::None);
Chris@575 285
Chris@775 286 if (!model->isOK()) {
Chris@575 287 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
Chris@575 288 << endl;
Chris@775 289 m_recording = false;
Chris@775 290 return {};
Chris@575 291 }
Chris@575 292
Chris@775 293 m_modelId = ModelById::add(model);
Chris@775 294
Chris@775 295 model->setObjectName(label);
Chris@575 296 m_recording = true;
Chris@575 297
Chris@477 298 emit recordStatusChanged(true);
Chris@575 299
Chris@611 300 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@575 301
Chris@775 302 return m_modelId;
Chris@476 303 }
Chris@476 304
Chris@476 305 void
Chris@574 306 AudioCallbackRecordTarget::stopRecording()
Chris@476 307 {
Chris@575 308 if (!m_recording) {
Chris@575 309 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl;
Chris@575 310 return;
Chris@477 311 }
Chris@477 312
Chris@575 313 m_recording = false;
Chris@575 314
Chris@575 315 m_bufPtrMutex.lock();
Chris@575 316 m_bufPtrMutex.unlock();
Chris@575 317
Chris@575 318 // buffers should now be up-to-date
Chris@575 319 updateModel();
Chris@575 320
Chris@775 321 auto model = ModelById::getAs<WritableWaveFileModel>(m_modelId);
Chris@775 322 if (model) {
Chris@775 323 model->writeComplete();
Chris@775 324 }
Chris@775 325
Chris@775 326 m_modelId = {};
Chris@575 327
Chris@477 328 emit recordStatusChanged(false);
Chris@497 329 emit recordCompleted();
Chris@476 330 }
Chris@476 331