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@12
|
16 #include "base/PlayParameterRepository.h"
|
Chris@0
|
17 #include "model/DenseTimeValueModel.h"
|
Chris@0
|
18 #include "model/SparseOneDimensionalModel.h"
|
Chris@0
|
19 #include "dsp/timestretching/IntegerTimeStretcher.h"
|
Chris@0
|
20
|
Chris@0
|
21 #include <iostream>
|
Chris@6
|
22 #include <cassert>
|
Chris@0
|
23
|
Chris@0
|
24 //#define DEBUG_AUDIO_PLAY_SOURCE 1
|
Chris@13
|
25 //#define DEBUG_AUDIO_PLAY_SOURCE_PLAYING 1
|
Chris@0
|
26
|
Chris@0
|
27 //const size_t AudioCallbackPlaySource::m_ringBufferSize = 102400;
|
Chris@0
|
28 const size_t AudioCallbackPlaySource::m_ringBufferSize = 131071;
|
Chris@0
|
29
|
Chris@0
|
30 AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManager *manager) :
|
Chris@0
|
31 m_viewManager(manager),
|
Chris@0
|
32 m_audioGenerator(new AudioGenerator(manager)),
|
Chris@6
|
33 m_readBuffers(0),
|
Chris@6
|
34 m_writeBuffers(0),
|
Chris@13
|
35 m_readBufferFill(0),
|
Chris@13
|
36 m_writeBufferFill(0),
|
Chris@13
|
37 m_bufferScavenger(1),
|
Chris@6
|
38 m_sourceChannelCount(0),
|
Chris@0
|
39 m_blockSize(1024),
|
Chris@0
|
40 m_sourceSampleRate(0),
|
Chris@0
|
41 m_targetSampleRate(0),
|
Chris@0
|
42 m_playLatency(0),
|
Chris@0
|
43 m_playing(false),
|
Chris@0
|
44 m_exiting(false),
|
Chris@4
|
45 m_lastModelEndFrame(0),
|
Chris@0
|
46 m_outputLeft(0.0),
|
Chris@0
|
47 m_outputRight(0.0),
|
Chris@0
|
48 m_slowdownCounter(0),
|
Chris@0
|
49 m_timeStretcher(0),
|
Chris@0
|
50 m_fillThread(0),
|
Chris@0
|
51 m_converter(0)
|
Chris@0
|
52 {
|
Chris@0
|
53 m_viewManager->setAudioPlaySource(this);
|
Chris@3
|
54
|
Chris@3
|
55 connect(m_viewManager, SIGNAL(selectionChanged()),
|
Chris@3
|
56 this, SLOT(selectionChanged()));
|
Chris@3
|
57 connect(m_viewManager, SIGNAL(playLoopModeChanged()),
|
Chris@3
|
58 this, SLOT(playLoopModeChanged()));
|
Chris@3
|
59 connect(m_viewManager, SIGNAL(playSelectionModeChanged()),
|
Chris@3
|
60 this, SLOT(playSelectionModeChanged()));
|
Chris@12
|
61
|
Chris@12
|
62 connect(PlayParameterRepository::instance(),
|
Chris@12
|
63 SIGNAL(playParametersChanged(PlayParameters *)),
|
Chris@12
|
64 this, SLOT(playParametersChanged(PlayParameters *)));
|
Chris@0
|
65 }
|
Chris@0
|
66
|
Chris@0
|
67 AudioCallbackPlaySource::~AudioCallbackPlaySource()
|
Chris@0
|
68 {
|
Chris@0
|
69 m_exiting = true;
|
Chris@0
|
70
|
Chris@0
|
71 if (m_fillThread) {
|
Chris@0
|
72 m_condition.wakeAll();
|
Chris@0
|
73 m_fillThread->wait();
|
Chris@0
|
74 delete m_fillThread;
|
Chris@0
|
75 }
|
Chris@0
|
76
|
Chris@0
|
77 clearModels();
|
Chris@6
|
78
|
Chris@6
|
79 if (m_readBuffers != m_writeBuffers) {
|
Chris@6
|
80 delete m_readBuffers;
|
Chris@6
|
81 }
|
Chris@6
|
82
|
Chris@6
|
83 delete m_writeBuffers;
|
Chris@6
|
84
|
Chris@6
|
85 m_bufferScavenger.scavenge(true);
|
Chris@0
|
86 }
|
Chris@0
|
87
|
Chris@0
|
88 void
|
Chris@0
|
89 AudioCallbackPlaySource::addModel(Model *model)
|
Chris@0
|
90 {
|
Chris@10
|
91 bool canPlay = m_audioGenerator->addModel(model);
|
Chris@8
|
92
|
Chris@0
|
93 m_mutex.lock();
|
Chris@0
|
94
|
Chris@0
|
95 m_models.insert(model);
|
Chris@4
|
96 if (model->getEndFrame() > m_lastModelEndFrame) {
|
Chris@4
|
97 m_lastModelEndFrame = model->getEndFrame();
|
Chris@4
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 bool buffersChanged = false, srChanged = false;
|
Chris@0
|
101
|
Chris@0
|
102 if (m_sourceSampleRate == 0) {
|
Chris@0
|
103
|
Chris@0
|
104 m_sourceSampleRate = model->getSampleRate();
|
Chris@0
|
105 srChanged = true;
|
Chris@0
|
106
|
Chris@0
|
107 } else if (model->getSampleRate() != m_sourceSampleRate) {
|
Chris@0
|
108 std::cerr << "AudioCallbackPlaySource::addModel: ERROR: "
|
Chris@0
|
109 << "New model sample rate does not match" << std::endl
|
Chris@0
|
110 << "existing model(s) (new " << model->getSampleRate()
|
Chris@0
|
111 << " vs " << m_sourceSampleRate
|
Chris@0
|
112 << "), playback will be wrong"
|
Chris@0
|
113 << std::endl;
|
Chris@0
|
114 }
|
Chris@0
|
115
|
Chris@0
|
116 size_t modelChannels = 1;
|
Chris@0
|
117 DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
|
Chris@0
|
118 if (dtvm) modelChannels = dtvm->getChannelCount();
|
Chris@6
|
119 if (modelChannels > m_sourceChannelCount) {
|
Chris@6
|
120 m_sourceChannelCount = modelChannels;
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@6
|
123 std::cerr << "Adding model with " << modelChannels << " channels " << std::endl;
|
Chris@6
|
124
|
Chris@13
|
125 if (!m_writeBuffers || (m_writeBuffers->size() < getTargetChannelCount())) {
|
Chris@13
|
126 clearRingBuffers(true, getTargetChannelCount());
|
Chris@6
|
127 buffersChanged = true;
|
Chris@6
|
128 } else {
|
Chris@10
|
129 if (canPlay) clearRingBuffers(true);
|
Chris@0
|
130 }
|
Chris@0
|
131
|
Chris@0
|
132 if (buffersChanged || srChanged) {
|
Chris@0
|
133 if (m_converter) {
|
Chris@0
|
134 src_delete(m_converter);
|
Chris@0
|
135 m_converter = 0;
|
Chris@0
|
136 }
|
Chris@0
|
137 }
|
Chris@0
|
138
|
Chris@0
|
139 m_mutex.unlock();
|
Chris@0
|
140
|
Chris@13
|
141 m_audioGenerator->setTargetChannelCount(getTargetChannelCount());
|
Chris@13
|
142
|
Chris@0
|
143 if (!m_fillThread) {
|
Chris@0
|
144 m_fillThread = new AudioCallbackPlaySourceFillThread(*this);
|
Chris@0
|
145 m_fillThread->start();
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
149 std::cerr << "AudioCallbackPlaySource::addModel: emitting modelReplaced" << std::endl;
|
Chris@0
|
150 #endif
|
Chris@0
|
151
|
Chris@1
|
152 if (buffersChanged || srChanged) {
|
Chris@1
|
153 emit modelReplaced();
|
Chris@0
|
154 }
|
Chris@6
|
155
|
Chris@6
|
156 m_condition.wakeAll();
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 void
|
Chris@0
|
160 AudioCallbackPlaySource::removeModel(Model *model)
|
Chris@0
|
161 {
|
Chris@0
|
162 m_mutex.lock();
|
Chris@0
|
163
|
Chris@0
|
164 m_models.erase(model);
|
Chris@0
|
165
|
Chris@0
|
166 if (m_models.empty()) {
|
Chris@0
|
167 if (m_converter) {
|
Chris@0
|
168 src_delete(m_converter);
|
Chris@0
|
169 m_converter = 0;
|
Chris@0
|
170 }
|
Chris@0
|
171 m_sourceSampleRate = 0;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@4
|
174 size_t lastEnd = 0;
|
Chris@4
|
175 for (std::set<Model *>::const_iterator i = m_models.begin();
|
Chris@4
|
176 i != m_models.end(); ++i) {
|
Chris@4
|
177 if ((*i)->getEndFrame() > lastEnd) lastEnd = (*i)->getEndFrame();
|
Chris@4
|
178 }
|
Chris@4
|
179 m_lastModelEndFrame = lastEnd;
|
Chris@4
|
180
|
Chris@8
|
181 m_mutex.unlock();
|
Chris@8
|
182
|
Chris@0
|
183 m_audioGenerator->removeModel(model);
|
Chris@0
|
184
|
Chris@6
|
185 clearRingBuffers();
|
Chris@0
|
186 }
|
Chris@0
|
187
|
Chris@0
|
188 void
|
Chris@0
|
189 AudioCallbackPlaySource::clearModels()
|
Chris@0
|
190 {
|
Chris@0
|
191 m_mutex.lock();
|
Chris@0
|
192
|
Chris@0
|
193 m_models.clear();
|
Chris@0
|
194
|
Chris@0
|
195 if (m_converter) {
|
Chris@0
|
196 src_delete(m_converter);
|
Chris@0
|
197 m_converter = 0;
|
Chris@0
|
198 }
|
Chris@0
|
199
|
Chris@4
|
200 m_lastModelEndFrame = 0;
|
Chris@4
|
201
|
Chris@0
|
202 m_sourceSampleRate = 0;
|
Chris@0
|
203
|
Chris@0
|
204 m_mutex.unlock();
|
Chris@8
|
205
|
Chris@8
|
206 m_audioGenerator->clearModels();
|
Chris@0
|
207 }
|
Chris@0
|
208
|
Chris@0
|
209 void
|
Chris@6
|
210 AudioCallbackPlaySource::clearRingBuffers(bool haveLock, size_t count)
|
Chris@6
|
211 {
|
Chris@6
|
212 if (!haveLock) m_mutex.lock();
|
Chris@6
|
213
|
Chris@6
|
214 if (count == 0) {
|
Chris@6
|
215 if (m_writeBuffers) count = m_writeBuffers->size();
|
Chris@6
|
216 }
|
Chris@6
|
217
|
Chris@13
|
218 size_t sf = m_readBufferFill;
|
Chris@13
|
219 RingBuffer<float> *rb = getReadRingBuffer(0);
|
Chris@13
|
220 if (rb) {
|
Chris@13
|
221 //!!! This is incorrect if we're in a non-contiguous selection
|
Chris@13
|
222 //Same goes for all related code (subtracting the read space
|
Chris@13
|
223 //from the fill frame to try to establish where the effective
|
Chris@13
|
224 //pre-resample/timestretch read pointer is)
|
Chris@13
|
225 size_t rs = rb->getReadSpace();
|
Chris@13
|
226 if (rs < sf) sf -= rs;
|
Chris@13
|
227 else sf = 0;
|
Chris@13
|
228 }
|
Chris@13
|
229 m_writeBufferFill = sf;
|
Chris@13
|
230
|
Chris@6
|
231 if (m_readBuffers != m_writeBuffers) {
|
Chris@6
|
232 delete m_writeBuffers;
|
Chris@6
|
233 }
|
Chris@6
|
234
|
Chris@6
|
235 m_writeBuffers = new RingBufferVector;
|
Chris@6
|
236
|
Chris@6
|
237 for (size_t i = 0; i < count; ++i) {
|
Chris@6
|
238 m_writeBuffers->push_back(new RingBuffer<float>(m_ringBufferSize));
|
Chris@6
|
239 }
|
Chris@6
|
240
|
Chris@6
|
241 std::cerr << "AudioCallbackPlaySource::clearRingBuffers: Created "
|
Chris@6
|
242 << count << " write buffers" << std::endl;
|
Chris@6
|
243
|
Chris@6
|
244 if (!haveLock) {
|
Chris@6
|
245 m_mutex.unlock();
|
Chris@8
|
246 //!!! m_condition.wakeAll();
|
Chris@6
|
247 }
|
Chris@6
|
248 }
|
Chris@6
|
249
|
Chris@6
|
250 void
|
Chris@0
|
251 AudioCallbackPlaySource::play(size_t startFrame)
|
Chris@0
|
252 {
|
Chris@5
|
253 if (m_viewManager->getPlaySelectionMode() &&
|
Chris@5
|
254 !m_viewManager->getSelections().empty()) {
|
Chris@9
|
255 MultiSelection::SelectionList selections = m_viewManager->getSelections();
|
Chris@9
|
256 MultiSelection::SelectionList::iterator i = selections.begin();
|
Chris@4
|
257 if (i != selections.end()) {
|
Chris@4
|
258 if (startFrame < i->getStartFrame()) {
|
Chris@4
|
259 startFrame = i->getStartFrame();
|
Chris@4
|
260 } else {
|
Chris@9
|
261 MultiSelection::SelectionList::iterator j = selections.end();
|
Chris@4
|
262 --j;
|
Chris@4
|
263 if (startFrame >= j->getEndFrame()) {
|
Chris@4
|
264 startFrame = i->getStartFrame();
|
Chris@4
|
265 }
|
Chris@4
|
266 }
|
Chris@4
|
267 }
|
Chris@5
|
268 } else {
|
Chris@5
|
269 if (startFrame >= m_lastModelEndFrame) {
|
Chris@5
|
270 startFrame = 0;
|
Chris@5
|
271 }
|
Chris@4
|
272 }
|
Chris@4
|
273
|
Chris@0
|
274 // The fill thread will automatically empty its buffers before
|
Chris@0
|
275 // starting again if we have not so far been playing, but not if
|
Chris@0
|
276 // we're just re-seeking.
|
Chris@0
|
277
|
Chris@6
|
278 m_mutex.lock();
|
Chris@0
|
279 if (m_playing) {
|
Chris@13
|
280 m_readBufferFill = m_writeBufferFill = startFrame;
|
Chris@6
|
281 if (m_readBuffers) {
|
Chris@13
|
282 for (size_t c = 0; c < getTargetChannelCount(); ++c) {
|
Chris@6
|
283 RingBuffer<float> *rb = getReadRingBuffer(c);
|
Chris@6
|
284 if (rb) rb->reset();
|
Chris@6
|
285 }
|
Chris@0
|
286 }
|
Chris@6
|
287 if (m_converter) src_reset(m_converter);
|
Chris@0
|
288 } else {
|
Chris@6
|
289 if (m_converter) src_reset(m_converter);
|
Chris@13
|
290 m_readBufferFill = m_writeBufferFill = startFrame;
|
Chris@0
|
291 }
|
Chris@6
|
292 m_mutex.unlock();
|
Chris@0
|
293
|
Chris@0
|
294 m_audioGenerator->reset();
|
Chris@0
|
295
|
Chris@0
|
296 m_playing = true;
|
Chris@0
|
297 m_condition.wakeAll();
|
Chris@4
|
298 emit playStatusChanged(m_playing);
|
Chris@0
|
299 }
|
Chris@0
|
300
|
Chris@0
|
301 void
|
Chris@0
|
302 AudioCallbackPlaySource::stop()
|
Chris@0
|
303 {
|
Chris@0
|
304 m_playing = false;
|
Chris@0
|
305 m_condition.wakeAll();
|
Chris@4
|
306 emit playStatusChanged(m_playing);
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 void
|
Chris@3
|
310 AudioCallbackPlaySource::selectionChanged()
|
Chris@3
|
311 {
|
Chris@3
|
312 if (m_viewManager->getPlaySelectionMode()) {
|
Chris@6
|
313 clearRingBuffers();
|
Chris@3
|
314 }
|
Chris@3
|
315 }
|
Chris@3
|
316
|
Chris@3
|
317 void
|
Chris@3
|
318 AudioCallbackPlaySource::playLoopModeChanged()
|
Chris@3
|
319 {
|
Chris@6
|
320 clearRingBuffers();
|
Chris@3
|
321 }
|
Chris@3
|
322
|
Chris@3
|
323 void
|
Chris@3
|
324 AudioCallbackPlaySource::playSelectionModeChanged()
|
Chris@3
|
325 {
|
Chris@3
|
326 if (!m_viewManager->getSelections().empty()) {
|
Chris@6
|
327 clearRingBuffers();
|
Chris@3
|
328 }
|
Chris@3
|
329 }
|
Chris@3
|
330
|
Chris@3
|
331 void
|
Chris@12
|
332 AudioCallbackPlaySource::playParametersChanged(PlayParameters *params)
|
Chris@12
|
333 {
|
Chris@12
|
334 clearRingBuffers();
|
Chris@12
|
335 }
|
Chris@12
|
336
|
Chris@12
|
337 void
|
Chris@0
|
338 AudioCallbackPlaySource::setTargetBlockSize(size_t size)
|
Chris@0
|
339 {
|
Chris@0
|
340 std::cerr << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl;
|
Chris@6
|
341 assert(size < m_ringBufferSize);
|
Chris@0
|
342 m_blockSize = size;
|
Chris@0
|
343 }
|
Chris@0
|
344
|
Chris@0
|
345 size_t
|
Chris@0
|
346 AudioCallbackPlaySource::getTargetBlockSize() const
|
Chris@0
|
347 {
|
Chris@0
|
348 std::cerr << "AudioCallbackPlaySource::getTargetBlockSize() -> " << m_blockSize << std::endl;
|
Chris@0
|
349 return m_blockSize;
|
Chris@0
|
350 }
|
Chris@0
|
351
|
Chris@0
|
352 void
|
Chris@0
|
353 AudioCallbackPlaySource::setTargetPlayLatency(size_t latency)
|
Chris@0
|
354 {
|
Chris@0
|
355 m_playLatency = latency;
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 size_t
|
Chris@0
|
359 AudioCallbackPlaySource::getTargetPlayLatency() const
|
Chris@0
|
360 {
|
Chris@0
|
361 return m_playLatency;
|
Chris@0
|
362 }
|
Chris@0
|
363
|
Chris@0
|
364 size_t
|
Chris@0
|
365 AudioCallbackPlaySource::getCurrentPlayingFrame()
|
Chris@0
|
366 {
|
Chris@0
|
367 bool resample = false;
|
Chris@0
|
368 double ratio = 1.0;
|
Chris@0
|
369
|
Chris@0
|
370 if (getSourceSampleRate() != getTargetSampleRate()) {
|
Chris@0
|
371 resample = true;
|
Chris@0
|
372 ratio = double(getSourceSampleRate()) / double(getTargetSampleRate());
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 size_t readSpace = 0;
|
Chris@13
|
376 for (size_t c = 0; c < getTargetChannelCount(); ++c) {
|
Chris@6
|
377 RingBuffer<float> *rb = getReadRingBuffer(c);
|
Chris@6
|
378 if (rb) {
|
Chris@6
|
379 size_t spaceHere = rb->getReadSpace();
|
Chris@6
|
380 if (c == 0 || spaceHere < readSpace) readSpace = spaceHere;
|
Chris@6
|
381 }
|
Chris@0
|
382 }
|
Chris@0
|
383
|
Chris@0
|
384 if (resample) {
|
Chris@0
|
385 readSpace = size_t(readSpace * ratio + 0.1);
|
Chris@0
|
386 }
|
Chris@0
|
387
|
Chris@0
|
388 size_t latency = m_playLatency;
|
Chris@0
|
389 if (resample) latency = size_t(m_playLatency * ratio + 0.1);
|
Chris@0
|
390
|
Chris@0
|
391 TimeStretcherData *timeStretcher = m_timeStretcher;
|
Chris@0
|
392 if (timeStretcher) {
|
Chris@0
|
393 latency += timeStretcher->getStretcher(0)->getProcessingLatency();
|
Chris@0
|
394 }
|
Chris@0
|
395
|
Chris@3
|
396 latency += readSpace;
|
Chris@13
|
397 size_t bufferedFrame = m_readBufferFill;
|
Chris@3
|
398
|
Chris@5
|
399 bool looping = m_viewManager->getPlayLoopMode();
|
Chris@5
|
400 bool constrained = (m_viewManager->getPlaySelectionMode() &&
|
Chris@5
|
401 !m_viewManager->getSelections().empty());
|
Chris@5
|
402
|
Chris@3
|
403 size_t framePlaying = bufferedFrame;
|
Chris@5
|
404
|
Chris@5
|
405 if (looping && !constrained) {
|
Chris@5
|
406 while (framePlaying < latency) framePlaying += m_lastModelEndFrame;
|
Chris@4
|
407 }
|
Chris@3
|
408
|
Chris@5
|
409 if (framePlaying > latency) framePlaying -= latency;
|
Chris@5
|
410 else framePlaying = 0;
|
Chris@5
|
411
|
Chris@5
|
412 if (!constrained) {
|
Chris@5
|
413 if (!looping && framePlaying > m_lastModelEndFrame) {
|
Chris@5
|
414 framePlaying = m_lastModelEndFrame;
|
Chris@5
|
415 stop();
|
Chris@5
|
416 }
|
Chris@3
|
417 return framePlaying;
|
Chris@0
|
418 }
|
Chris@0
|
419
|
Chris@9
|
420 MultiSelection::SelectionList selections = m_viewManager->getSelections();
|
Chris@9
|
421 MultiSelection::SelectionList::const_iterator i;
|
Chris@3
|
422
|
Chris@4
|
423 i = selections.begin();
|
Chris@4
|
424 size_t rangeStart = i->getStartFrame();
|
Chris@4
|
425
|
Chris@4
|
426 i = selections.end();
|
Chris@4
|
427 --i;
|
Chris@4
|
428 size_t rangeEnd = i->getEndFrame();
|
Chris@4
|
429
|
Chris@3
|
430 for (i = selections.begin(); i != selections.end(); ++i) {
|
Chris@3
|
431 if (i->contains(bufferedFrame)) break;
|
Chris@3
|
432 }
|
Chris@3
|
433
|
Chris@3
|
434 size_t f = bufferedFrame;
|
Chris@3
|
435
|
Chris@4
|
436 // std::cerr << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << ", rangeEnd=" << rangeEnd << std::endl;
|
Chris@3
|
437
|
Chris@3
|
438 if (i == selections.end()) {
|
Chris@3
|
439 --i;
|
Chris@3
|
440 if (i->getEndFrame() + latency < f) {
|
Chris@4
|
441 // std::cerr << "framePlaying = " << framePlaying << ", rangeEnd = " << rangeEnd << std::endl;
|
Chris@4
|
442
|
Chris@5
|
443 if (!looping && (framePlaying > rangeEnd)) {
|
Chris@4
|
444 // std::cerr << "STOPPING" << std::endl;
|
Chris@4
|
445 stop();
|
Chris@4
|
446 return rangeEnd;
|
Chris@4
|
447 } else {
|
Chris@4
|
448 return framePlaying;
|
Chris@4
|
449 }
|
Chris@3
|
450 } else {
|
Chris@4
|
451 // std::cerr << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl;
|
Chris@3
|
452 latency -= (f - i->getEndFrame());
|
Chris@3
|
453 f = i->getEndFrame();
|
Chris@3
|
454 }
|
Chris@3
|
455 }
|
Chris@3
|
456
|
Chris@4
|
457 // std::cerr << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl;
|
Chris@3
|
458
|
Chris@3
|
459 while (latency > 0) {
|
Chris@3
|
460 size_t offset = f - i->getStartFrame();
|
Chris@3
|
461 if (offset >= latency) {
|
Chris@3
|
462 if (f > latency) {
|
Chris@3
|
463 framePlaying = f - latency;
|
Chris@3
|
464 } else {
|
Chris@3
|
465 framePlaying = 0;
|
Chris@3
|
466 }
|
Chris@3
|
467 break;
|
Chris@3
|
468 } else {
|
Chris@3
|
469 if (i == selections.begin()) {
|
Chris@5
|
470 if (looping) {
|
Chris@3
|
471 i = selections.end();
|
Chris@3
|
472 }
|
Chris@3
|
473 }
|
Chris@3
|
474 latency -= offset;
|
Chris@3
|
475 --i;
|
Chris@3
|
476 f = i->getEndFrame();
|
Chris@3
|
477 }
|
Chris@3
|
478 }
|
Chris@0
|
479
|
Chris@0
|
480 return framePlaying;
|
Chris@0
|
481 }
|
Chris@0
|
482
|
Chris@0
|
483 void
|
Chris@0
|
484 AudioCallbackPlaySource::setOutputLevels(float left, float right)
|
Chris@0
|
485 {
|
Chris@0
|
486 m_outputLeft = left;
|
Chris@0
|
487 m_outputRight = right;
|
Chris@0
|
488 }
|
Chris@0
|
489
|
Chris@0
|
490 bool
|
Chris@0
|
491 AudioCallbackPlaySource::getOutputLevels(float &left, float &right)
|
Chris@0
|
492 {
|
Chris@0
|
493 left = m_outputLeft;
|
Chris@0
|
494 right = m_outputRight;
|
Chris@0
|
495 return true;
|
Chris@0
|
496 }
|
Chris@0
|
497
|
Chris@0
|
498 void
|
Chris@0
|
499 AudioCallbackPlaySource::setTargetSampleRate(size_t sr)
|
Chris@0
|
500 {
|
Chris@0
|
501 m_targetSampleRate = sr;
|
Chris@1
|
502
|
Chris@1
|
503 if (getSourceSampleRate() != getTargetSampleRate()) {
|
Chris@1
|
504
|
Chris@1
|
505 int err = 0;
|
Chris@13
|
506 m_converter = src_new(SRC_SINC_BEST_QUALITY,
|
Chris@13
|
507 getTargetChannelCount(), &err);
|
Chris@1
|
508 if (!m_converter) {
|
Chris@1
|
509 std::cerr
|
Chris@1
|
510 << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: "
|
Chris@1
|
511 << src_strerror(err) << std::endl;
|
Chris@1
|
512 }
|
Chris@1
|
513
|
Chris@1
|
514 emit sampleRateMismatch(getSourceSampleRate(), getTargetSampleRate());
|
Chris@1
|
515 }
|
Chris@0
|
516 }
|
Chris@0
|
517
|
Chris@0
|
518 size_t
|
Chris@0
|
519 AudioCallbackPlaySource::getTargetSampleRate() const
|
Chris@0
|
520 {
|
Chris@0
|
521 if (m_targetSampleRate) return m_targetSampleRate;
|
Chris@0
|
522 else return getSourceSampleRate();
|
Chris@0
|
523 }
|
Chris@0
|
524
|
Chris@0
|
525 size_t
|
Chris@0
|
526 AudioCallbackPlaySource::getSourceChannelCount() const
|
Chris@0
|
527 {
|
Chris@6
|
528 return m_sourceChannelCount;
|
Chris@0
|
529 }
|
Chris@0
|
530
|
Chris@0
|
531 size_t
|
Chris@13
|
532 AudioCallbackPlaySource::getTargetChannelCount() const
|
Chris@13
|
533 {
|
Chris@13
|
534 if (m_sourceChannelCount < 2) return 2;
|
Chris@13
|
535 return m_sourceChannelCount;
|
Chris@13
|
536 }
|
Chris@13
|
537
|
Chris@13
|
538 size_t
|
Chris@0
|
539 AudioCallbackPlaySource::getSourceSampleRate() const
|
Chris@0
|
540 {
|
Chris@0
|
541 return m_sourceSampleRate;
|
Chris@0
|
542 }
|
Chris@0
|
543
|
Chris@0
|
544 AudioCallbackPlaySource::TimeStretcherData::TimeStretcherData(size_t channels,
|
Chris@0
|
545 size_t factor,
|
Chris@0
|
546 size_t blockSize) :
|
Chris@0
|
547 m_factor(factor),
|
Chris@0
|
548 m_blockSize(blockSize)
|
Chris@0
|
549 {
|
Chris@0
|
550 std::cerr << "TimeStretcherData::TimeStretcherData(" << channels << ", " << factor << ", " << blockSize << ")" << std::endl;
|
Chris@0
|
551
|
Chris@0
|
552 for (size_t ch = 0; ch < channels; ++ch) {
|
Chris@0
|
553 m_stretcher[ch] = StretcherBuffer
|
Chris@0
|
554 //!!! We really need to measure performance and work out
|
Chris@0
|
555 //what sort of quality level to use -- or at least to
|
Chris@0
|
556 //allow the user to configure it
|
Chris@0
|
557 (new IntegerTimeStretcher(factor, blockSize, 128),
|
Chris@0
|
558 new double[blockSize * factor]);
|
Chris@0
|
559 }
|
Chris@0
|
560 m_stretchInputBuffer = new double[blockSize];
|
Chris@0
|
561 }
|
Chris@0
|
562
|
Chris@0
|
563 AudioCallbackPlaySource::TimeStretcherData::~TimeStretcherData()
|
Chris@0
|
564 {
|
Chris@0
|
565 std::cerr << "IntegerTimeStretcher::~IntegerTimeStretcher" << std::endl;
|
Chris@0
|
566
|
Chris@0
|
567 while (!m_stretcher.empty()) {
|
Chris@0
|
568 delete m_stretcher.begin()->second.first;
|
Chris@0
|
569 delete[] m_stretcher.begin()->second.second;
|
Chris@0
|
570 m_stretcher.erase(m_stretcher.begin());
|
Chris@0
|
571 }
|
Chris@0
|
572 delete m_stretchInputBuffer;
|
Chris@0
|
573 }
|
Chris@0
|
574
|
Chris@0
|
575 IntegerTimeStretcher *
|
Chris@0
|
576 AudioCallbackPlaySource::TimeStretcherData::getStretcher(size_t channel)
|
Chris@0
|
577 {
|
Chris@0
|
578 return m_stretcher[channel].first;
|
Chris@0
|
579 }
|
Chris@0
|
580
|
Chris@0
|
581 double *
|
Chris@0
|
582 AudioCallbackPlaySource::TimeStretcherData::getOutputBuffer(size_t channel)
|
Chris@0
|
583 {
|
Chris@0
|
584 return m_stretcher[channel].second;
|
Chris@0
|
585 }
|
Chris@0
|
586
|
Chris@0
|
587 double *
|
Chris@0
|
588 AudioCallbackPlaySource::TimeStretcherData::getInputBuffer()
|
Chris@0
|
589 {
|
Chris@0
|
590 return m_stretchInputBuffer;
|
Chris@0
|
591 }
|
Chris@0
|
592
|
Chris@0
|
593 void
|
Chris@0
|
594 AudioCallbackPlaySource::TimeStretcherData::run(size_t channel)
|
Chris@0
|
595 {
|
Chris@0
|
596 getStretcher(channel)->process(getInputBuffer(),
|
Chris@0
|
597 getOutputBuffer(channel),
|
Chris@0
|
598 m_blockSize);
|
Chris@0
|
599 }
|
Chris@0
|
600
|
Chris@0
|
601 void
|
Chris@0
|
602 AudioCallbackPlaySource::setSlowdownFactor(size_t factor)
|
Chris@0
|
603 {
|
Chris@0
|
604 // Avoid locks -- create, assign, mark old one for scavenging
|
Chris@0
|
605 // later (as a call to getSourceSamples may still be using it)
|
Chris@0
|
606
|
Chris@0
|
607 TimeStretcherData *existingStretcher = m_timeStretcher;
|
Chris@0
|
608
|
Chris@0
|
609 if (existingStretcher && existingStretcher->getFactor() == factor) {
|
Chris@0
|
610 return;
|
Chris@0
|
611 }
|
Chris@0
|
612
|
Chris@0
|
613 if (factor > 1) {
|
Chris@0
|
614 TimeStretcherData *newStretcher = new TimeStretcherData
|
Chris@13
|
615 (getTargetChannelCount(), factor, getTargetBlockSize());
|
Chris@0
|
616 m_slowdownCounter = 0;
|
Chris@0
|
617 m_timeStretcher = newStretcher;
|
Chris@0
|
618 } else {
|
Chris@0
|
619 m_timeStretcher = 0;
|
Chris@0
|
620 }
|
Chris@0
|
621
|
Chris@0
|
622 if (existingStretcher) {
|
Chris@0
|
623 m_timeStretcherScavenger.claim(existingStretcher);
|
Chris@0
|
624 }
|
Chris@0
|
625 }
|
Chris@0
|
626
|
Chris@0
|
627 size_t
|
Chris@0
|
628 AudioCallbackPlaySource::getSourceSamples(size_t count, float **buffer)
|
Chris@0
|
629 {
|
Chris@0
|
630 if (!m_playing) {
|
Chris@13
|
631 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
|
Chris@0
|
632 for (size_t i = 0; i < count; ++i) {
|
Chris@0
|
633 buffer[ch][i] = 0.0;
|
Chris@0
|
634 }
|
Chris@0
|
635 }
|
Chris@0
|
636 return 0;
|
Chris@0
|
637 }
|
Chris@0
|
638
|
Chris@0
|
639 TimeStretcherData *timeStretcher = m_timeStretcher;
|
Chris@0
|
640
|
Chris@0
|
641 if (!timeStretcher || timeStretcher->getFactor() == 1) {
|
Chris@0
|
642
|
Chris@0
|
643 size_t got = 0;
|
Chris@0
|
644
|
Chris@13
|
645 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
|
Chris@0
|
646
|
Chris@6
|
647 RingBuffer<float> *rb = getReadRingBuffer(ch);
|
Chris@0
|
648
|
Chris@6
|
649 if (rb) {
|
Chris@0
|
650
|
Chris@6
|
651 // this is marginally more likely to leave our channels in
|
Chris@6
|
652 // sync after a processing failure than just passing "count":
|
Chris@6
|
653 size_t request = count;
|
Chris@6
|
654 if (ch > 0) request = got;
|
Chris@6
|
655
|
Chris@6
|
656 got = rb->read(buffer[ch], request);
|
Chris@0
|
657
|
Chris@7
|
658 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING
|
Chris@6
|
659 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", signalling for more (possibly)" << std::endl;
|
Chris@0
|
660 #endif
|
Chris@6
|
661 }
|
Chris@0
|
662
|
Chris@13
|
663 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
|
Chris@6
|
664 for (size_t i = got; i < count; ++i) {
|
Chris@6
|
665 buffer[ch][i] = 0.0;
|
Chris@6
|
666 }
|
Chris@0
|
667 }
|
Chris@0
|
668 }
|
Chris@0
|
669
|
Chris@0
|
670 m_condition.wakeAll();
|
Chris@0
|
671 return got;
|
Chris@0
|
672 }
|
Chris@0
|
673
|
Chris@0
|
674 if (m_slowdownCounter == 0) {
|
Chris@0
|
675
|
Chris@0
|
676 size_t got = 0;
|
Chris@0
|
677 double *ib = timeStretcher->getInputBuffer();
|
Chris@0
|
678
|
Chris@13
|
679 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
|
Chris@0
|
680
|
Chris@6
|
681 RingBuffer<float> *rb = getReadRingBuffer(ch);
|
Chris@6
|
682
|
Chris@6
|
683 if (rb) {
|
Chris@6
|
684
|
Chris@6
|
685 size_t request = count;
|
Chris@6
|
686 if (ch > 0) request = got; // see above
|
Chris@6
|
687 got = rb->read(buffer[ch], request);
|
Chris@0
|
688
|
Chris@0
|
689 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@6
|
690 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl;
|
Chris@0
|
691 #endif
|
Chris@0
|
692
|
Chris@6
|
693 for (size_t i = 0; i < count; ++i) {
|
Chris@6
|
694 ib[i] = buffer[ch][i];
|
Chris@6
|
695 }
|
Chris@6
|
696
|
Chris@6
|
697 timeStretcher->run(ch);
|
Chris@0
|
698 }
|
Chris@0
|
699 }
|
Chris@0
|
700
|
Chris@0
|
701 } else if (m_slowdownCounter >= timeStretcher->getFactor()) {
|
Chris@0
|
702 // reset this in case the factor has changed leaving the
|
Chris@0
|
703 // counter out of range
|
Chris@0
|
704 m_slowdownCounter = 0;
|
Chris@0
|
705 }
|
Chris@0
|
706
|
Chris@13
|
707 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) {
|
Chris@0
|
708
|
Chris@0
|
709 double *ob = timeStretcher->getOutputBuffer(ch);
|
Chris@0
|
710
|
Chris@0
|
711 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
712 std::cerr << "AudioCallbackPlaySource::getSamples: Copying from (" << (m_slowdownCounter * count) << "," << count << ") to buffer" << std::endl;
|
Chris@0
|
713 #endif
|
Chris@0
|
714
|
Chris@0
|
715 for (size_t i = 0; i < count; ++i) {
|
Chris@0
|
716 buffer[ch][i] = ob[m_slowdownCounter * count + i];
|
Chris@0
|
717 }
|
Chris@0
|
718 }
|
Chris@0
|
719
|
Chris@7
|
720 //!!! if (m_slowdownCounter == 0) m_condition.wakeAll();
|
Chris@0
|
721 m_slowdownCounter = (m_slowdownCounter + 1) % timeStretcher->getFactor();
|
Chris@0
|
722 return count;
|
Chris@0
|
723 }
|
Chris@0
|
724
|
Chris@6
|
725 // Called from fill thread, m_playing true, mutex held
|
Chris@7
|
726 bool
|
Chris@0
|
727 AudioCallbackPlaySource::fillBuffers()
|
Chris@0
|
728 {
|
Chris@0
|
729 static float *tmp = 0;
|
Chris@0
|
730 static size_t tmpSize = 0;
|
Chris@0
|
731
|
Chris@0
|
732 size_t space = 0;
|
Chris@13
|
733 for (size_t c = 0; c < getTargetChannelCount(); ++c) {
|
Chris@6
|
734 RingBuffer<float> *wb = getWriteRingBuffer(c);
|
Chris@6
|
735 if (wb) {
|
Chris@6
|
736 size_t spaceHere = wb->getWriteSpace();
|
Chris@6
|
737 if (c == 0 || spaceHere < space) space = spaceHere;
|
Chris@6
|
738 }
|
Chris@0
|
739 }
|
Chris@0
|
740
|
Chris@7
|
741 if (space == 0) return false;
|
Chris@4
|
742
|
Chris@13
|
743 size_t f = m_writeBufferFill;
|
Chris@13
|
744
|
Chris@13
|
745 bool readWriteEqual = (m_readBuffers == m_writeBuffers);
|
Chris@13
|
746
|
Chris@0
|
747 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
748 std::cout << "AudioCallbackPlaySourceFillThread: filling " << space << " frames" << std::endl;
|
Chris@0
|
749 #endif
|
Chris@0
|
750
|
Chris@0
|
751 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
752 std::cout << "buffered to " << f << " already" << std::endl;
|
Chris@0
|
753 #endif
|
Chris@0
|
754
|
Chris@0
|
755 bool resample = (getSourceSampleRate() != getTargetSampleRate());
|
Chris@1
|
756
|
Chris@1
|
757 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@1
|
758 std::cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << std::endl;
|
Chris@1
|
759 #endif
|
Chris@1
|
760
|
Chris@13
|
761 size_t channels = getTargetChannelCount();
|
Chris@13
|
762
|
Chris@0
|
763 size_t orig = space;
|
Chris@0
|
764 size_t got = 0;
|
Chris@0
|
765
|
Chris@0
|
766 static float **bufferPtrs = 0;
|
Chris@0
|
767 static size_t bufferPtrCount = 0;
|
Chris@0
|
768
|
Chris@0
|
769 if (bufferPtrCount < channels) {
|
Chris@0
|
770 if (bufferPtrs) delete[] bufferPtrs;
|
Chris@0
|
771 bufferPtrs = new float *[channels];
|
Chris@0
|
772 bufferPtrCount = channels;
|
Chris@0
|
773 }
|
Chris@0
|
774
|
Chris@0
|
775 size_t generatorBlockSize = m_audioGenerator->getBlockSize();
|
Chris@0
|
776
|
Chris@1
|
777 if (resample && !m_converter) {
|
Chris@1
|
778 static bool warned = false;
|
Chris@1
|
779 if (!warned) {
|
Chris@1
|
780 std::cerr << "WARNING: sample rates differ, but no converter available!" << std::endl;
|
Chris@1
|
781 warned = true;
|
Chris@1
|
782 }
|
Chris@1
|
783 }
|
Chris@1
|
784
|
Chris@0
|
785 if (resample && m_converter) {
|
Chris@0
|
786
|
Chris@0
|
787 double ratio =
|
Chris@0
|
788 double(getTargetSampleRate()) / double(getSourceSampleRate());
|
Chris@0
|
789 orig = size_t(orig / ratio + 0.1);
|
Chris@0
|
790
|
Chris@0
|
791 // orig must be a multiple of generatorBlockSize
|
Chris@0
|
792 orig = (orig / generatorBlockSize) * generatorBlockSize;
|
Chris@7
|
793 if (orig == 0) return false;
|
Chris@0
|
794
|
Chris@0
|
795 size_t work = std::max(orig, space);
|
Chris@0
|
796
|
Chris@0
|
797 // We only allocate one buffer, but we use it in two halves.
|
Chris@0
|
798 // We place the non-interleaved values in the second half of
|
Chris@0
|
799 // the buffer (orig samples for channel 0, orig samples for
|
Chris@0
|
800 // channel 1 etc), and then interleave them into the first
|
Chris@0
|
801 // half of the buffer. Then we resample back into the second
|
Chris@0
|
802 // half (interleaved) and de-interleave the results back to
|
Chris@0
|
803 // the start of the buffer for insertion into the ringbuffers.
|
Chris@0
|
804 // What a faff -- especially as we've already de-interleaved
|
Chris@0
|
805 // the audio data from the source file elsewhere before we
|
Chris@0
|
806 // even reach this point.
|
Chris@0
|
807
|
Chris@0
|
808 if (tmpSize < channels * work * 2) {
|
Chris@0
|
809 delete[] tmp;
|
Chris@0
|
810 tmp = new float[channels * work * 2];
|
Chris@0
|
811 tmpSize = channels * work * 2;
|
Chris@0
|
812 }
|
Chris@0
|
813
|
Chris@0
|
814 float *nonintlv = tmp + channels * work;
|
Chris@0
|
815 float *intlv = tmp;
|
Chris@0
|
816 float *srcout = tmp + channels * work;
|
Chris@0
|
817
|
Chris@0
|
818 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
819 for (size_t i = 0; i < orig; ++i) {
|
Chris@0
|
820 nonintlv[channels * i + c] = 0.0f;
|
Chris@0
|
821 }
|
Chris@0
|
822 }
|
Chris@0
|
823
|
Chris@3
|
824 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
825 bufferPtrs[c] = nonintlv + c * orig;
|
Chris@3
|
826 }
|
Chris@0
|
827
|
Chris@6
|
828 got = mixModels(f, orig, bufferPtrs);
|
Chris@0
|
829
|
Chris@0
|
830 // and interleave into first half
|
Chris@0
|
831 for (size_t c = 0; c < channels; ++c) {
|
Chris@6
|
832 for (size_t i = 0; i < got; ++i) {
|
Chris@6
|
833 float sample = nonintlv[c * got + i];
|
Chris@0
|
834 intlv[channels * i + c] = sample;
|
Chris@0
|
835 }
|
Chris@0
|
836 }
|
Chris@0
|
837
|
Chris@0
|
838 SRC_DATA data;
|
Chris@0
|
839 data.data_in = intlv;
|
Chris@0
|
840 data.data_out = srcout;
|
Chris@6
|
841 data.input_frames = got;
|
Chris@0
|
842 data.output_frames = work;
|
Chris@0
|
843 data.src_ratio = ratio;
|
Chris@0
|
844 data.end_of_input = 0;
|
Chris@0
|
845
|
Chris@0
|
846 int err = src_process(m_converter, &data);
|
Chris@6
|
847 // size_t toCopy = size_t(work * ratio + 0.1);
|
Chris@6
|
848 size_t toCopy = size_t(got * ratio + 0.1);
|
Chris@6
|
849
|
Chris@0
|
850 if (err) {
|
Chris@0
|
851 std::cerr
|
Chris@0
|
852 << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: "
|
Chris@0
|
853 << src_strerror(err) << std::endl;
|
Chris@0
|
854 //!!! Then what?
|
Chris@0
|
855 } else {
|
Chris@0
|
856 got = data.input_frames_used;
|
Chris@0
|
857 toCopy = data.output_frames_gen;
|
Chris@0
|
858 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
859 std::cerr << "Resampled " << got << " frames to " << toCopy << " frames" << std::endl;
|
Chris@0
|
860 #endif
|
Chris@0
|
861 }
|
Chris@0
|
862
|
Chris@0
|
863 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
864 for (size_t i = 0; i < toCopy; ++i) {
|
Chris@0
|
865 tmp[i] = srcout[channels * i + c];
|
Chris@0
|
866 }
|
Chris@6
|
867 RingBuffer<float> *wb = getWriteRingBuffer(c);
|
Chris@6
|
868 if (wb) wb->write(tmp, toCopy);
|
Chris@0
|
869 }
|
Chris@3
|
870
|
Chris@13
|
871 m_writeBufferFill = f;
|
Chris@13
|
872 if (readWriteEqual) m_readBufferFill = f;
|
Chris@13
|
873
|
Chris@0
|
874 } else {
|
Chris@0
|
875
|
Chris@0
|
876 // space must be a multiple of generatorBlockSize
|
Chris@0
|
877 space = (space / generatorBlockSize) * generatorBlockSize;
|
Chris@7
|
878 if (space == 0) return false;
|
Chris@0
|
879
|
Chris@0
|
880 if (tmpSize < channels * space) {
|
Chris@0
|
881 delete[] tmp;
|
Chris@0
|
882 tmp = new float[channels * space];
|
Chris@0
|
883 tmpSize = channels * space;
|
Chris@0
|
884 }
|
Chris@0
|
885
|
Chris@0
|
886 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
887
|
Chris@0
|
888 bufferPtrs[c] = tmp + c * space;
|
Chris@3
|
889
|
Chris@0
|
890 for (size_t i = 0; i < space; ++i) {
|
Chris@0
|
891 tmp[c * space + i] = 0.0f;
|
Chris@0
|
892 }
|
Chris@0
|
893 }
|
Chris@0
|
894
|
Chris@6
|
895 size_t got = mixModels(f, space, bufferPtrs);
|
Chris@0
|
896
|
Chris@0
|
897 for (size_t c = 0; c < channels; ++c) {
|
Chris@0
|
898
|
Chris@6
|
899 RingBuffer<float> *wb = getWriteRingBuffer(c);
|
Chris@6
|
900 if (wb) wb->write(bufferPtrs[c], got);
|
Chris@0
|
901
|
Chris@0
|
902 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@6
|
903 if (wb)
|
Chris@6
|
904 std::cerr << "Wrote " << got << " frames for ch " << c << ", now "
|
Chris@6
|
905 << wb->getReadSpace() << " to read"
|
Chris@6
|
906 << std::endl;
|
Chris@0
|
907 #endif
|
Chris@0
|
908 }
|
Chris@3
|
909
|
Chris@13
|
910 m_writeBufferFill = f;
|
Chris@13
|
911 if (readWriteEqual) m_readBufferFill = f;
|
Chris@13
|
912
|
Chris@3
|
913 //!!! 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
|
914 }
|
Chris@7
|
915
|
Chris@7
|
916 return true;
|
Chris@3
|
917 }
|
Chris@3
|
918
|
Chris@6
|
919 size_t
|
Chris@3
|
920 AudioCallbackPlaySource::mixModels(size_t &frame, size_t count, float **buffers)
|
Chris@3
|
921 {
|
Chris@3
|
922 size_t processed = 0;
|
Chris@3
|
923 size_t chunkStart = frame;
|
Chris@3
|
924 size_t chunkSize = count;
|
Chris@6
|
925 size_t selectionSize = 0;
|
Chris@3
|
926 size_t nextChunkStart = chunkStart + chunkSize;
|
Chris@0
|
927
|
Chris@5
|
928 bool looping = m_viewManager->getPlayLoopMode();
|
Chris@5
|
929 bool constrained = (m_viewManager->getPlaySelectionMode() &&
|
Chris@5
|
930 !m_viewManager->getSelections().empty());
|
Chris@3
|
931
|
Chris@3
|
932 static float **chunkBufferPtrs = 0;
|
Chris@3
|
933 static size_t chunkBufferPtrCount = 0;
|
Chris@13
|
934 size_t channels = getTargetChannelCount();
|
Chris@3
|
935
|
Chris@3
|
936 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
937 std::cerr << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << std::endl;
|
Chris@3
|
938 #endif
|
Chris@3
|
939
|
Chris@3
|
940 if (chunkBufferPtrCount < channels) {
|
Chris@3
|
941 if (chunkBufferPtrs) delete[] chunkBufferPtrs;
|
Chris@3
|
942 chunkBufferPtrs = new float *[channels];
|
Chris@3
|
943 chunkBufferPtrCount = channels;
|
Chris@3
|
944 }
|
Chris@3
|
945
|
Chris@3
|
946 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
947 chunkBufferPtrs[c] = buffers[c];
|
Chris@3
|
948 }
|
Chris@3
|
949
|
Chris@3
|
950 while (processed < count) {
|
Chris@3
|
951
|
Chris@3
|
952 chunkSize = count - processed;
|
Chris@3
|
953 nextChunkStart = chunkStart + chunkSize;
|
Chris@6
|
954 selectionSize = 0;
|
Chris@3
|
955
|
Chris@4
|
956 size_t fadeIn = 0, fadeOut = 0;
|
Chris@4
|
957
|
Chris@5
|
958 if (constrained) {
|
Chris@3
|
959
|
Chris@3
|
960 Selection selection =
|
Chris@3
|
961 m_viewManager->getContainingSelection(chunkStart, true);
|
Chris@3
|
962
|
Chris@3
|
963 if (selection.isEmpty()) {
|
Chris@5
|
964 if (looping) {
|
Chris@3
|
965 selection = *m_viewManager->getSelections().begin();
|
Chris@3
|
966 chunkStart = selection.getStartFrame();
|
Chris@4
|
967 fadeIn = 50;
|
Chris@3
|
968 }
|
Chris@3
|
969 }
|
Chris@3
|
970
|
Chris@3
|
971 if (selection.isEmpty()) {
|
Chris@3
|
972
|
Chris@3
|
973 chunkSize = 0;
|
Chris@3
|
974 nextChunkStart = chunkStart;
|
Chris@3
|
975
|
Chris@3
|
976 } else {
|
Chris@3
|
977
|
Chris@6
|
978 selectionSize =
|
Chris@6
|
979 selection.getEndFrame() -
|
Chris@6
|
980 selection.getStartFrame();
|
Chris@6
|
981
|
Chris@3
|
982 if (chunkStart < selection.getStartFrame()) {
|
Chris@3
|
983 chunkStart = selection.getStartFrame();
|
Chris@4
|
984 fadeIn = 50;
|
Chris@3
|
985 }
|
Chris@3
|
986
|
Chris@4
|
987 nextChunkStart = chunkStart + chunkSize;
|
Chris@4
|
988
|
Chris@6
|
989 if (nextChunkStart >= selection.getEndFrame()) {
|
Chris@4
|
990 nextChunkStart = selection.getEndFrame();
|
Chris@4
|
991 fadeOut = 50;
|
Chris@4
|
992 }
|
Chris@3
|
993
|
Chris@3
|
994 chunkSize = nextChunkStart - chunkStart;
|
Chris@3
|
995 }
|
Chris@4
|
996
|
Chris@5
|
997 } else if (looping && m_lastModelEndFrame > 0) {
|
Chris@4
|
998
|
Chris@4
|
999 if (chunkStart >= m_lastModelEndFrame) {
|
Chris@4
|
1000 chunkStart = 0;
|
Chris@4
|
1001 }
|
Chris@4
|
1002 if (chunkSize > m_lastModelEndFrame - chunkStart) {
|
Chris@4
|
1003 chunkSize = m_lastModelEndFrame - chunkStart;
|
Chris@4
|
1004 }
|
Chris@4
|
1005 nextChunkStart = chunkStart + chunkSize;
|
Chris@3
|
1006 }
|
Chris@3
|
1007
|
Chris@6
|
1008 // std::cerr << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << std::endl;
|
Chris@6
|
1009
|
Chris@3
|
1010 if (!chunkSize) {
|
Chris@3
|
1011 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
1012 std::cerr << "Ending selection playback at " << nextChunkStart << std::endl;
|
Chris@3
|
1013 #endif
|
Chris@3
|
1014 // We need to maintain full buffers so that the other
|
Chris@3
|
1015 // thread can tell where it's got to in the playback -- so
|
Chris@3
|
1016 // return the full amount here
|
Chris@3
|
1017 frame = frame + count;
|
Chris@6
|
1018 return count;
|
Chris@3
|
1019 }
|
Chris@3
|
1020
|
Chris@3
|
1021 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@3
|
1022 std::cerr << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << std::endl;
|
Chris@3
|
1023 #endif
|
Chris@3
|
1024
|
Chris@3
|
1025 size_t got = 0;
|
Chris@3
|
1026
|
Chris@6
|
1027 if (selectionSize < 100) {
|
Chris@4
|
1028 fadeIn = 0;
|
Chris@4
|
1029 fadeOut = 0;
|
Chris@6
|
1030 } else if (selectionSize < 300) {
|
Chris@4
|
1031 if (fadeIn > 0) fadeIn = 10;
|
Chris@4
|
1032 if (fadeOut > 0) fadeOut = 10;
|
Chris@4
|
1033 }
|
Chris@4
|
1034
|
Chris@4
|
1035 if (fadeIn > 0) {
|
Chris@4
|
1036 if (processed * 2 < fadeIn) {
|
Chris@4
|
1037 fadeIn = processed * 2;
|
Chris@4
|
1038 }
|
Chris@4
|
1039 }
|
Chris@4
|
1040
|
Chris@4
|
1041 if (fadeOut > 0) {
|
Chris@6
|
1042 if ((count - processed - chunkSize) * 2 < fadeOut) {
|
Chris@6
|
1043 fadeOut = (count - processed - chunkSize) * 2;
|
Chris@4
|
1044 }
|
Chris@4
|
1045 }
|
Chris@4
|
1046
|
Chris@3
|
1047 for (std::set<Model *>::iterator mi = m_models.begin();
|
Chris@3
|
1048 mi != m_models.end(); ++mi) {
|
Chris@3
|
1049
|
Chris@3
|
1050 got = m_audioGenerator->mixModel(*mi, chunkStart,
|
Chris@4
|
1051 chunkSize, chunkBufferPtrs,
|
Chris@4
|
1052 fadeIn, fadeOut);
|
Chris@3
|
1053 }
|
Chris@3
|
1054
|
Chris@3
|
1055 for (size_t c = 0; c < channels; ++c) {
|
Chris@3
|
1056 chunkBufferPtrs[c] += chunkSize;
|
Chris@3
|
1057 }
|
Chris@3
|
1058
|
Chris@3
|
1059 processed += chunkSize;
|
Chris@3
|
1060 chunkStart = nextChunkStart;
|
Chris@3
|
1061 }
|
Chris@3
|
1062
|
Chris@3
|
1063 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@7
|
1064 std::cerr << "Returning selection playback " << processed << " frames to " << nextChunkStart << std::endl;
|
Chris@3
|
1065 #endif
|
Chris@3
|
1066
|
Chris@3
|
1067 frame = nextChunkStart;
|
Chris@6
|
1068 return processed;
|
Chris@3
|
1069 }
|
Chris@0
|
1070
|
Chris@0
|
1071 void
|
Chris@13
|
1072 AudioCallbackPlaySource::unifyRingBuffers()
|
Chris@13
|
1073 {
|
Chris@13
|
1074 if (m_readBuffers == m_writeBuffers) return;
|
Chris@13
|
1075
|
Chris@13
|
1076 // only unify if there will be something to read
|
Chris@13
|
1077 for (size_t c = 0; c < getTargetChannelCount(); ++c) {
|
Chris@13
|
1078 RingBuffer<float> *wb = getWriteRingBuffer(c);
|
Chris@13
|
1079 if (wb) {
|
Chris@13
|
1080 if (wb->getReadSpace() < m_blockSize * 2) {
|
Chris@13
|
1081 if ((m_writeBufferFill + m_blockSize * 2) <
|
Chris@13
|
1082 m_lastModelEndFrame) {
|
Chris@13
|
1083 // OK, we don't have enough and there's more to
|
Chris@13
|
1084 // read -- don't unify until we can do better
|
Chris@13
|
1085 return;
|
Chris@13
|
1086 }
|
Chris@13
|
1087 }
|
Chris@13
|
1088 break;
|
Chris@13
|
1089 }
|
Chris@13
|
1090 }
|
Chris@13
|
1091
|
Chris@13
|
1092 size_t rf = m_readBufferFill;
|
Chris@13
|
1093 RingBuffer<float> *rb = getReadRingBuffer(0);
|
Chris@13
|
1094 if (rb) {
|
Chris@13
|
1095 size_t rs = rb->getReadSpace();
|
Chris@13
|
1096 //!!! incorrect when in non-contiguous selection, see comments elsewhere
|
Chris@13
|
1097 // std::cerr << "rs = " << rs << std::endl;
|
Chris@13
|
1098 if (rs < rf) rf -= rs;
|
Chris@13
|
1099 else rf = 0;
|
Chris@13
|
1100 }
|
Chris@13
|
1101
|
Chris@13
|
1102 //std::cerr << "m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << std::endl;
|
Chris@13
|
1103
|
Chris@13
|
1104 size_t wf = m_writeBufferFill;
|
Chris@13
|
1105 size_t skip = 0;
|
Chris@13
|
1106 for (size_t c = 0; c < getTargetChannelCount(); ++c) {
|
Chris@13
|
1107 RingBuffer<float> *wb = getWriteRingBuffer(c);
|
Chris@13
|
1108 if (wb) {
|
Chris@13
|
1109 if (c == 0) {
|
Chris@13
|
1110
|
Chris@13
|
1111 size_t wrs = wb->getReadSpace();
|
Chris@13
|
1112 // std::cerr << "wrs = " << wrs << std::endl;
|
Chris@13
|
1113
|
Chris@13
|
1114 if (wrs < wf) wf -= wrs;
|
Chris@13
|
1115 else wf = 0;
|
Chris@13
|
1116 // std::cerr << "wf = " << wf << std::endl;
|
Chris@13
|
1117
|
Chris@13
|
1118 if (wf < rf) skip = rf - wf;
|
Chris@13
|
1119 if (skip == 0) break;
|
Chris@13
|
1120 }
|
Chris@13
|
1121
|
Chris@13
|
1122 // std::cerr << "skipping " << skip << std::endl;
|
Chris@13
|
1123 wb->skip(skip);
|
Chris@13
|
1124 }
|
Chris@13
|
1125 }
|
Chris@13
|
1126
|
Chris@13
|
1127 m_bufferScavenger.claim(m_readBuffers);
|
Chris@13
|
1128 m_readBuffers = m_writeBuffers;
|
Chris@13
|
1129 m_readBufferFill = m_writeBufferFill;
|
Chris@13
|
1130 std::cerr << "unified" << std::endl;
|
Chris@13
|
1131 }
|
Chris@13
|
1132
|
Chris@13
|
1133 void
|
Chris@0
|
1134 AudioCallbackPlaySource::AudioCallbackPlaySourceFillThread::run()
|
Chris@0
|
1135 {
|
Chris@0
|
1136 AudioCallbackPlaySource &s(m_source);
|
Chris@0
|
1137
|
Chris@0
|
1138 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
1139 std::cerr << "AudioCallbackPlaySourceFillThread starting" << std::endl;
|
Chris@0
|
1140 #endif
|
Chris@0
|
1141
|
Chris@0
|
1142 s.m_mutex.lock();
|
Chris@0
|
1143
|
Chris@0
|
1144 bool previouslyPlaying = s.m_playing;
|
Chris@7
|
1145 bool work = false;
|
Chris@0
|
1146
|
Chris@0
|
1147 while (!s.m_exiting) {
|
Chris@0
|
1148
|
Chris@13
|
1149 s.unifyRingBuffers();
|
Chris@6
|
1150 s.m_bufferScavenger.scavenge();
|
Chris@0
|
1151 s.m_timeStretcherScavenger.scavenge();
|
Chris@0
|
1152
|
Chris@7
|
1153 if (work && s.m_playing && s.getSourceSampleRate()) {
|
Chris@7
|
1154
|
Chris@7
|
1155 s.m_mutex.unlock();
|
Chris@7
|
1156 s.m_mutex.lock();
|
Chris@7
|
1157
|
Chris@7
|
1158 } else {
|
Chris@7
|
1159
|
Chris@7
|
1160 float ms = 100;
|
Chris@7
|
1161 if (s.getSourceSampleRate() > 0) {
|
Chris@7
|
1162 ms = float(m_ringBufferSize) / float(s.getSourceSampleRate()) * 1000.0;
|
Chris@7
|
1163 }
|
Chris@7
|
1164
|
Chris@7
|
1165 if (s.m_playing) ms /= 10;
|
Chris@7
|
1166
|
Chris@7
|
1167 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@7
|
1168 std::cout << "AudioCallbackPlaySourceFillThread: waiting for " << ms << "ms..." << std::endl;
|
Chris@7
|
1169 #endif
|
Chris@7
|
1170
|
Chris@7
|
1171 s.m_condition.wait(&s.m_mutex, size_t(ms));
|
Chris@0
|
1172 }
|
Chris@0
|
1173
|
Chris@0
|
1174 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
1175 std::cout << "AudioCallbackPlaySourceFillThread: awoken" << std::endl;
|
Chris@0
|
1176 #endif
|
Chris@0
|
1177
|
Chris@7
|
1178 work = false;
|
Chris@7
|
1179
|
Chris@0
|
1180 if (!s.getSourceSampleRate()) continue;
|
Chris@0
|
1181
|
Chris@0
|
1182 bool playing = s.m_playing;
|
Chris@0
|
1183
|
Chris@0
|
1184 if (playing && !previouslyPlaying) {
|
Chris@0
|
1185 #ifdef DEBUG_AUDIO_PLAY_SOURCE
|
Chris@0
|
1186 std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl;
|
Chris@0
|
1187 #endif
|
Chris@13
|
1188 for (size_t c = 0; c < s.getTargetChannelCount(); ++c) {
|
Chris@6
|
1189 RingBuffer<float> *rb = s.getReadRingBuffer(c);
|
Chris@6
|
1190 if (rb) rb->reset();
|
Chris@0
|
1191 }
|
Chris@0
|
1192 }
|
Chris@0
|
1193 previouslyPlaying = playing;
|
Chris@0
|
1194
|
Chris@7
|
1195 work = s.fillBuffers();
|
Chris@0
|
1196 }
|
Chris@0
|
1197
|
Chris@0
|
1198 s.m_mutex.unlock();
|
Chris@0
|
1199 }
|
Chris@0
|
1200
|
Chris@0
|
1201
|
Chris@0
|
1202
|
Chris@0
|
1203 #ifdef INCLUDE_MOCFILES
|
Chris@0
|
1204 #include "AudioCallbackPlaySource.moc.cpp"
|
Chris@0
|
1205 #endif
|
Chris@0
|
1206
|