changeset 91:9fc4b256c283

* 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)
author Chris Cannam
date Fri, 08 Feb 2008 17:51:15 +0000
parents db267a315058
children 792bca285459
files audioio/AudioCallbackPlaySource.cpp audioio/AudioCallbackPlaySource.h audioio/AudioCallbackPlayTarget.h audioio/AudioJACKTarget.cpp audioio/AudioJACKTarget.h audioio/AudioPortAudioTarget.cpp audioio/AudioPortAudioTarget.h audioio/PhaseVocoderTimeStretcher.cpp audioio/PhaseVocoderTimeStretcher.h audioio/audioio.pro
diffstat 10 files changed, 309 insertions(+), 1091 deletions(-) [+]
line wrap: on
line diff
--- 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 <rubberband/RubberBandStretcher.h>
 using namespace RubberBand;
-#else
-#include "PhaseVocoderTimeStretcher.h"
-#endif
 
 #include <iostream>
 #include <cassert>
@@ -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<float> *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<RealTime> rangeStarts;
+    std::vector<RealTime> 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 = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << std::endl;
 #endif
 
-    size_t available;
+        for (size_t c = 0; c < channels; ++c) {
+            if (c >= m_stretcherInputCount) continue;
+            if (reqd > m_stretcherInputSizes[c]) {
+                if (c == 0) {
+                    std::cerr << "WARNING: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << std::endl;
+                }
+                delete[] m_stretcherInputs[c];
+                m_stretcherInputSizes[c] = reqd * 2;
+                m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]];
+            }
+        }
 
-    int warned = 0;
-
-    // We want output blocks of e.g. 1024 (probably fixed, certainly
-    // bounded).  We can provide input blocks of any size (unbounded)
-    // at the timestretcher's request.  The input block for a given
-    // output is approx output / ratio, but we can't predict it
-    // exactly, for an adaptive timestretcher.  The stretcher will
-    // need some additional buffer space.  See the time stretcher code
-    // and comments.
-
-#ifdef HAVE_RUBBERBAND
-    m_timeStretchRatioMutex.lock();
-    while ((available = ts->available()) < count) {
-#else
-    while ((available = ts->getAvailableOutputSamples()) < count) {
+        for (size_t c = 0; c < channels; ++c) {
+            if (c >= m_stretcherInputCount) continue;
+            RingBuffer<float> *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<float> *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<float> *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()) {
 	    
--- 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 <set>
 #include <map>
 
-#ifdef HAVE_RUBBERBAND
-#include <rubberband/RubberBandStretcher.h>
-#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<PhaseVocoderTimeStretcher> 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
--- 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;
     }
--- 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();
--- 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();
 
--- 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,
--- 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();
 
--- 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 <iostream>
-#include <cassert>
-
-#include <QMutexLocker>
-
-//#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<float>(HanningWindow, m_wlen);
-    m_synthesisWindow = new Window<float>(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<float> *[m_channels];
-    m_outbuf = new RingBuffer<float> *[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<float>
-            ((m_maxOutputBlockSize + m_wlen) * 2);
-        m_inbuf[c] = new RingBuffer<float>
-            (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<float> **newin = new RingBuffer<float> *[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<float>(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: " <<last10num << "/" << (10 * m_n1) << " = " << float(last10num) / float(10 * m_n1) << " (should be " << m_ratio << ")" << std::endl;
-            
-            if (m_totalCount > 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
--- 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 <QMutex>
-
-/**
- * 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<float> *m_analysisWindow;
-    Window<float> *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<float> **m_inbuf;
-    RingBuffer<float> **m_outbuf;
-    float **m_mashbuf;
-    float *m_modulationbuf;
-
-    QMutex *m_mutex;
-};
-
-#endif
-
-#endif
--- 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