Chris@0
|
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 A waveform viewer and audio annotation editor.
|
Chris@1
|
5 Chris Cannam, Queen Mary University of London, 2005-2006
|
Chris@0
|
6
|
Chris@0
|
7 This is experimental software. Not for distribution.
|
Chris@0
|
8 */
|
Chris@0
|
9
|
Chris@0
|
10 #include "AudioCallbackPlaySource.h"
|
Chris@0
|
11
|
Chris@0
|
12 #include "AudioGenerator.h"
|
Chris@0
|
13
|
Chris@0
|
14 #include "base/Model.h"
|
Chris@0
|
15 #include "base/ViewManager.h"
|
Chris@0
|
16 #include "model/DenseTimeValueModel.h"
|
Chris@0
|
17 #include "model/SparseOneDimensionalModel.h"
|
Chris@0
|
18 #include "dsp/timestretching/IntegerTimeStretcher.h"
|
Chris@0
|
19
|
Chris@0
|
20 #include <iostream>
|
Chris@0
|
21
|
Chris@0
|
22 //#define DEBUG_AUDIO_PLAY_SOURCE 1
|
Chris@0
|
23
|
Chris@0
|
24 //const size_t AudioCallbackPlaySource::m_ringBufferSize = 102400;
|
Chris@0
|
25 const size_t AudioCallbackPlaySource::m_ringBufferSize = 131071;
|
Chris@0
|
26
|
Chris@0
|
27 AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManager *manager) :
|
Chris@0
|
28 m_viewManager(manager),
|
Chris@0
|
29 m_audioGenerator(new AudioGenerator(manager)),
|
Chris@0
|
30 m_bufferCount(0),
|
Chris@0
|
31 m_blockSize(1024),
|
Chris@0
|
32 m_sourceSampleRate(0),
|
Chris@0
|
33 m_targetSampleRate(0),
|
Chris@0
|
34 m_playLatency(0),
|
Chris@0
|
35 m_playing(false),
|
Chris@0
|
36 m_exiting(false),
|
Chris@0
|
37 m_bufferedToFrame(0),
|
Chris@0
|
38 m_outputLeft(0.0),
|
Chris@0
|
39 m_outputRight(0.0),
|
Chris@0
|
40 m_slowdownCounter(0),
|
Chris@0
|
41 m_timeStretcher(0),
|
Chris@0
|
42 m_fillThread(0),
|
Chris@0
|
43 m_converter(0)
|
Chris@0
|
44 {
|
Chris@0
|
45 // preallocate some slots, to avoid reallocation in an
|
Chris@0
|
46 // un-thread-safe manner later
|
Chris@0
|
47 while (m_buffers.size() < 20) m_buffers.push_back(0);
|
Chris@0
|
48
|
Chris@0
|
49 m_viewManager->setAudioPlaySource(this);
|
Chris@3
|
50
|
Chris@3
|
51 connect(m_viewManager, SIGNAL(selectionChanged()),
|
Chris@3
|
52 this, SLOT(selectionChanged()));
|
Chris@3
|
53 connect(m_viewManager, SIGNAL(playLoopModeChanged()),
|
Chris@3
|
54 this, SLOT(playLoopModeChanged()));
|
Chris@3
|
55 connect(m_viewManager, SIGNAL(playSelectionModeChanged()),
|
Chris@3
|
56 this, SLOT(playSelectionModeChanged()));
|
Chris@0
|
57 }
|
Chris@0
|
58
|
Chris@0
|
59 AudioCallbackPlaySource::~AudioCallbackPlaySource()
|
Chris@0
|
60 {
|
Chris@0
|
61 m_exiting = true;
|
Chris@0
|
62
|
Chris@0
|
63 if (m_fillThread) {
|
Chris@0
|
64 m_condition.wakeAll();
|
Chris@0
|
65 m_fillThread->wait();
|
Chris@0
|
66 delete m_fillThread;
|
Chris@0
|
67 }
|
Chris@0
|
68
|
Chris@0
|
69 clearModels();
|
Chris@0
|
70 }
|
Chris@0
|
71
|
Chris@0
|
72 void
|
Chris@0
|
73 AudioCallbackPlaySource::addModel(Model *model)
|
Chris@0
|
74 {
|
Chris@0
|
75 m_mutex.lock();
|
Chris@0
|
76
|
Chris@0
|
77 m_models.insert(model);
|
Chris@0
|
78
|
Chris@0
|
79 bool buffersChanged = false, srChanged = false;
|
Chris@0
|
80
|
Chris@0
|
81 if (m_sourceSampleRate == 0) {
|
Chris@0
|
82
|
Chris@0
|
83 m_sourceSampleRate = model->getSampleRate();
|
Chris@0
|
84 srChanged = true;
|
Chris@0
|
85
|
Chris@0
|
86 } else if (model->getSampleRate() != m_sourceSampleRate) {
|
Chris@0
|
87 std::cerr << "AudioCallbackPlaySource::addModel: ERROR: "
|
Chris@0
|
88 << "New model sample rate does not match" << std::endl
|
Chris@0
|
89 << "existing model(s) (new " << model->getSampleRate()
|
Chris@0
|
90 << " vs " << m_sourceSampleRate
|
Chris@0
|
91 << "), playback will be wrong"
|
Chris@0
|
92 << std::endl;
|
Chris@0
|
93 }
|
Chris@0
|
94
|
Chris@0
|
95 size_t sz = m_ringBufferSize;
|
Chris@0
|
96 if (m_bufferCount > 0) {
|
Chris@0
|
97 sz = m_buffers[0]->getSize();
|
Chris@0
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 size_t modelChannels = 1;
|
Chris@0
|
101 DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
|
Chris@0
|
102 if (dtvm) modelChannels = dtvm->getChannelCount();
|
Chris@0
|
103
|
Chris@0
|
104 while (m_bufferCount < modelChannels) {
|
Chris@0
|
105
|
Chris@0
|
106 if (m_buffers.size() < modelChannels) {
|
Chris@0
|
107 // This is a hideously chancy operation -- the RT thread
|
Chris@0
|
108 // could be using this vector. We allocated several slots
|
Chris@0
|
109 // in the ctor to avoid exactly this, but if we ever end
|
Chris@0
|
110 // up with more channels than that (!) then we're just
|
Chris@0
|
111 // going to have to risk it
|
Chris@0
|
112 m_buffers.push_back(new RingBuffer<float>(sz));
|
Chris@0
|
113
|
Chris@0
|
114 } else {
|
Chris@0
|
115 // The usual case
|
Chris@0
|
116 m_buffers[m_bufferCount] = new RingBuffer<float>(sz);
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 ++m_bufferCount;
|
Chris@0
|
120 buffersChanged = true;
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 if (buffersChanged) {
|
Chris@0
|
124 m_audioGenerator->setTargetChannelCount(m_bufferCount);
|
Chris@0
|
125 }
|
Chris@0
|
126
|
Chris@0
|
127 if (buffersChanged || srChanged) {
|
Chris@0
|
128 if (m_converter) {
|
Chris@0
|
129 src_delete(m_converter);
|
Chris@0
|
130 m_converter = 0;
|
Chris@0
|
131 }
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 m_audioGenerator->addModel(model);
|
Chris@0
|
135
|
Chris@0
|
136 m_mutex.unlock();
|
Chris@0
|
137
|
Chris@0
|
138 if (!m_fillThread) {
|
Chris@0
|
139 m_fillThread = new AudioCallbackPlaySourceFillThread(*this);
|
Chris@0
|
140 m_fillThread->start();
|
Chris@0
|
141 }
|
Chris@0
|
142
|
Chris@0
|
143 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
144 std::cerr << "AudioCallbackPlaySource::addModel: emitting modelReplaced" << std::endl;
|
Chris@0
|
145 #endif
|
Chris@0
|
146
|
Chris@1
|
147 if (buffersChanged || srChanged) {
|
Chris@1
|
148 emit modelReplaced();
|
Chris@0
|
149 }
|
Chris@0
|
150 }
|
Chris@0
|
151
|
Chris@0
|
152 void
|
Chris@0
|
153 AudioCallbackPlaySource::removeModel(Model *model)
|
Chris@0
|
154 {
|
Chris@0
|
155 m_mutex.lock();
|
Chris@0
|
156
|
Chris@0
|
157 m_models.erase(model);
|
Chris@0
|
158
|
Chris@0
|
159 if (m_models.empty()) {
|
Chris@0
|
160 if (m_converter) {
|
Chris@0
|
161 src_delete(m_converter);
|
Chris@0
|
162 m_converter = 0;
|
Chris@0
|
163 }
|
Chris@0
|
164 m_sourceSampleRate = 0;
|
Chris@0
|
165 }
|
Chris@0
|
166
|
Chris@0
|
167 m_audioGenerator->removeModel(model);
|
Chris@0
|
168
|
Chris@0
|
169 m_mutex.unlock();
|
Chris@0
|
170 }
|
Chris@0
|
171
|
Chris@0
|
172 void
|
Chris@0
|
173 AudioCallbackPlaySource::clearModels()
|
Chris@0
|
174 {
|
Chris@0
|
175 m_mutex.lock();
|
Chris@0
|
176
|
Chris@0
|
177 m_models.clear();
|
Chris@0
|
178
|
Chris@0
|
179 if (m_converter) {
|
Chris@0
|
180 src_delete(m_converter);
|
Chris@0
|
181 m_converter = 0;
|
Chris@0
|
182 }
|
Chris@0
|
183
|
Chris@0
|
184 m_audioGenerator->clearModels();
|
Chris@0
|
185
|
Chris@0
|
186 m_sourceSampleRate = 0;
|
Chris@0
|
187
|
Chris@0
|
188 m_mutex.unlock();
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 void
|
Chris@0
|
192 AudioCallbackPlaySource::play(size_t startFrame)
|
Chris@0
|
193 {
|
Chris@0
|
194 // The fill thread will automatically empty its buffers before
|
Chris@0
|
195 // starting again if we have not so far been playing, but not if
|
Chris@0
|
196 // we're just re-seeking.
|
Chris@0
|
197
|
Chris@0
|
198 if (m_playing) {
|
Chris@0
|
199 m_mutex.lock();
|
Chris@0
|
200 m_bufferedToFrame = startFrame;
|
Chris@0
|
201 for (size_t c = 0; c < m_bufferCount; ++c) {
|
Chris@0
|
202 getRingBuffer(c).reset();
|
Chris@0
|
203 if (m_converter) src_reset(m_converter);
|
Chris@0
|
204 }
|
Chris@0
|
205 m_mutex.unlock();
|
Chris@0
|
206 } else {
|
Chris@0
|
207 m_bufferedToFrame = startFrame;
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@0
|
210 m_audioGenerator->reset();
|
Chris@0
|
211
|
Chris@0
|
212 m_playing = true;
|
Chris@0
|
213 m_condition.wakeAll();
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 void
|
Chris@0
|
217 AudioCallbackPlaySource::stop()
|
Chris@0
|
218 {
|
Chris@0
|
219 m_playing = false;
|
Chris@0
|
220 m_condition.wakeAll();
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 void
|
Chris@3
|
224 AudioCallbackPlaySource::selectionChanged()
|
Chris@3
|
225 {
|
Chris@3
|
226 if (m_viewManager->getPlaySelectionMode()) {
|
Chris@3
|
227 m_mutex.lock();
|
Chris@3
|
228 for (size_t c = 0; c < m_bufferCount; ++c) {
|
Chris@3
|
229 getRingBuffer(c).reset();
|
Chris@3
|
230 }
|
Chris@3
|
231 m_mutex.unlock();
|
Chris@3
|
232 }
|
Chris@3
|
233 }
|
Chris@3
|
234
|
Chris@3
|
235 void
|
Chris@3
|
236 AudioCallbackPlaySource::playLoopModeChanged()
|
Chris@3
|
237 {
|
Chris@3
|
238 m_mutex.lock();
|
Chris@3
|
239 for (size_t c = 0; c < m_bufferCount; ++c) {
|
Chris@3
|
240 getRingBuffer(c).reset();
|
Chris@3
|
241 }
|
Chris@3
|
242 m_mutex.unlock();
|
Chris@3
|
243 }
|
Chris@3
|
244
|
Chris@3
|
245 void
|
Chris@3
|
246 AudioCallbackPlaySource::playSelectionModeChanged()
|
Chris@3
|
247 {
|
Chris@3
|
248 if (!m_viewManager->getSelections().empty()) {
|
Chris@3
|
249 m_mutex.lock();
|
Chris@3
|
250 for (size_t c = 0; c < m_bufferCount; ++c) {
|
Chris@3
|
251 getRingBuffer(c).reset();
|
Chris@3
|
252 }
|
Chris@3
|
253 m_mutex.unlock();
|
Chris@3
|
254 }
|
Chris@3
|
255 }
|
Chris@3
|
256
|
Chris@3
|
257 void
|
Chris@0
|
258 AudioCallbackPlaySource::setTargetBlockSize(size_t size)
|
Chris@0
|
259 {
|
Chris@0
|
260 std::cerr << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl;
|
Chris@0
|
261 m_blockSize = size;
|
Chris@0
|
262 for (size_t i = 0; i < m_bufferCount; ++i) {
|
Chris@0
|
263 getRingBuffer(i).resize(m_ringBufferSize);
|
Chris@0
|
264 }
|
Chris@0
|
265 }
|
Chris@0
|
266
|
Chris@0
|
267 size_t
|
Chris@0
|
268 AudioCallbackPlaySource::getTargetBlockSize() const
|
Chris@0
|
269 {
|
Chris@0
|
270 std::cerr << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << std::endl;
|
Chris@0
|
271 return m_blockSize;
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@0
|
274 void
|
Chris@0
|
275 AudioCallbackPlaySource::setTargetPlayLatency(size_t latency)
|
Chris@0
|
276 {
|
Chris@0
|
277 m_playLatency = latency;
|
Chris@0
|
278 }
|
Chris@0
|
279
|
Chris@0
|
280 size_t
|
Chris@0
|
281 AudioCallbackPlaySource::getTargetPlayLatency() const
|
Chris@0
|
282 {
|
Chris@0
|
283 return m_playLatency;
|
Chris@0
|
284 }
|
Chris@0
|
285
|
Chris@0
|
286 size_t
|
Chris@0
|
287 AudioCallbackPlaySource::getCurrentPlayingFrame()
|
Chris@0
|
288 {
|
Chris@0
|
289 bool resample = false;
|
Chris@0
|
290 double ratio = 1.0;
|
Chris@0
|
291
|
Chris@0
|
292 if (getSourceSampleRate() != getTargetSampleRate()) {
|
Chris@0
|
293 resample = true;
|
Chris@0
|
294 ratio = double(getSourceSampleRate()) / double(getTargetSampleRate());
|
Chris@0
|
295 }
|
Chris@0
|
296
|
Chris@0
|
297 size_t readSpace = 0;
|
Chris@0
|
298 for (size_t c = 0; c < getSourceChannelCount(); ++c) {
|
Chris@0
|
299 size_t spaceHere = getRingBuffer(c).getReadSpace();
|
Chris@0
|
300 if (c == 0 || spaceHere < readSpace) readSpace = spaceHere;
|
Chris@0
|
301 }
|
Chris@0
|
302
|
Chris@0
|
303 if (resample) {
|
Chris@0
|
304 readSpace = size_t(readSpace * ratio + 0.1);
|
Chris@0
|
305 }
|
Chris@0
|
306
|
Chris@0
|
307 size_t latency = m_playLatency;
|
Chris@0
|
308 if (resample) latency = size_t(m_playLatency * ratio + 0.1);
|
Chris@0
|
309
|
Chris@0
|
310 TimeStretcherData *timeStretcher = m_timeStretcher;
|
Chris@0
|
311 if (timeStretcher) {
|
Chris@0
|
312 latency += timeStretcher->getStretcher(0)->getProcessingLatency();
|
Chris@0
|
313 }
|
Chris@0
|
314
|
Chris@3
|
315 latency += readSpace;
|
Chris@3
|
316 size_t bufferedFrame = m_bufferedToFrame;
|
Chris@3
|
317
|
Chris@3
|
318 size_t framePlaying = bufferedFrame;
|
Chris@3
|
319 if (framePlaying > latency) framePlaying -= latency;
|
Chris@3
|
320 else framePlaying = 0;
|
Chris@3
|
321
|
Chris@3
|
322 if (!m_viewManager->getPlaySelectionMode()) {
|
Chris@3
|
323 return framePlaying;
|
Chris@0
|
324 }
|
Chris@0
|
325
|
Chris@3
|
326 ViewManager::SelectionList selections = m_viewManager->getSelections();
|
Chris@3
|
327 if (selections.empty()) {
|
Chris@3
|
328 return framePlaying;
|
Chris@3
|
329 }
|
Chris@3
|
330
|
Chris@3
|
331 ViewManager::SelectionList::const_iterator i;
|
Chris@3
|
332
|
Chris@3
|
333 for (i = selections.begin(); i != selections.end(); ++i) {
|
Chris@3
|
334 if (i->contains(bufferedFrame)) break;
|
Chris@3
|
335 }
|
Chris@3
|
336
|
Chris@3
|
337 size_t f = bufferedFrame;
|
Chris@3
|
338
|
Chris@3
|
339 std::cerr << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << std::endl;
|
Chris@3
|
340
|
Chris@3
|
341 if (i == selections.end()) {
|
Chris@3
|
342 --i;
|
Chris@3
|
343 if (i->getEndFrame() + latency < f) {
|
Chris@3
|
344 return framePlaying;
|
Chris@3
|
345 } else {
|
Chris@3
|
346 std::cerr << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl;
|
Chris@3
|
347 latency -= (f - i->getEndFrame());
|
Chris@3
|
348 f = i->getEndFrame();
|
Chris@3
|
349 }
|
Chris@3
|
350 }
|
Chris@3
|
351
|
Chris@3
|
352 std::cerr << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl;
|
Chris@3
|
353
|
Chris@3
|
354 while (latency > 0) {
|
Chris@3
|
355 size_t offset = f - i->getStartFrame();
|
Chris@3
|
356 if (offset >= latency) {
|
Chris@3
|
357 if (f > latency) {
|
Chris@3
|
358 framePlaying = f - latency;
|
Chris@3
|
359 } else {
|
Chris@3
|
360 framePlaying = 0;
|
Chris@3
|
361 }
|
Chris@3
|
362 break;
|
Chris@3
|
363 } else {
|
Chris@3
|
364 if (i == selections.begin()) {
|
Chris@3
|
365 if (m_viewManager->getPlayLoopMode()) {
|
Chris@3
|
366 i = selections.end();
|
Chris@3
|
367 }
|
Chris@3
|
368 }
|
Chris@3
|
369 latency -= offset;
|
Chris@3
|
370 --i;
|
Chris@3
|
371 f = i->getEndFrame();
|
Chris@3
|
372 }
|
Chris@3
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 return framePlaying;
|
Chris@0
|
376 }
|
Chris@0
|
377
|
Chris@0
|
378 void
|
Chris@0
|
379 AudioCallbackPlaySource::setOutputLevels(float left, float right)
|
Chris@0
|
380 {
|
Chris@0
|
381 m_outputLeft = left;
|
Chris@0
|
382 m_outputRight = right;
|
Chris@0
|
383 }
|
Chris@0
|
384
|
Chris@0
|
385 bool
|
Chris@0
|
386 AudioCallbackPlaySource::getOutputLevels(float &left, float &right)
|
Chris@0
|
387 {
|
Chris@0
|
388 left = m_outputLeft;
|
Chris@0
|
389 right = m_outputRight;
|
Chris@0
|
390 return true;
|
Chris@0
|
391 }
|
Chris@0
|
392
|
Chris@0
|
393 void
|
Chris@0
|
394 AudioCallbackPlaySource::setTargetSampleRate(size_t sr)
|
Chris@0
|
395 {
|
Chris@0
|
396 m_targetSampleRate = sr;
|
Chris@1
|
397
|
Chris@1
|
398 if (getSourceSampleRate() != getTargetSampleRate()) {
|
Chris@1
|
399
|
Chris@1
|
400 int err = 0;
|
Chris@1
|
401 m_converter = src_new(SRC_SINC_BEST_QUALITY, m_bufferCount, &err);
|
Chris@1
|
402 if (!m_converter) {
|
Chris@1
|
403 std::cerr
|
Chris@1
|
404 << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: "
|
Chris@1
|
405 << src_strerror(err) << std::endl;
|
Chris@1
|
406 }
|
Chris@1
|
407
|
Chris@1
|
408 emit sampleRateMismatch(getSourceSampleRate(), getTargetSampleRate());
|
Chris@1
|
409 }
|
Chris@0
|
410 }
|
Chris@0
|
411
|
Chris@0
|
412 size_t
|
Chris@0
|
413 AudioCallbackPlaySource::getTargetSampleRate() const
|
Chris@0
|
414 {
|
Chris@0
|
415 if (m_targetSampleRate) return m_targetSampleRate;
|
Chris@0
|
416 else return getSourceSampleRate();
|
Chris@0
|
417 }
|
Chris@0
|
418
|
Chris@0
|
419 size_t
|
Chris@0
|
420 AudioCallbackPlaySource::getSourceChannelCount() const
|
Chris@0
|
421 {
|
Chris@0
|
422 return m_bufferCount;
|
Chris@0
|
423 }
|
Chris@0
|
424
|
Chris@0
|
425 size_t
|
Chris@0
|
426 AudioCallbackPlaySource::getSourceSampleRate() const
|
Chris@0
|
427 {
|
Chris@0
|
428 return m_sourceSampleRate;
|
Chris@0
|
429 }
|
Chris@0
|
430
|
Chris@0
|
431 AudioCallbackPlaySource::TimeStretcherData::TimeStretcherData(size_t channels,
|
Chris@0
|
432 size_t factor,
|
Chris@0
|
433 size_t blockSize) :
|
Chris@0
|
434 m_factor(factor),
|
Chris@0
|
435 m_blockSize(blockSize)
|
Chris@0
|
436 {
|
Chris@0
|
437 std::cerr << "TimeStretcherData::TimeStretcherData(" << channels << ", " << factor << ", " << blockSize << ")" << std::endl;
|
Chris@0
|
438
|
Chris@0
|
439 for (size_t ch = 0; ch < channels; ++ch) {
|
Chris@0
|
440 m_stretcher[ch] = StretcherBuffer
|
Chris@0
|
441 //!!! We really need to measure performance and work out
|
Chris@0
|
442 //what sort of quality level to use -- or at least to
|
Chris@0
|
443 //allow the user to configure it
|
Chris@0
|
444 (new IntegerTimeStretcher(factor, blockSize, 128),
|
Chris@0
|
445 new double[blockSize * factor]);
|
Chris@0
|
446 }
|
Chris@0
|
447 m_stretchInputBuffer = new double[blockSize];
|
Chris@0
|
448 }
|
Chris@0
|
449
|
Chris@0
|
450 AudioCallbackPlaySource::TimeStretcherData::~TimeStretcherData()
|
Chris@0
|
451 {
|
Chris@0
|
452 std::cerr << "IntegerTimeStretcher::~IntegerTimeStretcher" << std::endl;
|
Chris@0
|
453
|
Chris@0
|
454 while (!m_stretcher.empty()) {
|
Chris@0
|
455 delete m_stretcher.begin()->second.first;
|
Chris@0
|
456 delete[] m_stretcher.begin()->second.second;
|
Chris@0
|
457 m_stretcher.erase(m_stretcher.begin());
|
Chris@0
|
458 }
|
Chris@0
|
459 delete m_stretchInputBuffer;
|
Chris@0
|
460 }
|
Chris@0
|
461
|
Chris@0
|
462 IntegerTimeStretcher *
|
Chris@0
|
463 AudioCallbackPlaySource::TimeStretcherData::getStretcher(size_t channel)
|
Chris@0
|
464 {
|
Chris@0
|
465 return m_stretcher[channel].first;
|
Chris@0
|
466 }
|
Chris@0
|
467
|
Chris@0
|
468 double *
|
Chris@0
|
469 AudioCallbackPlaySource::TimeStretcherData::getOutputBuffer(size_t channel)
|
Chris@0
|
470 {
|
Chris@0
|
471 return m_stretcher[channel].second;
|
Chris@0
|
472 }
|
Chris@0
|
473
|
Chris@0
|
474 double *
|
Chris@0
|
475 AudioCallbackPlaySource::TimeStretcherData::getInputBuffer()
|
Chris@0
|
476 {
|
Chris@0
|
477 return m_stretchInputBuffer;
|
Chris@0
|
478 }
|
Chris@0
|
479
|
Chris@0
|
480 void
|
Chris@0
|
481 AudioCallbackPlaySource::TimeStretcherData::run(size_t channel)
|
Chris@0
|
482 {
|
Chris@0
|
483 getStretcher(channel)->process(getInputBuffer(),
|
Chris@0
|
484 getOutputBuffer(channel),
|
Chris@0
|
485 m_blockSize);
|
Chris@0
|
486 }
|
Chris@0
|
487
|
Chris@0
|
488 void
|
Chris@0
|
489 AudioCallbackPlaySource::setSlowdownFactor(size_t factor)
|
Chris@0
|
490 {
|
Chris@0
|
491 // Avoid locks -- create, assign, mark old one for scavenging
|
Chris@0
|
492 // later (as a call to getSourceSamples may still be using it)
|
Chris@0
|
493
|
Chris@0
|
494 TimeStretcherData *existingStretcher = m_timeStretcher;
|
Chris@0
|
495
|
Chris@0
|
496 if (existingStretcher && existingStretcher->getFactor() == factor) {
|
Chris@0
|
497 return;
|
Chris@0
|
498 }
|
Chris@0
|
499
|
Chris@0
|
500 if (factor > 1) {
|
Chris@0
|
501 TimeStretcherData *newStretcher = new TimeStretcherData
|
Chris@0
|
502 (getSourceChannelCount(), factor, getTargetBlockSize());
|
Chris@0
|
503 m_slowdownCounter = 0;
|
Chris@0
|
504 m_timeStretcher = newStretcher;
|
Chris@0
|
505 } else {
|
Chris@0
|
506 m_timeStretcher = 0;
|
Chris@0
|
507 }
|
Chris@0
|
508
|
Chris@0
|
509 if (existingStretcher) {
|
Chris@0
|
510 m_timeStretcherScavenger.claim(existingStretcher);
|
Chris@0
|
511 }
|
Chris@0
|
512 }
|
Chris@0
|
513
|
Chris@0
|
514 size_t
|
Chris@0
|
515 AudioCallbackPlaySource::getSourceSamples(size_t count, float **buffer)
|
Chris@0
|
516 {
|
Chris@0
|
517 if (!m_playing) {
|
Chris@0
|
518 for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
|
Chris@0
|
519 for (size_t i = 0; i < count; ++i) {
|
Chris@0
|
520 buffer[ch][i] = 0.0;
|
Chris@0
|
521 }
|
Chris@0
|
522 }
|
Chris@0
|
523 return 0;
|
Chris@0
|
524 }
|
Chris@0
|
525
|
Chris@0
|
526 TimeStretcherData *timeStretcher = m_timeStretcher;
|
Chris@0
|
527
|
Chris@0
|
528 if (!timeStretcher || timeStretcher->getFactor() == 1) {
|
Chris@0
|
529
|
Chris@0
|
530 size_t got = 0;
|
Chris@0
|
531
|
Chris@0
|
532 for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
|
Chris@0
|
533
|
Chris@0
|
534 RingBuffer<float> &rb = *m_buffers[ch];
|
Chris@0
|
535
|
Chris@0
|
536 // this is marginally more likely to leave our channels in
|
Chris@0
|
537 // sync after a processing failure than just passing "count":
|
Chris@0
|
538 size_t request = count;
|
Chris@0
|
539 if (ch > 0) request = got;
|
Chris@0
|
540
|
Chris@0
|
541 got = rb.read(buffer[ch], request);
|
Chris@0
|
542
|
Chris@0
|
543 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
544 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", signalling for more (possibly)" << std::endl;
|
Chris@0
|
545 #endif
|
Chris@0
|
546 }
|
Chris@0
|
547
|
Chris@0
|
548 for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
|
Chris@0
|
549 for (size_t i = got; i < count; ++i) {
|
Chris@0
|
550 buffer[ch][i] = 0.0;
|
Chris@0
|
551 }
|
Chris@0
|
552 }
|
Chris@0
|
553
|
Chris@0
|
554 m_condition.wakeAll();
|
Chris@0
|
555 return got;
|
Chris@0
|
556 }
|
Chris@0
|
557
|
Chris@0
|
558 if (m_slowdownCounter == 0) {
|
Chris@0
|
559
|
Chris@0
|
560 size_t got = 0;
|
Chris@0
|
561 double *ib = timeStretcher->getInputBuffer();
|
Chris@0
|
562
|
Chris@0
|
563 for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
|
Chris@0
|
564
|
Chris@0
|
565 RingBuffer<float> &rb = *m_buffers[ch];
|
Chris@0
|
566 size_t request = count;
|
Chris@0
|
567 if (ch > 0) request = got; // see above
|
Chris@0
|
568 got = rb.read(buffer[ch], request);
|
Chris@0
|
569
|
Chris@0
|
570 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
571 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl;
|
Chris@0
|
572 #endif
|
Chris@0
|
573
|
Chris@0
|
574 for (size_t i = 0; i < count; ++i) {
|
Chris@0
|
575 ib[i] = buffer[ch][i];
|
Chris@0
|
576 }
|
Chris@0
|
577
|
Chris@0
|
578 timeStretcher->run(ch);
|
Chris@0
|
579 }
|
Chris@0
|
580
|
Chris@0
|
581 } else if (m_slowdownCounter >= timeStretcher->getFactor()) {
|
Chris@0
|
582 // reset this in case the factor has changed leaving the
|
Chris@0
|
583 // counter out of range
|
Chris@0
|
584 m_slowdownCounter = 0;
|
Chris@0
|
585 }
|
Chris@0
|
586
|
Chris@0
|
587 for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) {
|
Chris@0
|
588
|
Chris@0
|
589 double *ob = timeStretcher->getOutputBuffer(ch);
|
Chris@0
|
590
|
Chris@0
|
591 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
592 std::cerr << "AudioCallbackPlaySource::getSamples: Copying from (" << (m_slowdownCounter * count) << "," << count << ") to buffer" << std::endl;
|
Chris@0
|
593 #endif
|
Chris@0
|
594
|
Chris@0
|
595 for (size_t i = 0; i < count; ++i) {
|
Chris@0
|
596 buffer[ch][i] = ob[m_slowdownCounter * count + i];
|
Chris@0
|
597 }
|
Chris@0
|
598 }
|
Chris@0
|
599
|
Chris@0
|
600 if (m_slowdownCounter == 0) m_condition.wakeAll();
|
Chris@0
|
601 m_slowdownCounter = (m_slowdownCounter + 1) % timeStretcher->getFactor();
|
Chris@0
|
602 return count;
|
Chris@0
|
603 }
|
Chris@0
|
604
|
Chris@0
|
605 void
|
Chris@0
|
606 AudioCallbackPlaySource::fillBuffers()
|
Chris@0
|
607 {
|
Chris@0
|
608 static float *tmp = 0;
|
Chris@0
|
609 static size_t tmpSize = 0;
|
Chris@0
|
610
|
Chris@0
|
611 size_t space = 0;
|
Chris@0
|
612 for (size_t c = 0; c < m_bufferCount; ++c) {
|
Chris@0
|
613 size_t spaceHere = getRingBuffer(c).getWriteSpace();
|
Chris@0
|
614 if (c == 0 || spaceHere < space) space = spaceHere;
|
Chris@0
|
615 }
|
Chris@0
|
616
|
Chris@0
|
617 if (space == 0) return;
|
Chris@0
|
618
|
Chris@0
|
619 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
620 std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl;
|
Chris@0
|
621 #endif
|
Chris@0
|
622
|
Chris@0
|
623 size_t f = m_bufferedToFrame;
|
Chris@0
|
624
|
Chris@0
|
625 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
626 std::cout << "buffered to " << f << " already" << std::endl;
|
Chris@0
|
627 #endif
|
Chris@0
|
628
|
Chris@0
|
629 bool resample = (getSourceSampleRate() != getTargetSampleRate());
|
Chris@1
|
630
|
Chris@1
|
631 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@1
|
632 std::cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << std::endl;
|
Chris@1
|
633 #endif
|
Chris@1
|
634
|
Chris@0
|
635 size_t channels = getSourceChannelCount();
|
Chris@0
|
636 size_t orig = space;
|
Chris@0
|
637 size_t got = 0;
|
Chris@0
|
638
|
Chris@0
|
639 static float **bufferPtrs = 0;
|
Chris@0
|
640 static size_t bufferPtrCount = 0;
|
Chris@0
|
641
|
Chris@0
|
642 if (bufferPtrCount < channels) {
|
Chris@0
|
643 if (bufferPtrs) delete[] bufferPtrs;
|
Chris@0
|
644 bufferPtrs = new float *[channels];
|
Chris@0
|
645 bufferPtrCount = channels;
|
Chris@0
|
646 }
|
Chris@0
|
647
|
Chris@0
|
648 size_t generatorBlockSize = m_audioGenerator->getBlockSize();
|
Chris@0
|
649
|
Chris@1
|
650 if (resample && !m_converter) {
|
Chris@1
|
651 static bool warned = false;
|
Chris@1
|
652 if (!warned) {
|
Chris@1
|
653 std::cerr << "WARNING: sample rates differ, but no converter available!" << std::endl;
|
Chris@1
|
654 warned = true;
|
Chris@1
|
655 }
|
Chris@1
|
656 }
|
Chris@1
|
657
|
Chris@0
|
658 if (resample && m_converter) {
|
Chris@0
|
659
|
Chris@0
|
660 double ratio =
|
Chris@0
|
661 double(getTargetSampleRate()) / double(getSourceSampleRate());
|
Chris@0
|
662 orig = size_t(orig / ratio + 0.1);
|
Chris@0
|
663
|
Chris@0
|
664 // orig must be a multiple of generatorBlockSize
|
Chris@0
|
665 orig = (orig / generatorBlockSize) * generatorBlockSize;
|
Chris@0
|
666 if (orig == 0) return;
|
Chris@0
|
667
|
Chris@0
|
668 size_t work = std::max(orig, space);
|
Chris@0
|
669
|
Chris@0
|
670 // We only allocate one buffer, but we use it in two halves.
|
Chris@0
|
671 // We place the non-interleaved values in the second half of
|
Chris@0
|
672 // the buffer (orig samples for channel 0, orig samples for
|
Chris@0
|
673 // channel 1 etc), and then interleave them into the first
|
Chris@0
|
674 // half of the buffer. Then we resample back into the second
|
Chris@0
|
675 // half (interleaved) and de-interleave the results back to
|
Chris@0
|
676 // the start of the buffer for insertion into the ringbuffers.
|
Chris@0
|
677 // What a faff -- especially as we've already de-interleaved
|
Chris@0
|
678 // the audio data from the source file elsewhere before we
|
Chris@0
|
679 // even reach this point.
|
Chris@0
|
680
|
Chris@0
|
681 if (tmpSize < channels * work * 2) {
|
Chris@0
|
682 delete[] tmp;
|
Chris@0
|
683 tmp = new float[channels * work * 2];
|
Chris@0
|
684 tmpSize = channels * work * 2;
|
Chris@0
|
685 }
|
Chris@0
|
686
|
Chris@0
|
687 float *nonintlv = tmp + channels * work;
|
Chris@0
|
688 float *intlv = tmp;
|
Chris@0
|
689 float *srcout = tmp + channels * work;
|
Chris@0
|
690
|
Chris@0
|
691 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
692 for (size_t i = 0; i < orig; ++i) {
|
Chris@0
|
693 nonintlv[channels * i + c] = 0.0f;
|
Chris@0
|
694 }
|
Chris@0
|
695 }
|
Chris@0
|
696
|
Chris@3
|
697 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
698 bufferPtrs[c] = nonintlv + c * orig;
|
Chris@3
|
699 }
|
Chris@0
|
700
|
Chris@3
|
701 bool ended = !mixModels(f, orig, bufferPtrs);
|
Chris@3
|
702 got = orig;
|
Chris@0
|
703
|
Chris@0
|
704 // and interleave into first half
|
Chris@0
|
705 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
706 for (size_t i = 0; i < orig; ++i) {
|
Chris@0
|
707 float sample = 0;
|
Chris@0
|
708 if (i < got) {
|
Chris@0
|
709 sample = nonintlv[c * orig + i];
|
Chris@0
|
710 }
|
Chris@0
|
711 intlv[channels * i + c] = sample;
|
Chris@0
|
712 }
|
Chris@0
|
713 }
|
Chris@0
|
714
|
Chris@0
|
715 SRC_DATA data;
|
Chris@0
|
716 data.data_in = intlv;
|
Chris@0
|
717 data.data_out = srcout;
|
Chris@0
|
718 data.input_frames = orig;
|
Chris@0
|
719 data.output_frames = work;
|
Chris@0
|
720 data.src_ratio = ratio;
|
Chris@0
|
721 data.end_of_input = 0;
|
Chris@0
|
722
|
Chris@0
|
723 int err = src_process(m_converter, &data);
|
Chris@0
|
724 size_t toCopy = size_t(work * ratio + 0.1);
|
Chris@0
|
725
|
Chris@0
|
726 if (err) {
|
Chris@0
|
727 std::cerr
|
Chris@0
|
728 << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: "
|
Chris@0
|
729 << src_strerror(err) << std::endl;
|
Chris@0
|
730 //!!! Then what?
|
Chris@0
|
731 } else {
|
Chris@0
|
732 got = data.input_frames_used;
|
Chris@0
|
733 toCopy = data.output_frames_gen;
|
Chris@0
|
734 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
735 std::cerr << "Resampled " << got << " frames to " << toCopy << " frames" << std::endl;
|
Chris@0
|
736 #endif
|
Chris@0
|
737 }
|
Chris@0
|
738
|
Chris@0
|
739 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
740 for (size_t i = 0; i < toCopy; ++i) {
|
Chris@0
|
741 tmp[i] = srcout[channels * i + c];
|
Chris@0
|
742 }
|
Chris@0
|
743 getRingBuffer(c).write(tmp, toCopy);
|
Chris@0
|
744 }
|
Chris@3
|
745
|
Chris@3
|
746 m_bufferedToFrame = f;
|
Chris@0
|
747
|
Chris@0
|
748 } else {
|
Chris@0
|
749
|
Chris@0
|
750 // space must be a multiple of generatorBlockSize
|
Chris@0
|
751 space = (space / generatorBlockSize) * generatorBlockSize;
|
Chris@0
|
752 if (space == 0) return;
|
Chris@0
|
753
|
Chris@0
|
754 if (tmpSize < channels * space) {
|
Chris@0
|
755 delete[] tmp;
|
Chris@0
|
756 tmp = new float[channels * space];
|
Chris@0
|
757 tmpSize = channels * space;
|
Chris@0
|
758 }
|
Chris@0
|
759
|
Chris@0
|
760 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
761
|
Chris@0
|
762 bufferPtrs[c] = tmp + c * space;
|
Chris@3
|
763
|
Chris@0
|
764 for (size_t i = 0; i < space; ++i) {
|
Chris@0
|
765 tmp[c * space + i] = 0.0f;
|
Chris@0
|
766 }
|
Chris@0
|
767 }
|
Chris@0
|
768
|
Chris@3
|
769 bool ended = !mixModels(f, space, bufferPtrs);
|
Chris@0
|
770
|
Chris@0
|
771 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
772
|
Chris@3
|
773 getRingBuffer(c).write(bufferPtrs[c], space);
|
Chris@0
|
774
|
Chris@0
|
775 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
776 std::cerr << "Wrote " << got << " frames for ch " << c << ", now "
|
Chris@0
|
777 << getRingBuffer(c).getReadSpace() << " to read"
|
Chris@0
|
778 << std::endl;
|
Chris@0
|
779 #endif
|
Chris@0
|
780 }
|
Chris@3
|
781
|
Chris@3
|
782 m_bufferedToFrame = f;
|
Chris@3
|
783 //!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples
|
Chris@0
|
784 }
|
Chris@3
|
785 }
|
Chris@3
|
786
|
Chris@3
|
787 bool
|
Chris@3
|
788 AudioCallbackPlaySource::mixModels(size_t &frame, size_t count, float **buffers)
|
Chris@3
|
789 {
|
Chris@3
|
790 size_t processed = 0;
|
Chris@3
|
791 size_t chunkStart = frame;
|
Chris@3
|
792 size_t chunkSize = count;
|
Chris@3
|
793 size_t nextChunkStart = chunkStart + chunkSize;
|
Chris@0
|
794
|
Chris@3
|
795 bool useSelection = (m_viewManager->getPlaySelectionMode() &&
|
Chris@3
|
796 !m_viewManager->getSelections().empty());
|
Chris@3
|
797
|
Chris@3
|
798 static float **chunkBufferPtrs = 0;
|
Chris@3
|
799 static size_t chunkBufferPtrCount = 0;
|
Chris@3
|
800 size_t channels = getSourceChannelCount();
|
Chris@3
|
801
|
Chris@3
|
802 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
803 std::cerr << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << std::endl;
|
Chris@3
|
804 #endif
|
Chris@3
|
805
|
Chris@3
|
806 if (chunkBufferPtrCount < channels) {
|
Chris@3
|
807 if (chunkBufferPtrs) delete[] chunkBufferPtrs;
|
Chris@3
|
808 chunkBufferPtrs = new float *[channels];
|
Chris@3
|
809 chunkBufferPtrCount = channels;
|
Chris@3
|
810 }
|
Chris@3
|
811
|
Chris@3
|
812 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
813 chunkBufferPtrs[c] = buffers[c];
|
Chris@3
|
814 }
|
Chris@3
|
815
|
Chris@3
|
816 while (processed < count) {
|
Chris@3
|
817
|
Chris@3
|
818 chunkSize = count - processed;
|
Chris@3
|
819 nextChunkStart = chunkStart + chunkSize;
|
Chris@3
|
820
|
Chris@3
|
821 if (useSelection) {
|
Chris@3
|
822
|
Chris@3
|
823 Selection selection =
|
Chris@3
|
824 m_viewManager->getContainingSelection(chunkStart, true);
|
Chris@3
|
825
|
Chris@3
|
826 if (selection.isEmpty()) {
|
Chris@3
|
827 if (m_viewManager->getPlayLoopMode()) {
|
Chris@3
|
828 selection = *m_viewManager->getSelections().begin();
|
Chris@3
|
829 chunkStart = selection.getStartFrame();
|
Chris@3
|
830 }
|
Chris@3
|
831 }
|
Chris@3
|
832
|
Chris@3
|
833 if (selection.isEmpty()) {
|
Chris@3
|
834
|
Chris@3
|
835 chunkSize = 0;
|
Chris@3
|
836 nextChunkStart = chunkStart;
|
Chris@3
|
837
|
Chris@3
|
838 } else {
|
Chris@3
|
839
|
Chris@3
|
840 if (chunkStart < selection.getStartFrame()) {
|
Chris@3
|
841 chunkStart = selection.getStartFrame();
|
Chris@3
|
842 }
|
Chris@3
|
843
|
Chris@3
|
844 nextChunkStart = std::min(chunkStart + chunkSize,
|
Chris@3
|
845 selection.getEndFrame());
|
Chris@3
|
846
|
Chris@3
|
847 chunkSize = nextChunkStart - chunkStart;
|
Chris@3
|
848 }
|
Chris@3
|
849 }
|
Chris@3
|
850
|
Chris@3
|
851 if (!chunkSize) {
|
Chris@3
|
852 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
853 std::cerr << "Ending selection playback at " << nextChunkStart << std::endl;
|
Chris@3
|
854 #endif
|
Chris@3
|
855 // We need to maintain full buffers so that the other
|
Chris@3
|
856 // thread can tell where it's got to in the playback -- so
|
Chris@3
|
857 // return the full amount here
|
Chris@3
|
858 frame = frame + count;
|
Chris@3
|
859 return false;
|
Chris@3
|
860 }
|
Chris@3
|
861
|
Chris@3
|
862 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
863 std::cerr << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << std::endl;
|
Chris@3
|
864 #endif
|
Chris@3
|
865
|
Chris@3
|
866 size_t got = 0;
|
Chris@3
|
867
|
Chris@3
|
868 for (std::set<Model *>::iterator mi = m_models.begin();
|
Chris@3
|
869 mi != m_models.end(); ++mi) {
|
Chris@3
|
870
|
Chris@3
|
871 got = m_audioGenerator->mixModel(*mi, chunkStart,
|
Chris@3
|
872 chunkSize, chunkBufferPtrs);
|
Chris@3
|
873 }
|
Chris@3
|
874
|
Chris@3
|
875 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
876 chunkBufferPtrs[c] += chunkSize;
|
Chris@3
|
877 }
|
Chris@3
|
878
|
Chris@3
|
879 processed += chunkSize;
|
Chris@3
|
880 chunkStart = nextChunkStart;
|
Chris@3
|
881 }
|
Chris@3
|
882
|
Chris@3
|
883 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
884 std::cerr << "Returning selection playback at " << nextChunkStart << std::endl;
|
Chris@3
|
885 #endif
|
Chris@3
|
886
|
Chris@3
|
887 frame = nextChunkStart;
|
Chris@3
|
888 return true;
|
Chris@3
|
889 }
|
Chris@0
|
890
|
Chris@0
|
891 void
|
Chris@0
|
892 AudioCallbackPlaySource::AudioCallbackPlaySourceFillThread::run()
|
Chris@0
|
893 {
|
Chris@0
|
894 AudioCallbackPlaySource &s(m_source);
|
Chris@0
|
895
|
Chris@0
|
896 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
897 std::cerr << "AudioCallbackPlaySourceFillThread starting" << std::endl;
|
Chris@0
|
898 #endif
|
Chris@0
|
899
|
Chris@0
|
900 s.m_mutex.lock();
|
Chris@0
|
901
|
Chris@0
|
902 bool previouslyPlaying = s.m_playing;
|
Chris@0
|
903
|
Chris@0
|
904 while (!s.m_exiting) {
|
Chris@0
|
905
|
Chris@0
|
906 s.m_timeStretcherScavenger.scavenge();
|
Chris@0
|
907
|
Chris@0
|
908 float ms = 100;
|
Chris@0
|
909 if (s.getSourceSampleRate() > 0) {
|
Chris@0
|
910 ms = float(m_ringBufferSize) / float(s.getSourceSampleRate()) * 1000.0;
|
Chris@0
|
911 }
|
Chris@0
|
912
|
Chris@0
|
913 if (!s.m_playing) ms *= 10;
|
Chris@0
|
914
|
Chris@0
|
915 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
916 std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms/4 << "ms..." << std::endl;
|
Chris@0
|
917 #endif
|
Chris@0
|
918
|
Chris@0
|
919 s.m_condition.wait(&s.m_mutex, size_t(ms / 4));
|
Chris@0
|
920
|
Chris@0
|
921 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
922 std::cout << "AudioCallbackPlaySourceFillThread: awoken" << std::endl;
|
Chris@0
|
923 #endif
|
Chris@0
|
924
|
Chris@0
|
925 if (!s.getSourceSampleRate()) continue;
|
Chris@0
|
926
|
Chris@0
|
927 bool playing = s.m_playing;
|
Chris@0
|
928
|
Chris@0
|
929 if (playing && !previouslyPlaying) {
|
Chris@0
|
930 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
931 std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl;
|
Chris@0
|
932 #endif
|
Chris@0
|
933 for (size_t c = 0; c < s.getSourceChannelCount(); ++c) {
|
Chris@0
|
934 s.getRingBuffer(c).reset();
|
Chris@0
|
935 }
|
Chris@0
|
936 }
|
Chris@0
|
937 previouslyPlaying = playing;
|
Chris@0
|
938
|
Chris@0
|
939 if (!playing) continue;
|
Chris@0
|
940
|
Chris@0
|
941 s.fillBuffers();
|
Chris@0
|
942 }
|
Chris@0
|
943
|
Chris@0
|
944 s.m_mutex.unlock();
|
Chris@0
|
945 }
|
Chris@0
|
946
|
Chris@0
|
947
|
Chris@0
|
948
|
Chris@0
|
949 #ifdef INCLUDE_MOCFILES
|
Chris@0
|
950 #include "AudioCallbackPlaySource.moc.cpp"
|
Chris@0
|
951 #endif
|
Chris@0
|
952
|