annotate audio/AudioCallbackRecordTarget.cpp @ 725:16f6737fa557 spectrogram-export

Rework OSC handler so as to consume all available messages rather than having to wait for the timeout in between them. Pause to process events, and also wait for file loads and transforms to complete. (Should only certain kinds of OSC command wait for transforms?)
author Chris Cannam
date Wed, 08 Jan 2020 15:33:17 +0000
parents aee03ad6d3f6
children 56a81812c131
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@476 20
Chris@477 21 #include "data/model/WritableWaveFileModel.h"
Chris@476 22
Chris@476 23 #include <QDir>
Chris@575 24 #include <QTimer>
Chris@711 25 #include <QDateTime>
Chris@476 26
Chris@609 27 //#define DEBUG_AUDIO_CALLBACK_RECORD_TARGET 1
Chris@609 28
Chris@609 29 static const int recordUpdateTimeout = 200; // ms
Chris@609 30
Chris@574 31 AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager,
Chris@574 32 QString clientName) :
Chris@476 33 m_viewManager(manager),
Chris@476 34 m_clientName(clientName.toUtf8().data()),
Chris@476 35 m_recording(false),
Chris@476 36 m_recordSampleRate(44100),
Chris@546 37 m_recordChannelCount(2),
Chris@485 38 m_frameCount(0),
Chris@636 39 m_model(nullptr),
Chris@636 40 m_buffers(nullptr),
Chris@575 41 m_bufferCount(0),
Chris@574 42 m_inputLeft(0.f),
Chris@580 43 m_inputRight(0.f),
Chris@580 44 m_levelsSet(false)
Chris@476 45 {
Chris@574 46 m_viewManager->setAudioRecordTarget(this);
Chris@574 47
Chris@574 48 connect(this, SIGNAL(recordStatusChanged(bool)),
Chris@574 49 m_viewManager, SLOT(recordStatusChanged(bool)));
Chris@575 50
Chris@575 51 recreateBuffers();
Chris@476 52 }
Chris@476 53
Chris@574 54 AudioCallbackRecordTarget::~AudioCallbackRecordTarget()
Chris@476 55 {
Chris@641 56 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@641 57 cerr << "AudioCallbackRecordTarget dtor" << endl;
Chris@641 58 #endif
Chris@641 59
Chris@636 60 m_viewManager->setAudioRecordTarget(nullptr);
Chris@575 61
Chris@575 62 QMutexLocker locker(&m_bufPtrMutex);
Chris@575 63 for (int c = 0; c < m_bufferCount; ++c) {
Chris@575 64 delete m_buffers[c];
Chris@575 65 }
Chris@575 66 delete[] m_buffers;
Chris@476 67 }
Chris@476 68
Chris@575 69 void
Chris@575 70 AudioCallbackRecordTarget::recreateBuffers()
Chris@575 71 {
Chris@575 72 static int bufferSize = 441000;
Chris@575 73
Chris@575 74 int count = m_recordChannelCount;
Chris@575 75
Chris@575 76 if (count > m_bufferCount) {
Chris@575 77
Chris@575 78 RingBuffer<float> **newBuffers = new RingBuffer<float> *[count];
Chris@575 79 for (int c = 0; c < m_bufferCount; ++c) {
Chris@575 80 newBuffers[c] = m_buffers[c];
Chris@575 81 }
Chris@575 82 for (int c = m_bufferCount; c < count; ++c) {
Chris@575 83 newBuffers[c] = new RingBuffer<float>(bufferSize);
Chris@575 84 }
Chris@575 85
Chris@575 86 // This is the only place where m_buffers is rewritten and
Chris@575 87 // should be the only possible source of contention against
Chris@575 88 // putSamples for this mutex (as the model-updating code is
Chris@575 89 // supposed to run in the same thread as this)
Chris@575 90 QMutexLocker locker(&m_bufPtrMutex);
Chris@575 91 delete[] m_buffers;
Chris@575 92 m_buffers = newBuffers;
Chris@575 93 m_bufferCount = count;
Chris@575 94 }
Chris@575 95 }
Chris@575 96
Chris@559 97 int
Chris@574 98 AudioCallbackRecordTarget::getApplicationSampleRate() const
Chris@559 99 {
Chris@559 100 return 0; // don't care
Chris@559 101 }
Chris@559 102
Chris@559 103 int
Chris@574 104 AudioCallbackRecordTarget::getApplicationChannelCount() const
Chris@559 105 {
Chris@559 106 return m_recordChannelCount;
Chris@559 107 }
Chris@559 108
Chris@476 109 void
Chris@574 110 AudioCallbackRecordTarget::setSystemRecordBlockSize(int)
Chris@476 111 {
Chris@476 112 }
Chris@476 113
Chris@476 114 void
Chris@574 115 AudioCallbackRecordTarget::setSystemRecordSampleRate(int n)
Chris@476 116 {
Chris@476 117 m_recordSampleRate = n;
Chris@476 118 }
Chris@476 119
Chris@476 120 void
Chris@574 121 AudioCallbackRecordTarget::setSystemRecordLatency(int)
Chris@476 122 {
Chris@476 123 }
Chris@476 124
Chris@476 125 void
Chris@574 126 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
Chris@546 127 {
Chris@546 128 m_recordChannelCount = c;
Chris@575 129 recreateBuffers();
Chris@546 130 }
Chris@546 131
Chris@546 132 void
Chris@574 133 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
Chris@476 134 {
Chris@575 135 // This may be called from RT context, and in a different thread
Chris@575 136 // from everything else in this class. It takes a mutex that
Chris@575 137 // should almost never be contended (see recreateBuffers())
Chris@575 138 if (!m_recording) return;
Chris@575 139
Chris@575 140 QMutexLocker locker(&m_bufPtrMutex);
Chris@575 141 if (m_buffers && m_bufferCount >= m_recordChannelCount) {
Chris@575 142 for (int c = 0; c < m_recordChannelCount; ++c) {
Chris@575 143 m_buffers[c]->write(samples[c], nframes);
Chris@575 144 }
Chris@575 145 }
Chris@575 146 }
Chris@575 147
Chris@575 148 void
Chris@575 149 AudioCallbackRecordTarget::updateModel()
Chris@575 150 {
Chris@611 151 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@611 152 cerr << "AudioCallbackRecordTarget::updateModel" << endl;
Chris@611 153 #endif
Chris@611 154
Chris@485 155 sv_frame_t frameToEmit = 0;
Chris@485 156
Chris@575 157 int nframes = 0;
Chris@575 158 for (int c = 0; c < m_recordChannelCount; ++c) {
Chris@575 159 if (c == 0 || m_buffers[c]->getReadSpace() < nframes) {
Chris@575 160 nframes = m_buffers[c]->getReadSpace();
Chris@575 161 }
Chris@575 162 }
Chris@485 163
Chris@575 164 if (nframes == 0) {
Chris@609 165 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@609 166 cerr << "AudioCallbackRecordTarget::updateModel: no frames available" << endl;
Chris@609 167 #endif
Chris@609 168 if (m_recording) {
Chris@609 169 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@609 170 }
Chris@575 171 return;
Chris@575 172 }
Chris@485 173
Chris@611 174 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@611 175 cerr << "AudioCallbackRecordTarget::updateModel: have " << nframes << " frames" << endl;
Chris@611 176 #endif
Chris@611 177
Chris@641 178 if (!m_model) {
Chris@641 179 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@641 180 cerr << "AudioCallbackRecordTarget::updateModel: have no model to update; I am hoping there is a good reason for this" << endl;
Chris@641 181 #endif
Chris@641 182 return;
Chris@641 183 }
Chris@641 184
Chris@575 185 float **samples = new float *[m_recordChannelCount];
Chris@575 186 for (int c = 0; c < m_recordChannelCount; ++c) {
Chris@575 187 samples[c] = new float[nframes];
Chris@575 188 m_buffers[c]->read(samples[c], nframes);
Chris@575 189 }
Chris@485 190
Chris@575 191 m_model->addSamples(samples, nframes);
Chris@485 192
Chris@575 193 for (int c = 0; c < m_recordChannelCount; ++c) {
Chris@575 194 delete[] samples[c];
Chris@575 195 }
Chris@575 196 delete[] samples;
Chris@575 197
Chris@575 198 m_frameCount += nframes;
Chris@575 199
Chris@611 200 m_model->updateModel();
Chris@611 201 frameToEmit = m_frameCount;
Chris@611 202 emit recordDurationChanged(frameToEmit, m_recordSampleRate);
Chris@575 203
Chris@575 204 if (m_recording) {
Chris@611 205 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@575 206 }
Chris@476 207 }
Chris@476 208
Chris@476 209 void
Chris@574 210 AudioCallbackRecordTarget::setInputLevels(float left, float right)
Chris@476 211 {
Chris@574 212 if (left > m_inputLeft) m_inputLeft = left;
Chris@574 213 if (right > m_inputRight) m_inputRight = right;
Chris@580 214 m_levelsSet = true;
Chris@574 215 }
Chris@574 216
Chris@574 217 bool
Chris@574 218 AudioCallbackRecordTarget::getInputLevels(float &left, float &right)
Chris@574 219 {
Chris@574 220 left = m_inputLeft;
Chris@574 221 right = m_inputRight;
Chris@581 222 bool valid = m_levelsSet;
Chris@574 223 m_inputLeft = 0.f;
Chris@574 224 m_inputRight = 0.f;
Chris@581 225 m_levelsSet = false;
Chris@581 226 return valid;
Chris@476 227 }
Chris@476 228
Chris@477 229 void
Chris@574 230 AudioCallbackRecordTarget::modelAboutToBeDeleted()
Chris@477 231 {
Chris@477 232 if (sender() == m_model) {
Chris@641 233 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
Chris@641 234 cerr << "AudioCallbackRecordTarget::modelAboutToBeDeleted: taking note" << endl;
Chris@641 235 #endif
Chris@636 236 m_model = nullptr;
Chris@477 237 m_recording = false;
Chris@642 238 } else if (m_model) {
Chris@642 239 SVCERR << "WARNING: AudioCallbackRecordTarget::modelAboutToBeDeleted: this is not my model!" << endl;
Chris@477 240 }
Chris@477 241 }
Chris@477 242
Chris@477 243 WritableWaveFileModel *
Chris@574 244 AudioCallbackRecordTarget::startRecording()
Chris@476 245 {
Chris@575 246 if (m_recording) {
Chris@575 247 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
Chris@636 248 return nullptr;
Chris@477 249 }
Chris@477 250
Chris@636 251 m_model = nullptr;
Chris@575 252 m_frameCount = 0;
Chris@575 253
Chris@620 254 QString folder = RecordDirectory::getRecordDirectory();
Chris@636 255 if (folder == "") return nullptr;
Chris@575 256 QDir recordedDir(folder);
Chris@575 257
Chris@575 258 QDateTime now = QDateTime::currentDateTime();
Chris@575 259
Chris@575 260 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
Chris@575 261 // isn't permitted in filenames on Windows
Chris@575 262 QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
Chris@575 263
Chris@575 264 QString filename = tr("recorded-%1.wav").arg(nowString);
Chris@575 265 QString label = tr("Recorded %1").arg(nowString);
Chris@575 266
Chris@575 267 m_audioFileName = recordedDir.filePath(filename);
Chris@575 268
Chris@621 269 m_model = new WritableWaveFileModel
Chris@621 270 (m_audioFileName,
Chris@621 271 m_recordSampleRate,
Chris@621 272 m_recordChannelCount,
Chris@621 273 WritableWaveFileModel::Normalisation::None);
Chris@575 274
Chris@575 275 if (!m_model->isOK()) {
Chris@575 276 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
Chris@575 277 << endl;
Chris@575 278 //!!! and throw?
Chris@575 279 delete m_model;
Chris@636 280 m_model = nullptr;
Chris@636 281 return nullptr;
Chris@575 282 }
Chris@575 283
Chris@641 284 connect(m_model, SIGNAL(aboutToBeDeleted()),
Chris@641 285 this, SLOT(modelAboutToBeDeleted()));
Chris@641 286
Chris@575 287 m_model->setObjectName(label);
Chris@575 288 m_recording = true;
Chris@575 289
Chris@477 290 emit recordStatusChanged(true);
Chris@575 291
Chris@611 292 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
Chris@575 293
Chris@477 294 return m_model;
Chris@476 295 }
Chris@476 296
Chris@476 297 void
Chris@574 298 AudioCallbackRecordTarget::stopRecording()
Chris@476 299 {
Chris@575 300 if (!m_recording) {
Chris@575 301 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl;
Chris@575 302 return;
Chris@477 303 }
Chris@477 304
Chris@575 305 m_recording = false;
Chris@575 306
Chris@575 307 m_bufPtrMutex.lock();
Chris@575 308 m_bufPtrMutex.unlock();
Chris@575 309
Chris@575 310 // buffers should now be up-to-date
Chris@575 311 updateModel();
Chris@575 312
Chris@575 313 m_model->writeComplete();
Chris@636 314 m_model = nullptr;
Chris@575 315
Chris@477 316 emit recordStatusChanged(false);
Chris@497 317 emit recordCompleted();
Chris@476 318 }
Chris@476 319
Chris@476 320