# HG changeset patch # User Chris Cannam # Date 1202493075 0 # Node ID 9fc4b256c283f09255918d87a4d5e05afb061a82 # Parent db267a315058407d4c18cb0b0ec9870957624112 * PortAudio driver: do not specify frames per buffer, let PA decide * Remove old non-RubberBand time stretcher -- it doesn't work with varying buffer sizes such as the PA driver may now be using * Rewrite getCurrentPlayingFrame for greater correctness when using long buffer sizes (interpolating according to audio stream timestamp) * Several changes to make the timestretch management RT safe(r) diff -r db267a315058 -r 9fc4b256c283 audioio/AudioCallbackPlaySource.cpp --- a/audioio/AudioCallbackPlaySource.cpp Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioCallbackPlaySource.cpp Fri Feb 08 17:51:15 2008 +0000 @@ -26,12 +26,10 @@ #include "data/model/SparseOneDimensionalModel.h" #include "plugin/RealTimePluginInstance.h" -#ifdef HAVE_RUBBERBAND +#include "AudioCallbackPlayTarget.h" + #include using namespace RubberBand; -#else -#include "PhaseVocoderTimeStretcher.h" -#endif #include #include @@ -56,6 +54,9 @@ m_sourceSampleRate(0), m_targetSampleRate(0), m_playLatency(0), + m_target(0), + m_lastRetrievalTimestamp(0.0), + m_lastRetrievedBlockSize(0), m_playing(false), m_exiting(false), m_lastModelEndFrame(0), @@ -64,6 +65,10 @@ m_auditioningPlugin(0), m_auditioningPluginBypassed(false), m_timeStretcher(0), + m_stretchRatio(1.0), + m_stretcherInputCount(0), + m_stretcherInputs(0), + m_stretcherInputSizes(0), m_fillThread(0), m_converter(0), m_crapConverter(0), @@ -107,11 +112,14 @@ delete m_audioGenerator; + for (size_t i = 0; i < m_stretcherInputCount; ++i) { + delete[] m_stretcherInputs[i]; + } + delete[] m_stretcherInputSizes; + delete[] m_stretcherInputs; + m_bufferScavenger.scavenge(true); m_pluginScavenger.scavenge(true); -#ifndef HAVE_RUBBERBAND - m_timeStretcherScavenger.scavenge(true); -#endif } void @@ -371,6 +379,9 @@ // we're just re-seeking. m_mutex.lock(); + if (m_timeStretcher) { + m_timeStretcher->reset(); + } if (m_playing) { m_readBufferFill = m_writeBufferFill = startFrame; if (m_readBuffers) { @@ -391,6 +402,7 @@ m_audioGenerator->reset(); bool changed = !m_playing; + m_lastRetrievalTimestamp = 0; m_playing = true; m_condition.wakeAll(); if (changed) emit playStatusChanged(m_playing); @@ -402,6 +414,7 @@ bool changed = m_playing; m_playing = false; m_condition.wakeAll(); + m_lastRetrievalTimestamp = 0; if (changed) emit playStatusChanged(m_playing); } @@ -452,8 +465,9 @@ } void -AudioCallbackPlaySource::setTargetBlockSize(size_t size) +AudioCallbackPlaySource::setTarget(AudioCallbackPlayTarget *target, size_t size) { + m_target = target; // std::cout << "AudioCallbackPlaySource::setTargetBlockSize() -> " << size << std::endl; assert(size < m_ringBufferSize); m_blockSize = size; @@ -481,134 +495,190 @@ size_t AudioCallbackPlaySource::getCurrentPlayingFrame() { + // This method attempts to estimate which audio sample frame is + // "currently coming through the speakers". + bool resample = false; - double ratio = 1.0; + double resampleRatio = 1.0; - if (getSourceSampleRate() != getTargetSampleRate()) { - resample = true; - ratio = double(getSourceSampleRate()) / double(getTargetSampleRate()); - } + // We resample when filling the ring buffer, and time-stretch when + // draining it. The buffer contains data at the "target rate" and + // the latency provided by the target is also at the target rate. + // Because of the multiple rates involved, we do the actual + // calculation using RealTime instead. - size_t readSpace = 0; + size_t sourceRate = getSourceSampleRate(); + size_t targetRate = getTargetSampleRate(); + + if (sourceRate == 0 || targetRate == 0) return 0; + + size_t inbuffer = 0; // at target rate + for (size_t c = 0; c < getTargetChannelCount(); ++c) { RingBuffer *rb = getReadRingBuffer(c); if (rb) { - size_t spaceHere = rb->getReadSpace(); - if (c == 0 || spaceHere < readSpace) readSpace = spaceHere; + size_t here = rb->getReadSpace(); + if (c == 0 || here < inbuffer) inbuffer = here; } } - if (resample) { - readSpace = size_t(readSpace * ratio + 0.1); + size_t readBufferFill = m_readBufferFill; + size_t lastRetrievedBlockSize = m_lastRetrievedBlockSize; + double lastRetrievalTimestamp = m_lastRetrievalTimestamp; + double currentTime = 0.0; + if (m_target) currentTime = m_target->getCurrentTime(); + + RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, targetRate); + + size_t latency = m_playLatency; // at target rate + RealTime latency_t = RealTime::frame2RealTime(latency, targetRate); + + size_t stretchlat = 0; + double timeRatio = 1.0; + + if (m_timeStretcher) { + stretchlat = m_timeStretcher->getLatency(); + timeRatio = m_timeStretcher->getTimeRatio(); } - size_t latency = m_playLatency; - if (resample) latency = size_t(m_playLatency * ratio + 0.1); + RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, targetRate); -#ifdef HAVE_RUBBERBAND - if (m_timeStretcher) { - latency += m_timeStretcher->getLatency(); + // When the target has just requested a block from us, the last + // sample it obtained was our buffer fill frame count minus the + // amount of read space (converted back to source sample rate) + // remaining now. That sample is not expected to be played until + // the target's play latency has elapsed. By the time the + // following block is requested, that sample will be at the + // target's play latency minus the last requested block size away + // from being played. + + RealTime sincerequest_t = RealTime::zeroTime; + RealTime lastretrieved_t = RealTime::zeroTime; + + if (m_target && lastRetrievalTimestamp != 0.0) { + + lastretrieved_t = RealTime::frame2RealTime + (lastRetrievedBlockSize, targetRate); + + // calculate number of frames at target rate that have elapsed + // since the end of the last call to getSourceSamples + + double elapsed = currentTime - lastRetrievalTimestamp; + + if (elapsed > 0.0) { + sincerequest_t = RealTime::fromSeconds(elapsed); + } + + } else { + + lastretrieved_t = RealTime::frame2RealTime + (getTargetBlockSize(), targetRate); } -#else - PhaseVocoderTimeStretcher *timeStretcher = m_timeStretcher; - if (timeStretcher) { - latency += timeStretcher->getProcessingLatency(); + + RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, sourceRate); + + if (timeRatio != 1.0) { + lastretrieved_t = lastretrieved_t / timeRatio; + sincerequest_t = sincerequest_t / timeRatio; } -#endif - - latency += readSpace; - size_t bufferedFrame = m_readBufferFill; bool looping = m_viewManager->getPlayLoopMode(); bool constrained = (m_viewManager->getPlaySelectionMode() && !m_viewManager->getSelections().empty()); - size_t framePlaying = bufferedFrame; +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + std::cerr << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved: " << lastretrieved_t << std::endl; +#endif - if (looping && !constrained) { - while (framePlaying < latency) framePlaying += m_lastModelEndFrame; - } - - if (framePlaying > latency) framePlaying -= latency; - else framePlaying = 0; - -// std::cerr << "framePlaying = " << framePlaying << " -> reference "; - - framePlaying = m_viewManager->alignPlaybackFrameToReference(framePlaying); - -// std::cerr << framePlaying << std::endl; - - if (!constrained) { - if (!looping && framePlaying > m_lastModelEndFrame) { - framePlaying = m_lastModelEndFrame; - stop(); - } - return framePlaying; - } - - bufferedFrame = m_viewManager->alignPlaybackFrameToReference(bufferedFrame); + RealTime end = RealTime::frame2RealTime(m_lastModelEndFrame, sourceRate); MultiSelection::SelectionList selections = m_viewManager->getSelections(); MultiSelection::SelectionList::const_iterator i; -// i = selections.begin(); -// size_t rangeStart = i->getStartFrame(); + // these could be cached from one call to the next, if the + // selection has not changed... but some of the work would still + // need to be done because the playback model may have changed - i = selections.end(); - --i; - size_t rangeEnd = i->getEndFrame(); + std::vector rangeStarts; + std::vector rangeDurations; - for (i = selections.begin(); i != selections.end(); ++i) { - if (i->contains(bufferedFrame)) break; + int inRange = 0; + int index = 0; + + if (constrained) { + + for (i = selections.begin(); i != selections.end(); ++i) { + + RealTime start = + (RealTime::frame2RealTime + (m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), + sourceRate)); + RealTime duration = + (RealTime::frame2RealTime + (m_viewManager->alignReferenceToPlaybackFrame(i->getEndFrame()) - + m_viewManager->alignReferenceToPlaybackFrame(i->getStartFrame()), + sourceRate)); + + rangeStarts.push_back(start); + rangeDurations.push_back(duration); + + if (bufferedto_t >= start) { + inRange = index; + } + + ++index; + } } - size_t f = bufferedFrame; - -// std::cout << "getCurrentPlayingFrame: f=" << f << ", latency=" << latency << ", rangeEnd=" << rangeEnd << std::endl; - - if (i == selections.end()) { - --i; - if (i->getEndFrame() + latency < f) { -// std::cout << "framePlaying = " << framePlaying << ", rangeEnd = " << rangeEnd << std::endl; - - if (!looping && (framePlaying > rangeEnd)) { -// std::cout << "STOPPING" << std::endl; - stop(); - return rangeEnd; - } else { - return framePlaying; - } - } else { -// std::cout << "latency <- " << latency << "-(" << f << "-" << i->getEndFrame() << ")" << std::endl; - latency -= (f - i->getEndFrame()); - f = i->getEndFrame(); - } + if (rangeStarts.empty()) { + rangeStarts.push_back(RealTime::zeroTime); + rangeDurations.push_back(end); } -// std::cout << "i=(" << i->getStartFrame() << "," << i->getEndFrame() << ") f=" << f << ", latency=" << latency << std::endl; + if (inRange >= rangeStarts.size()) inRange = rangeStarts.size()-1; - while (latency > 0) { - size_t offset = f - i->getStartFrame(); - if (offset >= latency) { - if (f > latency) { - framePlaying = f - latency; - } else { - framePlaying = 0; - } - break; - } else { - if (i == selections.begin()) { - if (looping) { - i = selections.end(); - } - } - latency -= offset; - --i; - f = i->getEndFrame(); - } + RealTime playing_t = bufferedto_t - rangeStarts[inRange]; + + playing_t = playing_t + - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + + sincerequest_t; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + std::cerr << "playing_t as offset into range " << inRange << " (with start = " << rangeStarts[inRange] << ") = " << playing_t << std::endl; +#endif + + while (playing_t < RealTime::zeroTime) { + + if (inRange == 0) { + if (looping) { + inRange = rangeStarts.size() - 1; + } else { + break; + } + } else { + --inRange; + } + + playing_t = playing_t + rangeDurations[inRange]; } - return framePlaying; + playing_t = playing_t + rangeStarts[inRange]; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + std::cerr << " playing time: " << playing_t << std::endl; +#endif + + if (!looping) { + if (inRange == rangeStarts.size()-1 && + playing_t >= rangeStarts[inRange] + rangeDurations[inRange]) { + stop(); + } + } + + if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; + + size_t frame = RealTime::realTime2Frame(playing_t, sourceRate); + return m_viewManager->alignPlaybackFrameToReference(frame); } void @@ -758,65 +828,29 @@ } void -AudioCallbackPlaySource::setTimeStretch(float factor, bool sharpen, bool mono) +AudioCallbackPlaySource::setTimeStretch(float factor) { -#ifdef HAVE_RUBBERBAND - if (m_timeStretcher) { - m_timeStretchRatioMutex.lock(); - m_timeStretcher->setTimeRatio(factor); - m_timeStretchRatioMutex.unlock(); + m_stretchRatio = factor; + + if (m_timeStretcher || (factor == 1.f)) { + // stretch ratio will be set in next process call if appropriate return; } else { + m_stretcherInputCount = getTargetChannelCount(); RubberBandStretcher *stretcher = new RubberBandStretcher (getTargetSampleRate(), - getTargetChannelCount(), + m_stretcherInputCount, RubberBandStretcher::OptionProcessRealTime, factor); + m_stretcherInputs = new float *[m_stretcherInputCount]; + m_stretcherInputSizes = new size_t[m_stretcherInputCount]; + for (size_t c = 0; c < m_stretcherInputCount; ++c) { + m_stretcherInputSizes[c] = 16384; + m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; + } m_timeStretcher = stretcher; return; } -#else - // Avoid locks -- create, assign, mark old one for scavenging - // later (as a call to getSourceSamples may still be using it) - - PhaseVocoderTimeStretcher *existingStretcher = m_timeStretcher; - - size_t channels = getTargetChannelCount(); - if (mono) channels = 1; - - if (existingStretcher && - existingStretcher->getRatio() == factor && - existingStretcher->getSharpening() == sharpen && - existingStretcher->getChannelCount() == channels) { - return; - } - - if (factor != 1) { - - if (existingStretcher && - existingStretcher->getSharpening() == sharpen && - existingStretcher->getChannelCount() == channels) { - existingStretcher->setRatio(factor); - return; - } - - PhaseVocoderTimeStretcher *newStretcher = new PhaseVocoderTimeStretcher - (getTargetSampleRate(), - channels, - factor, - sharpen, - getTargetBlockSize()); - - m_timeStretcher = newStretcher; - - } else { - m_timeStretcher = 0; - } - - if (existingStretcher) { - m_timeStretcherScavenger.claim(existingStretcher); - } -#endif } size_t @@ -860,13 +894,22 @@ if (count == 0) return 0; -#ifdef HAVE_RUBBERBAND RubberBandStretcher *ts = m_timeStretcher; float ratio = ts ? ts->getTimeRatio() : 1.f; -#else - PhaseVocoderTimeStretcher *ts = m_timeStretcher; - float ratio = ts ? ts->getRatio() : 1.f; -#endif + + if (ratio != m_stretchRatio) { + if (!ts) { + std::cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << std::endl; + m_stretchRatio = 1.f; + } else { + ts->setTimeRatio(m_stretchRatio); + } + } + + if (m_target) { + m_lastRetrievedBlockSize = count; + m_lastRetrievalTimestamp = m_target->getCurrentTime(); + } if (!ts || ratio == 1.f) { @@ -900,68 +943,58 @@ applyAuditioningEffect(count, buffer); m_condition.wakeAll(); + return got; } size_t channels = getTargetChannelCount(); + size_t available; + int warned = 0; + size_t fedToStretcher = 0; -#ifdef HAVE_RUBBERBAND - bool mix = false; -#else - bool mix = (channels > 1 && ts->getChannelCount() == 1); + // The input block for a given output is approx output / ratio, + // but we can't predict it exactly, for an adaptive timestretcher. + + while ((available = ts->available()) < count) { + + size_t reqd = lrintf((count - available) / ratio); + reqd = std::max(reqd, ts->getSamplesRequired()); + if (reqd == 0) reqd = 1; + + size_t got = reqd; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + std::cerr << "reqd = " <available()) < count) { -#else - while ((available = ts->getAvailableOutputSamples()) < count) { + for (size_t c = 0; c < channels; ++c) { + if (c >= m_stretcherInputCount) continue; + RingBuffer *rb = getReadRingBuffer(c); + if (rb) { + size_t gotHere = rb->read(m_stretcherInputs[c], got); + if (gotHere < got) got = gotHere; + +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + if (c == 0) { + std::cerr << "feeding stretcher: got " << gotHere + << ", " << rb->getReadSpace() << " remain" << std::endl; + } #endif - - size_t reqd = lrintf((count - available) / ratio); -#ifdef HAVE_RUBBERBAND - reqd = std::max(reqd, ts->getSamplesRequired()); -#else - reqd = std::max(reqd, ts->getRequiredInputSamples()); -#endif - if (reqd == 0) reqd = 1; - float *ib[channels]; - - size_t got = reqd; - - if (mix) { - for (size_t c = 0; c < channels; ++c) { - if (c == 0) ib[c] = new float[reqd]; //!!! fix -- this is a rt function - else ib[c] = 0; - RingBuffer *rb = getReadRingBuffer(c); - if (rb) { - size_t gotHere; - if (c > 0) gotHere = rb->readAdding(ib[0], got); - else gotHere = rb->read(ib[0], got); - if (gotHere < got) got = gotHere; - } - } - } else { - for (size_t c = 0; c < channels; ++c) { - ib[c] = new float[reqd]; //!!! fix -- this is a rt function - RingBuffer *rb = getReadRingBuffer(c); - if (rb) { - size_t gotHere = rb->read(ib[c], got); - if (gotHere < got) got = gotHere; - } + } else { + std::cerr << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << std::endl; } } @@ -969,46 +1002,20 @@ std::cerr << "WARNING: Read underrun in playback (" << got << " < " << reqd << ")" << std::endl; } - -#ifdef HAVE_RUBBERBAND - ts->process(ib, got, false); -#else - ts->putInput(ib, got); -#endif - for (size_t c = 0; c < channels; ++c) { - delete[] ib[c]; - } + ts->process(m_stretcherInputs, got, false); + + fedToStretcher += got; if (got == 0) break; -#ifdef HAVE_RUBBERBAND if (ts->available() == available) { -#else - if (ts->getAvailableOutputSamples() == available) { -#endif std::cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << std::endl; if (++warned == 5) break; } } -#ifdef HAVE_RUBBERBAND ts->retrieve(buffer, count); - m_timeStretchRatioMutex.unlock(); -#else - ts->getOutput(buffer, count); -#endif - - if (mix) { - for (size_t c = 1; c < channels; ++c) { - for (size_t i = 0; i < count; ++i) { - buffer[c][i] = buffer[0][i] / channels; - } - } - for (size_t i = 0; i < count; ++i) { - buffer[0][i] /= channels; - } - } applyAuditioningEffect(count, buffer); @@ -1184,11 +1191,7 @@ int err = 0; -#ifdef HAVE_RUBBERBAND if (m_timeStretcher && m_timeStretcher->getTimeRatio() < 0.4) { -#else - if (m_timeStretcher && m_timeStretcher->getRatio() < 0.4) { -#endif #ifdef DEBUG_AUDIO_PLAY_SOURCE std::cout << "Using crappy converter" << std::endl; #endif @@ -1227,7 +1230,13 @@ // space must be a multiple of generatorBlockSize space = (space / generatorBlockSize) * generatorBlockSize; - if (space == 0) return false; + if (space == 0) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + std::cout << "requested fill is less than generator block size of " + << generatorBlockSize << ", leaving it" << std::endl; +#endif + return false; + } if (tmpSize < channels * space) { delete[] tmp; @@ -1513,9 +1522,6 @@ s.unifyRingBuffers(); s.m_bufferScavenger.scavenge(); s.m_pluginScavenger.scavenge(); -#ifndef HAVE_RUBBERBAND - s.m_timeStretcherScavenger.scavenge(); -#endif if (work && s.m_playing && s.getSourceSampleRate()) { diff -r db267a315058 -r 9fc4b256c283 audioio/AudioCallbackPlaySource.h --- a/audioio/AudioCallbackPlaySource.h Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioCallbackPlaySource.h Fri Feb 08 17:51:15 2008 +0000 @@ -32,17 +32,16 @@ #include #include -#ifdef HAVE_RUBBERBAND -#include -#else -class PhaseVocoderTimeStretcher; -#endif +namespace RubberBand { + class RubberBandStretcher; +} class Model; class ViewManager; class AudioGenerator; class PlayParameters; class RealTimePluginInstance; +class AudioCallbackPlayTarget; /** * AudioCallbackPlaySource manages audio data supply to callback-based @@ -107,13 +106,16 @@ virtual size_t getPlayEndFrame() { return m_lastModelEndFrame; } /** - * Set the block size of the target audio device. This should - * be called by the target class. + * Set the target and the block size of the target audio device. + * This should be called by the target class. */ - void setTargetBlockSize(size_t); + void setTarget(AudioCallbackPlayTarget *, size_t blockSize); /** - * Get the block size of the target audio device. + * Get the block size of the target audio device. This may be an + * estimate or upper bound, if the target has a variable block + * size; the source should behave itself even if this value turns + * out to be inaccurate. */ size_t getTargetBlockSize() const; @@ -190,12 +192,9 @@ size_t getSourceSamples(size_t count, float **buffer); /** - * Set the time stretcher factor (i.e. playback speed). Also - * specify whether the time stretcher will be variable rate - * (sharpening transients), and whether time stretching will be - * carried out on data mixed down to mono for speed. + * Set the time stretcher factor (i.e. playback speed). */ - void setTimeStretch(float factor, bool sharpen, bool mono); + void setTimeStretch(float factor); /** * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is @@ -279,6 +278,9 @@ size_t m_sourceSampleRate; size_t m_targetSampleRate; size_t m_playLatency; + AudioCallbackPlayTarget *m_target; + double m_lastRetrievalTimestamp; + size_t m_lastRetrievedBlockSize; bool m_playing; bool m_exiting; size_t m_lastModelEndFrame; @@ -309,13 +311,12 @@ void clearRingBuffers(bool haveLock = false, size_t count = 0); void unifyRingBuffers(); -#ifdef HAVE_RUBBERBAND RubberBand::RubberBandStretcher *m_timeStretcher; - QMutex m_timeStretchRatioMutex; -#else - PhaseVocoderTimeStretcher *m_timeStretcher; - Scavenger m_timeStretcherScavenger; -#endif + float m_stretchRatio; + + size_t m_stretcherInputCount; + float **m_stretcherInputs; + size_t *m_stretcherInputSizes; // Called from fill thread, m_playing true, mutex held // Return true if work done diff -r db267a315058 -r 9fc4b256c283 audioio/AudioCallbackPlayTarget.h --- a/audioio/AudioCallbackPlayTarget.h Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioCallbackPlayTarget.h Fri Feb 08 17:51:15 2008 +0000 @@ -32,6 +32,8 @@ virtual void shutdown() = 0; + virtual double getCurrentTime() const = 0; + float getOutputGain() const { return m_outputGain; } diff -r db267a315058 -r 9fc4b256c283 audioio/AudioJACKTarget.cpp --- a/audioio/AudioJACKTarget.cpp Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioJACKTarget.cpp Fri Feb 08 17:51:15 2008 +0000 @@ -257,6 +257,10 @@ { std::cerr << "AudioJACKTarget::~AudioJACKTarget()" << std::endl; + if (m_source) { + m_source->setTarget(0, m_bufferSize); + } + shutdown(); if (m_client) { @@ -293,6 +297,16 @@ return (m_client != 0); } +double +AudioJACKTarget::getCurrentTime() const +{ + if (m_client && m_sampleRate) { + return double(jack_frame_time(m_client)) / double(m_sampleRate); + } else { + return 0.0; + } +} + int AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg) { @@ -310,7 +324,7 @@ { m_mutex.lock(); - m_source->setTargetBlockSize(m_bufferSize); + m_source->setTarget(this, m_bufferSize); m_source->setTargetSampleRate(m_sampleRate); size_t channels = m_source->getSourceChannelCount(); diff -r db267a315058 -r 9fc4b256c283 audioio/AudioJACKTarget.h --- a/audioio/AudioJACKTarget.h Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioJACKTarget.h Fri Feb 08 17:51:15 2008 +0000 @@ -39,6 +39,8 @@ virtual bool isOK() const; + virtual double getCurrentTime() const; + public slots: virtual void sourceModelReplaced(); diff -r db267a315058 -r 9fc4b256c283 audioio/AudioPortAudioTarget.cpp --- a/audioio/AudioPortAudioTarget.cpp Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioPortAudioTarget.cpp Fri Feb 08 17:51:15 2008 +0000 @@ -69,7 +69,8 @@ op.sampleFormat = paFloat32; op.suggestedLatency = 0.2; op.hostApiSpecificStreamInfo = 0; - err = Pa_OpenStream(&m_stream, 0, &op, m_sampleRate, m_bufferSize, + err = Pa_OpenStream(&m_stream, 0, &op, m_sampleRate, + paFramesPerBufferUnspecified, paNoFlag, processStatic, this); #endif @@ -83,6 +84,7 @@ #ifndef HAVE_PORTAUDIO_V18 const PaStreamInfo *info = Pa_GetStreamInfo(m_stream); m_latency = int(info->outputLatency * m_sampleRate + 0.001); + m_bufferSize = m_latency; #endif std::cerr << "PortAudio latency = " << m_latency << " frames" << std::endl; @@ -99,7 +101,7 @@ if (m_source) { std::cerr << "AudioPortAudioTarget: block size " << m_bufferSize << std::endl; - m_source->setTargetBlockSize(m_bufferSize); + m_source->setTarget(this, m_bufferSize); m_source->setTargetSampleRate(m_sampleRate); m_source->setTargetPlayLatency(m_latency); } @@ -113,6 +115,10 @@ { std::cerr << "AudioPortAudioTarget::~AudioPortAudioTarget()" << std::endl; + if (m_source) { + m_source->setTarget(0, m_bufferSize); + } + shutdown(); if (m_stream) { @@ -150,6 +156,13 @@ return (m_stream != 0); } +double +AudioPortAudioTarget::getCurrentTime() const +{ + if (!m_stream) return 0.0; + else return Pa_GetStreamTime(m_stream); +} + #ifdef HAVE_PORTAUDIO_V18 int AudioPortAudioTarget::processStatic(void *input, void *output, diff -r db267a315058 -r 9fc4b256c283 audioio/AudioPortAudioTarget.h --- a/audioio/AudioPortAudioTarget.h Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/AudioPortAudioTarget.h Fri Feb 08 17:51:15 2008 +0000 @@ -41,6 +41,8 @@ virtual bool isOK() const; + virtual double getCurrentTime() const; + public slots: virtual void sourceModelReplaced(); diff -r db267a315058 -r 9fc4b256c283 audioio/PhaseVocoderTimeStretcher.cpp --- a/audioio/PhaseVocoderTimeStretcher.cpp Thu Feb 07 12:35:08 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,629 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam and QMUL. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef HAVE_RUBBERBAND - -#include "PhaseVocoderTimeStretcher.h" - -#include -#include - -#include - -//#define DEBUG_PHASE_VOCODER_TIME_STRETCHER 1 - -PhaseVocoderTimeStretcher::PhaseVocoderTimeStretcher(size_t sampleRate, - size_t channels, - float ratio, - bool sharpen, - size_t maxOutputBlockSize) : - m_sampleRate(sampleRate), - m_channels(channels), - m_maxOutputBlockSize(maxOutputBlockSize), - m_ratio(ratio), - m_sharpen(sharpen), - m_totalCount(0), - m_transientCount(0), - m_n2sum(0), - m_mutex(new QMutex()) -{ - initialise(); -} - -PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher() -{ - std::cerr << "PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher" << std::endl; - - cleanup(); - - delete m_mutex; -} - -void -PhaseVocoderTimeStretcher::initialise() -{ - std::cerr << "PhaseVocoderTimeStretcher::initialise" << std::endl; - - calculateParameters(); - - m_analysisWindow = new Window(HanningWindow, m_wlen); - m_synthesisWindow = new Window(HanningWindow, m_wlen); - - m_prevPhase = new float *[m_channels]; - m_prevAdjustedPhase = new float *[m_channels]; - - m_prevTransientMag = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); - m_prevTransientScore = 0; - m_prevTransient = false; - - m_tempbuf = (float *)fftf_malloc(sizeof(float) * m_wlen); - - m_time = new float *[m_channels]; - m_freq = new fftf_complex *[m_channels]; - m_plan = new fftf_plan[m_channels]; - m_iplan = new fftf_plan[m_channels]; - - m_inbuf = new RingBuffer *[m_channels]; - m_outbuf = new RingBuffer *[m_channels]; - m_mashbuf = new float *[m_channels]; - - m_modulationbuf = (float *)fftf_malloc(sizeof(float) * m_wlen); - - for (size_t c = 0; c < m_channels; ++c) { - - m_prevPhase[c] = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); - m_prevAdjustedPhase[c] = (float *)fftf_malloc(sizeof(float) * (m_wlen / 2 + 1)); - - m_time[c] = (float *)fftf_malloc(sizeof(float) * m_wlen); - m_freq[c] = (fftf_complex *)fftf_malloc(sizeof(fftf_complex) * - (m_wlen / 2 + 1)); - - m_plan[c] = fftf_plan_dft_r2c_1d(m_wlen, m_time[c], m_freq[c], FFTW_MEASURE); - m_iplan[c] = fftf_plan_dft_c2r_1d(m_wlen, m_freq[c], m_time[c], FFTW_MEASURE); - - m_outbuf[c] = new RingBuffer - ((m_maxOutputBlockSize + m_wlen) * 2); - m_inbuf[c] = new RingBuffer - (lrintf(m_outbuf[c]->getSize() / m_ratio) + m_wlen); - - std::cerr << "making inbuf size " << m_inbuf[c]->getSize() << " (outbuf size is " << m_outbuf[c]->getSize() << ", ratio " << m_ratio << ")" << std::endl; - - - m_mashbuf[c] = (float *)fftf_malloc(sizeof(float) * m_wlen); - - for (size_t i = 0; i < m_wlen; ++i) { - m_mashbuf[c][i] = 0.0; - } - - for (size_t i = 0; i <= m_wlen/2; ++i) { - m_prevPhase[c][i] = 0.0; - m_prevAdjustedPhase[c][i] = 0.0; - } - } - - for (size_t i = 0; i < m_wlen; ++i) { - m_modulationbuf[i] = 0.0; - } - - for (size_t i = 0; i <= m_wlen/2; ++i) { - m_prevTransientMag[i] = 0.0; - } -} - -void -PhaseVocoderTimeStretcher::calculateParameters() -{ - std::cerr << "PhaseVocoderTimeStretcher::calculateParameters" << std::endl; - - m_wlen = 1024; - - //!!! In transient sharpening mode, we need to pick the window - //length so as to be more or less fixed in audio duration (i.e. we - //need to exploit the sample rate) - - //!!! have to work out the relationship between wlen and transient - //threshold - - if (m_ratio < 1) { - if (m_ratio < 0.4) { - m_n1 = 1024; - m_wlen = 2048; - } else if (m_ratio < 0.8) { - m_n1 = 512; - } else { - m_n1 = 256; - } - if (shouldSharpen()) { - m_wlen = 2048; - } - m_n2 = lrintf(m_n1 * m_ratio); - } else { - if (m_ratio > 2) { - m_n2 = 512; - m_wlen = 4096; - } else if (m_ratio > 1.6) { - m_n2 = 384; - m_wlen = 2048; - } else { - m_n2 = 256; - } - if (shouldSharpen()) { - if (m_wlen < 2048) m_wlen = 2048; - } - m_n1 = lrintf(m_n2 / m_ratio); - if (m_n1 == 0) { - m_n1 = 1; - m_n2 = lrintf(m_ratio); - } - } - - m_transientThreshold = lrintf(m_wlen / 4.5); - - m_totalCount = 0; - m_transientCount = 0; - m_n2sum = 0; - - - std::cerr << "PhaseVocoderTimeStretcher: channels = " << m_channels - << ", ratio = " << m_ratio - << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = " - << m_wlen << ", max = " << m_maxOutputBlockSize << std::endl; -// << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl; -} - -void -PhaseVocoderTimeStretcher::cleanup() -{ - std::cerr << "PhaseVocoderTimeStretcher::cleanup" << std::endl; - - for (size_t c = 0; c < m_channels; ++c) { - - fftf_destroy_plan(m_plan[c]); - fftf_destroy_plan(m_iplan[c]); - - fftf_free(m_time[c]); - fftf_free(m_freq[c]); - - fftf_free(m_mashbuf[c]); - fftf_free(m_prevPhase[c]); - fftf_free(m_prevAdjustedPhase[c]); - - delete m_inbuf[c]; - delete m_outbuf[c]; - } - - fftf_free(m_tempbuf); - fftf_free(m_modulationbuf); - fftf_free(m_prevTransientMag); - - delete[] m_prevPhase; - delete[] m_prevAdjustedPhase; - delete[] m_inbuf; - delete[] m_outbuf; - delete[] m_mashbuf; - delete[] m_time; - delete[] m_freq; - delete[] m_plan; - delete[] m_iplan; - - delete m_analysisWindow; - delete m_synthesisWindow; -} - -void -PhaseVocoderTimeStretcher::setRatio(float ratio) -{ - QMutexLocker locker(m_mutex); - - size_t formerWlen = m_wlen; - m_ratio = ratio; - - std::cerr << "PhaseVocoderTimeStretcher::setRatio: new ratio " << ratio - << std::endl; - - calculateParameters(); - - if (m_wlen == formerWlen) { - - // This is the only container whose size depends on m_ratio - - RingBuffer **newin = new RingBuffer *[m_channels]; - - size_t formerSize = m_inbuf[0]->getSize(); - size_t newSize = lrintf(m_outbuf[0]->getSize() / m_ratio) + m_wlen; - - std::cerr << "resizing inbuf from " << formerSize << " to " - << newSize << " (outbuf size is " << m_outbuf[0]->getSize() << ", ratio " << m_ratio << ")" << std::endl; - - if (formerSize != newSize) { - - size_t ready = m_inbuf[0]->getReadSpace(); - - for (size_t c = 0; c < m_channels; ++c) { - newin[c] = new RingBuffer(newSize); - } - - if (ready > 0) { - - size_t copy = std::min(ready, newSize); - float *tmp = new float[ready]; - - for (size_t c = 0; c < m_channels; ++c) { - m_inbuf[c]->read(tmp, ready); - newin[c]->write(tmp + ready - copy, copy); - } - - delete[] tmp; - } - - for (size_t c = 0; c < m_channels; ++c) { - delete m_inbuf[c]; - } - - delete[] m_inbuf; - m_inbuf = newin; - } - - } else { - - std::cerr << "wlen changed" << std::endl; - cleanup(); - initialise(); - } -} - -size_t -PhaseVocoderTimeStretcher::getProcessingLatency() const -{ - return getWindowSize() - getInputIncrement(); -} - -size_t -PhaseVocoderTimeStretcher::getRequiredInputSamples() const -{ - QMutexLocker locker(m_mutex); - - if (m_inbuf[0]->getReadSpace() >= m_wlen) return 0; - return m_wlen - m_inbuf[0]->getReadSpace(); -} - -void -PhaseVocoderTimeStretcher::putInput(float **input, size_t samples) -{ - QMutexLocker locker(m_mutex); - - // We need to add samples from input to our internal buffer. When - // we have m_windowSize samples in the buffer, we can process it, - // move the samples back by m_n1 and write the output onto our - // internal output buffer. If we have (samples * ratio) samples - // in that, we can write m_n2 of them back to output and return - // (otherwise we have to write zeroes). - - // When we process, we write m_wlen to our fixed output buffer - // (m_mashbuf). We then pull out the first m_n2 samples from that - // buffer, push them into the output ring buffer, and shift - // m_mashbuf left by that amount. - - // The processing latency is then m_wlen - m_n2. - - size_t consumed = 0; - - while (consumed < samples) { - - size_t writable = m_inbuf[0]->getWriteSpace(); - writable = std::min(writable, samples - consumed); - - if (writable == 0) { -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "WARNING: PhaseVocoderTimeStretcher::putInput: writable == 0 (inbuf has " << m_inbuf[0]->getReadSpace() << " samples available for reading, space for " << m_inbuf[0]->getWriteSpace() << " more)" << std::endl; -#endif - if (m_inbuf[0]->getReadSpace() < m_wlen || - m_outbuf[0]->getWriteSpace() < m_n2) { - std::cerr << "WARNING: PhaseVocoderTimeStretcher::putInput: Inbuf has " << m_inbuf[0]->getReadSpace() << ", outbuf has space for " << m_outbuf[0]->getWriteSpace() << " (n2 = " << m_n2 << ", wlen = " << m_wlen << "), won't be able to process" << std::endl; - break; - } - } else { - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "writing " << writable << " from index " << consumed << " to inbuf, consumed will be " << consumed + writable << std::endl; -#endif - - for (size_t c = 0; c < m_channels; ++c) { - m_inbuf[c]->write(input[c] + consumed, writable); - } - consumed += writable; - } - - while (m_inbuf[0]->getReadSpace() >= m_wlen && - m_outbuf[0]->getWriteSpace() >= m_n2) { - - // We know we have at least m_wlen samples available - // in m_inbuf. We need to peek m_wlen of them for - // processing, and then read m_n1 to advance the read - // pointer. - - for (size_t c = 0; c < m_channels; ++c) { - - size_t got = m_inbuf[c]->peek(m_tempbuf, m_wlen); - assert(got == m_wlen); - - analyseBlock(c, m_tempbuf); - } - - bool transient = false; - if (shouldSharpen()) transient = isTransient(); - - size_t n2 = m_n2; - - if (transient) { - n2 = m_n1; - } - - ++m_totalCount; - if (transient) ++m_transientCount; - m_n2sum += n2; - -// std::cerr << "ratio for last 10: " < 50 && m_transientCount < m_totalCount) { - - int fixed = lrintf(m_transientCount * m_n1); - - int idealTotal = lrintf(m_totalCount * m_n1 * m_ratio); - int idealSquashy = idealTotal - fixed; - - int squashyCount = m_totalCount - m_transientCount; - - n2 = lrintf(idealSquashy / squashyCount); - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - if (n2 != m_n2) { - std::cerr << m_n2 << " -> " << n2 << std::endl; - } -#endif - } - - for (size_t c = 0; c < m_channels; ++c) { - - synthesiseBlock(c, m_mashbuf[c], - c == 0 ? m_modulationbuf : 0, - m_prevTransient ? m_n1 : m_n2); - - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "writing first " << m_n2 << " from mashbuf, skipping " << m_n1 << " on inbuf " << std::endl; -#endif - m_inbuf[c]->skip(m_n1); - - for (size_t i = 0; i < n2; ++i) { - if (m_modulationbuf[i] > 0.f) { - m_mashbuf[c][i] /= m_modulationbuf[i]; - } - } - - m_outbuf[c]->write(m_mashbuf[c], n2); - - for (size_t i = 0; i < m_wlen - n2; ++i) { - m_mashbuf[c][i] = m_mashbuf[c][i + n2]; - } - - for (size_t i = m_wlen - n2; i < m_wlen; ++i) { - m_mashbuf[c][i] = 0.0f; - } - } - - m_prevTransient = transient; - - for (size_t i = 0; i < m_wlen - n2; ++i) { - m_modulationbuf[i] = m_modulationbuf[i + n2]; - } - - for (size_t i = m_wlen - n2; i < m_wlen; ++i) { - m_modulationbuf[i] = 0.0f; - } - - if (!transient) m_n2 = n2; - } - - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "loop ended: inbuf read space " << m_inbuf[0]->getReadSpace() << ", outbuf write space " << m_outbuf[0]->getWriteSpace() << std::endl; -#endif - } - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "PhaseVocoderTimeStretcher::putInput returning" << std::endl; -#endif - -// std::cerr << "ratio: nominal: " << getRatio() << " actual: " -// << m_total2 << "/" << m_total1 << " = " << float(m_total2) / float(m_total1) << " ideal: " << m_ratio << std::endl; -} - -size_t -PhaseVocoderTimeStretcher::getAvailableOutputSamples() const -{ - QMutexLocker locker(m_mutex); - - return m_outbuf[0]->getReadSpace(); -} - -void -PhaseVocoderTimeStretcher::getOutput(float **output, size_t samples) -{ - QMutexLocker locker(m_mutex); - - if (m_outbuf[0]->getReadSpace() < samples) { - std::cerr << "WARNING: PhaseVocoderTimeStretcher::getOutput: not enough data (yet?) (" << m_outbuf[0]->getReadSpace() << " < " << samples << ")" << std::endl; - size_t fill = samples - m_outbuf[0]->getReadSpace(); - for (size_t c = 0; c < m_channels; ++c) { - for (size_t i = 0; i < fill; ++i) { - output[c][i] = 0.0; - } - m_outbuf[c]->read(output[c] + fill, m_outbuf[c]->getReadSpace()); - } - } else { -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "enough data - writing " << samples << " from outbuf" << std::endl; -#endif - for (size_t c = 0; c < m_channels; ++c) { - m_outbuf[c]->read(output[c], samples); - } - } - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "PhaseVocoderTimeStretcher::getOutput returning" << std::endl; -#endif -} - -void -PhaseVocoderTimeStretcher::analyseBlock(size_t c, float *buf) -{ - size_t i; - - // buf contains m_wlen samples - -#ifdef DEBUG_PHASE_VOCODER_TIME_STRETCHER - std::cerr << "PhaseVocoderTimeStretcher::analyseBlock (channel " << c << ")" << std::endl; -#endif - - m_analysisWindow->cut(buf); - - for (i = 0; i < m_wlen/2; ++i) { - float temp = buf[i]; - buf[i] = buf[i + m_wlen/2]; - buf[i + m_wlen/2] = temp; - } - - for (i = 0; i < m_wlen; ++i) { - m_time[c][i] = buf[i]; - } - - fftf_execute(m_plan[c]); // m_time -> m_freq -} - -bool -PhaseVocoderTimeStretcher::isTransient() -{ - int count = 0; - - for (size_t i = 0; i <= m_wlen/2; ++i) { - - float real = 0.f, imag = 0.f; - - for (size_t c = 0; c < m_channels; ++c) { - real += m_freq[c][i][0]; - imag += m_freq[c][i][1]; - } - - float sqrmag = (real * real + imag * imag); - - if (m_prevTransientMag[i] > 0.f) { - float diff = 10.f * log10f(sqrmag / m_prevTransientMag[i]); - if (diff > 3.f) ++count; - } - - m_prevTransientMag[i] = sqrmag; - } - - bool isTransient = false; - -// if (count > m_transientThreshold && -// count > m_prevTransientScore * 1.2) { - if (count > m_prevTransientScore && - count > m_transientThreshold && - count - m_prevTransientScore > int(m_wlen) / 20) { - isTransient = true; - - -// std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ")" << std::endl; -// } else { -// std::cerr << " !transient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ")" << std::endl; - } - - m_prevTransientScore = count; - - return isTransient; -} - -void -PhaseVocoderTimeStretcher::synthesiseBlock(size_t c, - float *out, - float *modulation, - size_t lastStep) -{ - bool unchanged = (lastStep == m_n1); - - for (size_t i = 0; i <= m_wlen/2; ++i) { - - float phase = princargf(atan2f(m_freq[c][i][1], m_freq[c][i][0])); - float adjustedPhase = phase; - - if (!unchanged) { - - float omega = (2 * M_PI * m_n1 * i) / m_wlen; - - float expectedPhase = m_prevPhase[c][i] + omega; - - float phaseError = princargf(phase - expectedPhase); - - float phaseIncrement = (omega + phaseError) / m_n1; - - adjustedPhase = m_prevAdjustedPhase[c][i] + - lastStep * phaseIncrement; - - float mag = sqrtf(m_freq[c][i][0] * m_freq[c][i][0] + - m_freq[c][i][1] * m_freq[c][i][1]); - - float real = mag * cosf(adjustedPhase); - float imag = mag * sinf(adjustedPhase); - m_freq[c][i][0] = real; - m_freq[c][i][1] = imag; - } - - m_prevPhase[c][i] = phase; - m_prevAdjustedPhase[c][i] = adjustedPhase; - } - - fftf_execute(m_iplan[c]); // m_freq -> m_time, inverse fft - - for (size_t i = 0; i < m_wlen/2; ++i) { - float temp = m_time[c][i]; - m_time[c][i] = m_time[c][i + m_wlen/2]; - m_time[c][i + m_wlen/2] = temp; - } - - for (size_t i = 0; i < m_wlen; ++i) { - m_time[c][i] = m_time[c][i] / m_wlen; - } - - m_synthesisWindow->cut(m_time[c]); - - for (size_t i = 0; i < m_wlen; ++i) { - out[i] += m_time[c][i]; - } - - if (modulation) { - - float area = m_analysisWindow->getArea(); - - for (size_t i = 0; i < m_wlen; ++i) { - float val = m_synthesisWindow->getValue(i); - modulation[i] += val * area; - } - } -} - - -#endif diff -r db267a315058 -r 9fc4b256c283 audioio/PhaseVocoderTimeStretcher.h --- a/audioio/PhaseVocoderTimeStretcher.h Thu Feb 07 12:35:08 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam and QMUL. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _PHASE_VOCODER_TIME_STRETCHER_H_ -#define _PHASE_VOCODER_TIME_STRETCHER_H_ - -#ifndef HAVE_RUBBERBAND - -#include "base/Window.h" -#include "base/RingBuffer.h" - -#include "data/fft/FFTapi.h" - -#include - -/** - * A time stretcher that alters the performance speed of audio, - * preserving pitch. - * - * This is based on the straightforward phase vocoder with phase - * unwrapping (as in e.g. the DAFX book pp275-), with optional - * percussive transient detection to avoid smearing percussive notes - * and resynchronise phases, and adding a stream API for real-time - * use. Principles and methods from Chris Duxbury, AES 2002 and 2004 - * thesis; Emmanuel Ravelli, DAFX 2005; Dan Barry, ISSC 2005 on - * percussion detection; code by Chris Cannam. - */ - -class PhaseVocoderTimeStretcher -{ -public: - PhaseVocoderTimeStretcher(size_t sampleRate, - size_t channels, - float ratio, - bool sharpen, - size_t maxOutputBlockSize); - virtual ~PhaseVocoderTimeStretcher(); - - /** - * Return the number of samples that would need to be added via - * putInput in order to provoke the time stretcher into doing some - * time stretching and making more output samples available. - * This will be an estimate, if transient sharpening is on; the - * caller may need to do the put/get/test cycle more than once. - */ - size_t getRequiredInputSamples() const; - - /** - * Put (and possibly process) a given number of input samples. - * Number should usually equal the value returned from - * getRequiredInputSamples(). - */ - void putInput(float **input, size_t samples); - - /** - * Get the number of processed samples ready for reading. - */ - size_t getAvailableOutputSamples() const; - - /** - * Get some processed samples. - */ - void getOutput(float **output, size_t samples); - - //!!! and reset? - - /** - * Change the time stretch ratio. - */ - void setRatio(float ratio); - - /** - * Get the hop size for input. - */ - size_t getInputIncrement() const { return m_n1; } - - /** - * Get the hop size for output. - */ - size_t getOutputIncrement() const { return m_n2; } - - /** - * Get the window size for FFT processing. - */ - size_t getWindowSize() const { return m_wlen; } - - /** - * Get the stretch ratio. - */ - float getRatio() const { return float(m_n2) / float(m_n1); } - - /** - * Return whether this time stretcher will attempt to sharpen transients. - */ - bool getSharpening() const { return m_sharpen; } - - /** - * Return the number of channels for this time stretcher. - */ - size_t getChannelCount() const { return m_channels; } - - /** - * Get the latency added by the time stretcher, in sample frames. - * This will be exact if transient sharpening is off, or approximate - * if it is on. - */ - size_t getProcessingLatency() const; - -protected: - /** - * Process a single phase vocoder frame from "in" into - * m_freq[channel]. - */ - void analyseBlock(size_t channel, float *in); // into m_freq[channel] - - /** - * Examine m_freq[0..m_channels-1] and return whether a percussive - * transient is found. - */ - bool isTransient(); - - /** - * Resynthesise from m_freq[channel] adding in to "out", - * adjusting phases on the basis of a prior step size of lastStep. - * Also add the window shape in to the modulation array (if - * present) -- for use in ensuring the output has the correct - * magnitude afterwards. - */ - void synthesiseBlock(size_t channel, float *out, float *modulation, - size_t lastStep); - - void initialise(); - void calculateParameters(); - void cleanup(); - - bool shouldSharpen() { - return m_sharpen && (m_ratio > 0.25); - } - - size_t m_sampleRate; - size_t m_channels; - size_t m_maxOutputBlockSize; - float m_ratio; - bool m_sharpen; - size_t m_n1; - size_t m_n2; - size_t m_wlen; - Window *m_analysisWindow; - Window *m_synthesisWindow; - - int m_totalCount; - int m_transientCount; - int m_n2sum; - - float **m_prevPhase; - float **m_prevAdjustedPhase; - - float *m_prevTransientMag; - int m_prevTransientScore; - int m_transientThreshold; - bool m_prevTransient; - - float *m_tempbuf; - float **m_time; - fftf_complex **m_freq; - fftf_plan *m_plan; - fftf_plan *m_iplan; - - RingBuffer **m_inbuf; - RingBuffer **m_outbuf; - float **m_mashbuf; - float *m_modulationbuf; - - QMutex *m_mutex; -}; - -#endif - -#endif diff -r db267a315058 -r 9fc4b256c283 audioio/audioio.pro --- a/audioio/audioio.pro Thu Feb 07 12:35:08 2008 +0000 +++ b/audioio/audioio.pro Fri Feb 08 17:51:15 2008 +0000 @@ -19,7 +19,6 @@ AudioJACKTarget.h \ AudioPortAudioTarget.h \ AudioTargetFactory.h \ - PhaseVocoderTimeStretcher.h \ PlaySpeedRangeMapper.h SOURCES += AudioCallbackPlaySource.cpp \ AudioCallbackPlayTarget.cpp \ @@ -28,5 +27,4 @@ AudioJACKTarget.cpp \ AudioPortAudioTarget.cpp \ AudioTargetFactory.cpp \ - PhaseVocoderTimeStretcher.cpp \ PlaySpeedRangeMapper.cpp