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