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