annotate audio/AudioCallbackRecordTarget.cpp @ 626:51ecc3e2d71c

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