# HG changeset patch # User Chris Cannam # Date 1138385047 0 # Node ID f3d777b693f7f3ce889cb3a5bd3f79ed3cc1b5d2 # Parent 2edc0757ca75b1c3e869f5cf092bcfb829e47b2e * Introduce potentially-separate read and write ring buffers, so we can swap in a new set when something changes -- thus allowing us to respond quickly when something changes during playback, without losing the long buffers * Some fixes for display & editing diff -r 2edc0757ca75 -r f3d777b693f7 audioio/AudioCallbackPlaySource.cpp --- a/audioio/AudioCallbackPlaySource.cpp Thu Jan 26 11:56:09 2006 +0000 +++ b/audioio/AudioCallbackPlaySource.cpp Fri Jan 27 18:04:07 2006 +0000 @@ -18,6 +18,7 @@ #include "dsp/timestretching/IntegerTimeStretcher.h" #include +#include //#define DEBUG_AUDIO_PLAY_SOURCE 1 @@ -27,7 +28,9 @@ AudioCallbackPlaySource::AudioCallbackPlaySource(ViewManager *manager) : m_viewManager(manager), m_audioGenerator(new AudioGenerator(manager)), - m_bufferCount(0), + m_readBuffers(0), + m_writeBuffers(0), + m_sourceChannelCount(0), m_blockSize(1024), m_sourceSampleRate(0), m_targetSampleRate(0), @@ -43,10 +46,6 @@ m_fillThread(0), m_converter(0) { - // preallocate some slots, to avoid reallocation in an - // un-thread-safe manner later - while (m_buffers.size() < 20) m_buffers.push_back(0); - m_viewManager->setAudioPlaySource(this); connect(m_viewManager, SIGNAL(selectionChanged()), @@ -68,6 +67,14 @@ } clearModels(); + + if (m_readBuffers != m_writeBuffers) { + delete m_readBuffers; + } + + delete m_writeBuffers; + + m_bufferScavenger.scavenge(true); } void @@ -96,36 +103,26 @@ << std::endl; } - size_t sz = m_ringBufferSize; - if (m_bufferCount > 0) { - sz = m_buffers[0]->getSize(); - } - size_t modelChannels = 1; DenseTimeValueModel *dtvm = dynamic_cast(model); if (dtvm) modelChannels = dtvm->getChannelCount(); - - while (m_bufferCount < modelChannels) { - - if (m_buffers.size() < modelChannels) { - // This is a hideously chancy operation -- the RT thread - // could be using this vector. We allocated several slots - // in the ctor to avoid exactly this, but if we ever end - // up with more channels than that (!) then we're just - // going to have to risk it - m_buffers.push_back(new RingBuffer(sz)); - - } else { - // The usual case - m_buffers[m_bufferCount] = new RingBuffer(sz); - } - - ++m_bufferCount; - buffersChanged = true; + if (modelChannels > m_sourceChannelCount) { + m_sourceChannelCount = modelChannels; } - if (buffersChanged) { - m_audioGenerator->setTargetChannelCount(m_bufferCount); + std::cerr << "Adding model with " << modelChannels << " channels " << std::endl; + + if (!m_writeBuffers || m_writeBuffers->size() < modelChannels) { + m_audioGenerator->setTargetChannelCount(modelChannels); + } + + m_audioGenerator->addModel(model); + + if (!m_writeBuffers || (m_writeBuffers->size() < modelChannels)) { + clearRingBuffers(true, modelChannels); + buffersChanged = true; + } else { + clearRingBuffers(true); } if (buffersChanged || srChanged) { @@ -135,8 +132,6 @@ } } - m_audioGenerator->addModel(model); - m_mutex.unlock(); if (!m_fillThread) { @@ -151,6 +146,8 @@ if (buffersChanged || srChanged) { emit modelReplaced(); } + + m_condition.wakeAll(); } void @@ -178,6 +175,8 @@ m_audioGenerator->removeModel(model); m_mutex.unlock(); + + clearRingBuffers(); } void @@ -202,6 +201,34 @@ } void +AudioCallbackPlaySource::clearRingBuffers(bool haveLock, size_t count) +{ + if (!haveLock) m_mutex.lock(); + + if (count == 0) { + if (m_writeBuffers) count = m_writeBuffers->size(); + } + + if (m_readBuffers != m_writeBuffers) { + delete m_writeBuffers; + } + + m_writeBuffers = new RingBufferVector; + + for (size_t i = 0; i < count; ++i) { + m_writeBuffers->push_back(new RingBuffer(m_ringBufferSize)); + } + + std::cerr << "AudioCallbackPlaySource::clearRingBuffers: Created " + << count << " write buffers" << std::endl; + + if (!haveLock) { + m_mutex.unlock(); + m_condition.wakeAll(); + } +} + +void AudioCallbackPlaySource::play(size_t startFrame) { if (m_viewManager->getPlaySelectionMode() && @@ -229,17 +256,21 @@ // starting again if we have not so far been playing, but not if // we're just re-seeking. + m_mutex.lock(); if (m_playing) { - m_mutex.lock(); m_bufferedToFrame = startFrame; - for (size_t c = 0; c < m_bufferCount; ++c) { - getRingBuffer(c).reset(); - if (m_converter) src_reset(m_converter); + if (m_readBuffers) { + for (size_t c = 0; c < getSourceChannelCount(); ++c) { + RingBuffer *rb = getReadRingBuffer(c); + if (rb) rb->reset(); + } } - m_mutex.unlock(); + if (m_converter) src_reset(m_converter); } else { + if (m_converter) src_reset(m_converter); m_bufferedToFrame = startFrame; } + m_mutex.unlock(); m_audioGenerator->reset(); @@ -260,30 +291,21 @@ AudioCallbackPlaySource::selectionChanged() { if (m_viewManager->getPlaySelectionMode()) { - m_mutex.lock(); - for (size_t c = 0; c < m_bufferCount; ++c) { - getRingBuffer(c).reset(); - } - m_mutex.unlock(); - m_condition.wakeAll(); + clearRingBuffers(); } } void AudioCallbackPlaySource::playLoopModeChanged() { + clearRingBuffers(); } void AudioCallbackPlaySource::playSelectionModeChanged() { if (!m_viewManager->getSelections().empty()) { - m_mutex.lock(); - for (size_t c = 0; c < m_bufferCount; ++c) { - getRingBuffer(c).reset(); - } - m_mutex.unlock(); - m_condition.wakeAll(); + clearRingBuffers(); } } @@ -291,10 +313,8 @@ AudioCallbackPlaySource::setTargetBlockSize(size_t size) { std::cerr << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl; + assert(size < m_ringBufferSize); m_blockSize = size; - for (size_t i = 0; i < m_bufferCount; ++i) { - getRingBuffer(i).resize(m_ringBufferSize); - } } size_t @@ -329,8 +349,11 @@ size_t readSpace = 0; for (size_t c = 0; c < getSourceChannelCount(); ++c) { - size_t spaceHere = getRingBuffer(c).getReadSpace(); - if (c == 0 || spaceHere < readSpace) readSpace = spaceHere; + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + size_t spaceHere = rb->getReadSpace(); + if (c == 0 || spaceHere < readSpace) readSpace = spaceHere; + } } if (resample) { @@ -455,7 +478,7 @@ if (getSourceSampleRate() != getTargetSampleRate()) { int err = 0; - m_converter = src_new(SRC_SINC_BEST_QUALITY, m_bufferCount, &err); + m_converter = src_new(SRC_SINC_BEST_QUALITY, m_sourceChannelCount, &err); if (!m_converter) { std::cerr << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " @@ -476,7 +499,7 @@ size_t AudioCallbackPlaySource::getSourceChannelCount() const { - return m_bufferCount; + return m_sourceChannelCount; } size_t @@ -588,23 +611,26 @@ for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) { - RingBuffer &rb = *m_buffers[ch]; + RingBuffer *rb = getReadRingBuffer(ch); - // this is marginally more likely to leave our channels in - // sync after a processing failure than just passing "count": - size_t request = count; - if (ch > 0) request = got; + if (rb) { - got = rb.read(buffer[ch], request); + // this is marginally more likely to leave our channels in + // sync after a processing failure than just passing "count": + size_t request = count; + if (ch > 0) request = got; + + got = rb->read(buffer[ch], request); #ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", signalling for more (possibly)" << std::endl; + std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", signalling for more (possibly)" << std::endl; #endif - } + } - for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) { - for (size_t i = got; i < count; ++i) { - buffer[ch][i] = 0.0; + for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) { + for (size_t i = got; i < count; ++i) { + buffer[ch][i] = 0.0; + } } } @@ -619,20 +645,24 @@ for (size_t ch = 0; ch < getSourceChannelCount(); ++ch) { - RingBuffer &rb = *m_buffers[ch]; - size_t request = count; - if (ch > 0) request = got; // see above - got = rb.read(buffer[ch], request); + RingBuffer *rb = getReadRingBuffer(ch); + + if (rb) { + + size_t request = count; + if (ch > 0) request = got; // see above + got = rb->read(buffer[ch], request); #ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl; + std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl; #endif - for (size_t i = 0; i < count; ++i) { - ib[i] = buffer[ch][i]; + for (size_t i = 0; i < count; ++i) { + ib[i] = buffer[ch][i]; + } + + timeStretcher->run(ch); } - - timeStretcher->run(ch); } } else if (m_slowdownCounter >= timeStretcher->getFactor()) { @@ -659,6 +689,7 @@ return count; } +// Called from fill thread, m_playing true, mutex held void AudioCallbackPlaySource::fillBuffers() { @@ -666,9 +697,12 @@ static size_t tmpSize = 0; size_t space = 0; - for (size_t c = 0; c < m_bufferCount; ++c) { - size_t spaceHere = getRingBuffer(c).getWriteSpace(); - if (c == 0 || spaceHere < space) space = spaceHere; + for (size_t c = 0; c < getSourceChannelCount(); ++c) { + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + size_t spaceHere = wb->getWriteSpace(); + if (c == 0 || spaceHere < space) space = spaceHere; + } } if (space == 0) return; @@ -755,16 +789,12 @@ bufferPtrs[c] = nonintlv + c * orig; } - bool ended = !mixModels(f, orig, bufferPtrs); - got = orig; + got = mixModels(f, orig, bufferPtrs); // and interleave into first half for (size_t c = 0; c < channels; ++c) { - for (size_t i = 0; i < orig; ++i) { - float sample = 0; - if (i < got) { - sample = nonintlv[c * orig + i]; - } + for (size_t i = 0; i < got; ++i) { + float sample = nonintlv[c * got + i]; intlv[channels * i + c] = sample; } } @@ -772,14 +802,15 @@ SRC_DATA data; data.data_in = intlv; data.data_out = srcout; - data.input_frames = orig; + data.input_frames = got; data.output_frames = work; data.src_ratio = ratio; data.end_of_input = 0; int err = src_process(m_converter, &data); - size_t toCopy = size_t(work * ratio + 0.1); - +// size_t toCopy = size_t(work * ratio + 0.1); + size_t toCopy = size_t(got * ratio + 0.1); + if (err) { std::cerr << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " @@ -797,7 +828,8 @@ for (size_t i = 0; i < toCopy; ++i) { tmp[i] = srcout[channels * i + c]; } - getRingBuffer(c).write(tmp, toCopy); + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) wb->write(tmp, toCopy); } m_bufferedToFrame = f; @@ -823,16 +855,18 @@ } } - bool ended = !mixModels(f, space, bufferPtrs); + size_t got = mixModels(f, space, bufferPtrs); for (size_t c = 0; c < channels; ++c) { - getRingBuffer(c).write(bufferPtrs[c], space); + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) wb->write(bufferPtrs[c], got); #ifdef DEBUG_AUDIO_PLAY_SOURCE - std::cerr << "Wrote " << got << " frames for ch " << c << ", now " - << getRingBuffer(c).getReadSpace() << " to read" - << std::endl; + if (wb) + std::cerr << "Wrote " << got << " frames for ch " << c << ", now " + << wb->getReadSpace() << " to read" + << std::endl; #endif } @@ -841,12 +875,13 @@ } } -bool +size_t AudioCallbackPlaySource::mixModels(size_t &frame, size_t count, float **buffers) { size_t processed = 0; size_t chunkStart = frame; size_t chunkSize = count; + size_t selectionSize = 0; size_t nextChunkStart = chunkStart + chunkSize; bool looping = m_viewManager->getPlayLoopMode(); @@ -875,6 +910,7 @@ chunkSize = count - processed; nextChunkStart = chunkStart + chunkSize; + selectionSize = 0; size_t fadeIn = 0, fadeOut = 0; @@ -898,6 +934,10 @@ } else { + selectionSize = + selection.getEndFrame() - + selection.getStartFrame(); + if (chunkStart < selection.getStartFrame()) { chunkStart = selection.getStartFrame(); fadeIn = 50; @@ -905,7 +945,7 @@ nextChunkStart = chunkStart + chunkSize; - if (nextChunkStart > selection.getEndFrame()) { + if (nextChunkStart >= selection.getEndFrame()) { nextChunkStart = selection.getEndFrame(); fadeOut = 50; } @@ -924,6 +964,8 @@ nextChunkStart = chunkStart + chunkSize; } +// std::cerr << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << std::endl; + if (!chunkSize) { #ifdef DEBUG_AUDIO_PLAY_SOURCE std::cerr << "Ending selection playback at " << nextChunkStart << std::endl; @@ -932,7 +974,7 @@ // thread can tell where it's got to in the playback -- so // return the full amount here frame = frame + count; - return false; + return count; } #ifdef DEBUG_AUDIO_PLAY_SOURCE @@ -941,10 +983,10 @@ size_t got = 0; - if (chunkSize < 100) { + if (selectionSize < 100) { fadeIn = 0; fadeOut = 0; - } else if (chunkSize < 300) { + } else if (selectionSize < 300) { if (fadeIn > 0) fadeIn = 10; if (fadeOut > 0) fadeOut = 10; } @@ -956,8 +998,8 @@ } if (fadeOut > 0) { - if ((count - processed) * 2 < fadeOut) { - fadeOut = (count - processed) * 2; + if ((count - processed - chunkSize) * 2 < fadeOut) { + fadeOut = (count - processed - chunkSize) * 2; } } @@ -982,7 +1024,7 @@ #endif frame = nextChunkStart; - return true; + return processed; } void @@ -1000,6 +1042,14 @@ while (!s.m_exiting) { + if (s.m_readBuffers != s.m_writeBuffers) { + s.m_bufferScavenger.claim(s.m_readBuffers); + s.m_readBuffers = s.m_writeBuffers; + std::cerr << "unified" << std::endl; + } + + s.m_bufferScavenger.scavenge(); + s.m_timeStretcherScavenger.scavenge(); float ms = 100; @@ -1029,13 +1079,12 @@ std::cout << "AudioCallbackPlaySourceFillThread: playback state changed, resetting" << std::endl; #endif for (size_t c = 0; c < s.getSourceChannelCount(); ++c) { - s.getRingBuffer(c).reset(); + RingBuffer *rb = s.getReadRingBuffer(c); + if (rb) rb->reset(); } } previouslyPlaying = playing; - if (!playing) continue; - s.fillBuffers(); } diff -r 2edc0757ca75 -r f3d777b693f7 audioio/AudioCallbackPlaySource.h --- a/audioio/AudioCallbackPlaySource.h Thu Jan 26 11:56:09 2006 +0000 +++ b/audioio/AudioCallbackPlaySource.h Fri Jan 27 18:04:07 2006 +0000 @@ -178,9 +178,21 @@ ViewManager *m_viewManager; AudioGenerator *m_audioGenerator; + class RingBufferVector : public std::vector *> { + public: + virtual ~RingBufferVector() { + while (!empty()) { + delete *begin(); + erase(begin()); + } + } + }; + std::set m_models; - std::vector *> m_buffers; - size_t m_bufferCount; + RingBufferVector *m_readBuffers; + RingBufferVector *m_writeBuffers; + Scavenger m_bufferScavenger; + size_t m_sourceChannelCount; size_t m_blockSize; size_t m_sourceSampleRate; size_t m_targetSampleRate; @@ -193,10 +205,25 @@ float m_outputLeft; float m_outputRight; - RingBuffer &getRingBuffer(size_t c) { - return *m_buffers[c]; + RingBuffer *getWriteRingBuffer(size_t c) { + if (m_writeBuffers && c < m_writeBuffers->size()) { + return (*m_writeBuffers)[c]; + } else { + return 0; + } } + RingBuffer *getReadRingBuffer(size_t c) { + RingBufferVector *rb = m_readBuffers; + if (rb && c < rb->size()) { + return (*rb)[c]; + } else { + return 0; + } + } + + void clearRingBuffers(bool haveLock = false, size_t count = 0); + class TimeStretcherData { public: @@ -228,8 +255,11 @@ // Called from fill thread, m_playing true, mutex held void fillBuffers(); - // Called from fillBuffers - bool mixModels(size_t &frame, size_t count, float **buffers); + // Called from fillBuffers. Return the number of frames written, + // which will be count or fewer. Return in the frame argument the + // new buffered frame position (which may be earlier than the + // frame argument passed in, in the case of looping). + size_t mixModels(size_t &frame, size_t count, float **buffers); class AudioCallbackPlaySourceFillThread : public QThread {