comparison 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
comparison
equal deleted inserted replaced
774:7bded7599874 775:56a81812c131
15 #include "AudioCallbackRecordTarget.h" 15 #include "AudioCallbackRecordTarget.h"
16 16
17 #include "base/ViewManagerBase.h" 17 #include "base/ViewManagerBase.h"
18 #include "base/RecordDirectory.h" 18 #include "base/RecordDirectory.h"
19 #include "base/Debug.h" 19 #include "base/Debug.h"
20 #include "base/Preferences.h"
20 21
21 #include "data/model/WritableWaveFileModel.h" 22 #include "data/model/WritableWaveFileModel.h"
22 23
23 #include <QDir> 24 #include <QDir>
24 #include <QTimer> 25 #include <QTimer>
32 QString clientName) : 33 QString clientName) :
33 m_viewManager(manager), 34 m_viewManager(manager),
34 m_clientName(clientName.toUtf8().data()), 35 m_clientName(clientName.toUtf8().data()),
35 m_recording(false), 36 m_recording(false),
36 m_recordSampleRate(44100), 37 m_recordSampleRate(44100),
37 m_recordChannelCount(2), 38 m_systemRecordChannelCount(2),
39 m_recordMono(false),
38 m_frameCount(0), 40 m_frameCount(0),
39 m_model(nullptr),
40 m_buffers(nullptr), 41 m_buffers(nullptr),
41 m_bufferCount(0), 42 m_bufferCount(0),
42 m_inputLeft(0.f), 43 m_inputLeft(0.f),
43 m_inputRight(0.f), 44 m_inputRight(0.f),
44 m_levelsSet(false) 45 m_levelsSet(false)
69 void 70 void
70 AudioCallbackRecordTarget::recreateBuffers() 71 AudioCallbackRecordTarget::recreateBuffers()
71 { 72 {
72 static int bufferSize = 441000; 73 static int bufferSize = 441000;
73 74
74 int count = m_recordChannelCount; 75 int count = m_systemRecordChannelCount;
75 76
76 if (count > m_bufferCount) { 77 if (count > m_bufferCount) {
77 78
78 RingBuffer<float> **newBuffers = new RingBuffer<float> *[count]; 79 RingBuffer<float> **newBuffers = new RingBuffer<float> *[count];
79 for (int c = 0; c < m_bufferCount; ++c) { 80 for (int c = 0; c < m_bufferCount; ++c) {
101 } 102 }
102 103
103 int 104 int
104 AudioCallbackRecordTarget::getApplicationChannelCount() const 105 AudioCallbackRecordTarget::getApplicationChannelCount() const
105 { 106 {
106 return m_recordChannelCount; 107 // Pretend to just have as many as the system expects - we do our
108 // own mixing-down optionally in the m_recordMono case
109 return m_systemRecordChannelCount;
107 } 110 }
108 111
109 void 112 void
110 AudioCallbackRecordTarget::setSystemRecordBlockSize(int) 113 AudioCallbackRecordTarget::setSystemRecordBlockSize(int)
111 { 114 {
112 } 115 }
113 116
114 void 117 void
115 AudioCallbackRecordTarget::setSystemRecordSampleRate(int n) 118 AudioCallbackRecordTarget::setSystemRecordSampleRate(int n)
116 { 119 {
120 SVCERR << "AudioCallbackRecordTarget: system sample rate is " << n << endl;
117 m_recordSampleRate = n; 121 m_recordSampleRate = n;
118 } 122 }
119 123
120 void 124 void
121 AudioCallbackRecordTarget::setSystemRecordLatency(int) 125 AudioCallbackRecordTarget::setSystemRecordLatency(int)
123 } 127 }
124 128
125 void 129 void
126 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c) 130 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
127 { 131 {
128 m_recordChannelCount = c; 132 SVCERR << "AudioCallbackRecordTarget: system channel count is " << c << endl;
133 m_systemRecordChannelCount = c;
129 recreateBuffers(); 134 recreateBuffers();
130 } 135 }
131 136
132 void 137 void
133 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes) 138 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
136 // from everything else in this class. It takes a mutex that 141 // from everything else in this class. It takes a mutex that
137 // should almost never be contended (see recreateBuffers()) 142 // should almost never be contended (see recreateBuffers())
138 if (!m_recording) return; 143 if (!m_recording) return;
139 144
140 QMutexLocker locker(&m_bufPtrMutex); 145 QMutexLocker locker(&m_bufPtrMutex);
141 if (m_buffers && m_bufferCount >= m_recordChannelCount) { 146 if (m_buffers && m_bufferCount >= m_systemRecordChannelCount) {
142 for (int c = 0; c < m_recordChannelCount; ++c) { 147 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
143 m_buffers[c]->write(samples[c], nframes); 148 m_buffers[c]->write(samples[c], nframes);
144 } 149 }
145 } 150 }
146 } 151 }
147 152
153 #endif 158 #endif
154 159
155 sv_frame_t frameToEmit = 0; 160 sv_frame_t frameToEmit = 0;
156 161
157 int nframes = 0; 162 int nframes = 0;
158 for (int c = 0; c < m_recordChannelCount; ++c) { 163 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
159 if (c == 0 || m_buffers[c]->getReadSpace() < nframes) { 164 if (c == 0 || m_buffers[c]->getReadSpace() < nframes) {
160 nframes = m_buffers[c]->getReadSpace(); 165 nframes = m_buffers[c]->getReadSpace();
161 } 166 }
162 } 167 }
163 168
173 178
174 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET 179 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
175 cerr << "AudioCallbackRecordTarget::updateModel: have " << nframes << " frames" << endl; 180 cerr << "AudioCallbackRecordTarget::updateModel: have " << nframes << " frames" << endl;
176 #endif 181 #endif
177 182
178 if (!m_model) { 183 auto model = ModelById::getAs<WritableWaveFileModel>(m_modelId);
184 if (!model) {
179 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET 185 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
180 cerr << "AudioCallbackRecordTarget::updateModel: have no model to update; I am hoping there is a good reason for this" << endl; 186 cerr << "AudioCallbackRecordTarget::updateModel: have no model to update; I am hoping there is a good reason for this" << endl;
181 #endif 187 #endif
182 return; 188 return;
183 } 189 }
184 190
185 float **samples = new float *[m_recordChannelCount]; 191 float **samples = new float *[m_systemRecordChannelCount];
186 for (int c = 0; c < m_recordChannelCount; ++c) { 192 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
187 samples[c] = new float[nframes]; 193 samples[c] = new float[nframes];
188 m_buffers[c]->read(samples[c], nframes); 194 m_buffers[c]->read(samples[c], nframes);
189 } 195 }
190 196
191 m_model->addSamples(samples, nframes); 197 if (m_recordMono) {
192 198 breakfastquay::v_reconfigure_channels_inplace(samples, 1,
193 for (int c = 0; c < m_recordChannelCount; ++c) { 199 m_systemRecordChannelCount,
200 nframes);
201 }
202
203 model->addSamples(samples, nframes);
204
205 for (int c = 0; c < m_systemRecordChannelCount; ++c) {
194 delete[] samples[c]; 206 delete[] samples[c];
195 } 207 }
196 delete[] samples; 208 delete[] samples;
197 209
198 m_frameCount += nframes; 210 m_frameCount += nframes;
199 211
200 m_model->updateModel(); 212 model->updateModel();
201 frameToEmit = m_frameCount; 213 frameToEmit = m_frameCount;
202 emit recordDurationChanged(frameToEmit, m_recordSampleRate); 214 emit recordDurationChanged(frameToEmit, m_recordSampleRate);
203 215
204 if (m_recording) { 216 if (m_recording) {
205 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel())); 217 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
224 m_inputRight = 0.f; 236 m_inputRight = 0.f;
225 m_levelsSet = false; 237 m_levelsSet = false;
226 return valid; 238 return valid;
227 } 239 }
228 240
229 void 241 ModelId
230 AudioCallbackRecordTarget::modelAboutToBeDeleted()
231 {
232 if (sender() == m_model) {
233 #ifdef DEBUG_AUDIO_CALLBACK_RECORD_TARGET
234 cerr << "AudioCallbackRecordTarget::modelAboutToBeDeleted: taking note" << endl;
235 #endif
236 m_model = nullptr;
237 m_recording = false;
238 } else if (m_model) {
239 SVCERR << "WARNING: AudioCallbackRecordTarget::modelAboutToBeDeleted: this is not my model!" << endl;
240 }
241 }
242
243 WritableWaveFileModel *
244 AudioCallbackRecordTarget::startRecording() 242 AudioCallbackRecordTarget::startRecording()
245 { 243 {
246 if (m_recording) { 244 if (m_recording) {
247 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl; 245 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
248 return nullptr; 246 return {};
249 } 247 }
250 248
251 m_model = nullptr; 249 m_modelId = {};
252 m_frameCount = 0; 250 m_frameCount = 0;
253 251
254 QString folder = RecordDirectory::getRecordDirectory(); 252 QString folder = RecordDirectory::getRecordDirectory();
255 if (folder == "") return nullptr; 253 if (folder == "") return {};
256 QDir recordedDir(folder); 254 QDir recordedDir(folder);
257 255
258 QDateTime now = QDateTime::currentDateTime(); 256 QDateTime now = QDateTime::currentDateTime();
259 257
260 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character 258 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
264 QString filename = tr("recorded-%1.wav").arg(nowString); 262 QString filename = tr("recorded-%1.wav").arg(nowString);
265 QString label = tr("Recorded %1").arg(nowString); 263 QString label = tr("Recorded %1").arg(nowString);
266 264
267 m_audioFileName = recordedDir.filePath(filename); 265 m_audioFileName = recordedDir.filePath(filename);
268 266
269 m_model = new WritableWaveFileModel 267 m_recordMono = Preferences::getInstance()->getRecordMono();
268
269 int modelChannelCount = m_systemRecordChannelCount;
270 if (m_recordMono) {
271 modelChannelCount = 1;
272 }
273
274 SVCERR << "AudioCallbackRecordTarget::startRecording: Recording to \""
275 << m_audioFileName << "\", sample rate " << m_recordSampleRate
276 << ", system channel count " << m_systemRecordChannelCount
277 << ", model channel count " << modelChannelCount
278 << " (recordMono = " << m_recordMono << ")" << endl;
279
280 auto model = std::make_shared<WritableWaveFileModel>
270 (m_audioFileName, 281 (m_audioFileName,
271 m_recordSampleRate, 282 m_recordSampleRate,
272 m_recordChannelCount, 283 modelChannelCount,
273 WritableWaveFileModel::Normalisation::None); 284 WritableWaveFileModel::Normalisation::None);
274 285
275 if (!m_model->isOK()) { 286 if (!model->isOK()) {
276 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed" 287 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
277 << endl; 288 << endl;
278 //!!! and throw? 289 m_recording = false;
279 delete m_model; 290 return {};
280 m_model = nullptr; 291 }
281 return nullptr; 292
282 } 293 m_modelId = ModelById::add(model);
283 294
284 connect(m_model, SIGNAL(aboutToBeDeleted()), 295 model->setObjectName(label);
285 this, SLOT(modelAboutToBeDeleted()));
286
287 m_model->setObjectName(label);
288 m_recording = true; 296 m_recording = true;
289 297
290 emit recordStatusChanged(true); 298 emit recordStatusChanged(true);
291 299
292 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel())); 300 QTimer::singleShot(recordUpdateTimeout, this, SLOT(updateModel()));
293 301
294 return m_model; 302 return m_modelId;
295 } 303 }
296 304
297 void 305 void
298 AudioCallbackRecordTarget::stopRecording() 306 AudioCallbackRecordTarget::stopRecording()
299 { 307 {
308 m_bufPtrMutex.unlock(); 316 m_bufPtrMutex.unlock();
309 317
310 // buffers should now be up-to-date 318 // buffers should now be up-to-date
311 updateModel(); 319 updateModel();
312 320
313 m_model->writeComplete(); 321 auto model = ModelById::getAs<WritableWaveFileModel>(m_modelId);
314 m_model = nullptr; 322 if (model) {
323 model->writeComplete();
324 }
325
326 m_modelId = {};
315 327
316 emit recordStatusChanged(false); 328 emit recordStatusChanged(false);
317 emit recordCompleted(); 329 emit recordCompleted();
318 } 330 }
319 331
320