changeset 25:e74f508db18c

* Add setRatio method to the time stretcher, and make it possible to change the ratio without having to construct and replace the time stretcher. This means we can do it seamlessly. Add a lot more ratios to the time stretch control in the main window
author Chris Cannam
date Fri, 15 Sep 2006 15:35:06 +0000
parents ae0731ba8e67
children d88d117e0c34
files audioio/AudioCallbackPlaySource.cpp audioio/PhaseVocoderTimeStretcher.cpp audioio/PhaseVocoderTimeStretcher.h main/MainWindow.cpp
diffstat 4 files changed, 194 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/AudioCallbackPlaySource.cpp	Fri Sep 15 13:50:22 2006 +0000
+++ b/audioio/AudioCallbackPlaySource.cpp	Fri Sep 15 15:35:06 2006 +0000
@@ -602,6 +602,13 @@
     }
 
     if (factor != 1) {
+
+        if (existingStretcher &&
+            existingStretcher->getSharpening() == sharpen) {
+            existingStretcher->setRatio(factor);
+            return;
+        }
+
 	PhaseVocoderTimeStretcher *newStretcher = new PhaseVocoderTimeStretcher
 	    (getTargetSampleRate(),
              getTargetChannelCount(),
--- a/audioio/PhaseVocoderTimeStretcher.cpp	Fri Sep 15 13:50:22 2006 +0000
+++ b/audioio/PhaseVocoderTimeStretcher.cpp	Fri Sep 15 15:35:06 2006 +0000
@@ -18,6 +18,8 @@
 #include <iostream>
 #include <cassert>
 
+#include <QMutexLocker>
+
 //#define DEBUG_PHASE_VOCODER_TIME_STRETCHER 1
 
 PhaseVocoderTimeStretcher::PhaseVocoderTimeStretcher(size_t sampleRate,
@@ -27,51 +29,38 @@
                                                      size_t maxProcessInputBlockSize) :
     m_sampleRate(sampleRate),
     m_channels(channels),
+    m_maxProcessInputBlockSize(maxProcessInputBlockSize),
     m_ratio(ratio),
     m_sharpen(sharpen),
     m_totalCount(0),
     m_transientCount(0),
-    m_n2sum(0)
+    m_n2sum(0),
+    m_mutex(new QMutex())
 {
-    m_wlen = 1024;
+    initialise();
 
-    //!!! 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)
+    std::cerr << "PhaseVocoderTimeStretcher: channels = " << m_channels
+              << ", ratio = " << m_ratio
+              << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = "
+              << m_wlen << ", max = " << maxProcessInputBlockSize
+              << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl;
+}
 
-    //!!! have to work out the relationship between wlen and transient
-    //threshold
+PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher()
+{
+    std::cerr << "PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher" << std::endl;
 
-    if (ratio < 1) {
-        if (ratio < 0.4) {
-            m_n1 = 1024;
-            m_wlen = 2048;
-        } else if (ratio < 0.8) {
-            m_n1 = 512;
-        } else {
-            m_n1 = 256;
-        }
-        if (m_sharpen) {
-            m_wlen = 2048;
-        }
-        m_n2 = m_n1 * ratio;
-    } else {
-        if (ratio > 2) {
-            m_n2 = 512;
-            m_wlen = 4096; 
-        } else if (ratio > 1.6) {
-            m_n2 = 384;
-            m_wlen = 2048;
-        } else {
-            m_n2 = 256;
-        }
-        if (m_sharpen) {
-            if (m_wlen < 2048) m_wlen = 2048;
-        }
-        m_n1 = m_n2 / ratio;
-    }
+    cleanup();
+    
+    delete m_mutex;
+}
 
-    m_transientThreshold = m_wlen / 4.5;
+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);
@@ -110,7 +99,7 @@
 
         m_inbuf[c] = new RingBuffer<float>(m_wlen);
         m_outbuf[c] = new RingBuffer<float>
-            (lrintf((maxProcessInputBlockSize + m_wlen) * ratio));
+            (lrintf((m_maxProcessInputBlockSize + m_wlen) * m_ratio));
             
         m_mashbuf[c] = (float *)fftwf_malloc(sizeof(float) * m_wlen);
         
@@ -131,17 +120,58 @@
     for (int i = 0; i <= m_wlen/2; ++i) {
         m_prevTransientMag[i] = 0.0;
     }
-
-    std::cerr << "PhaseVocoderTimeStretcher: channels = " << channels
-              << ", ratio = " << ratio
-              << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = "
-              << m_wlen << ", max = " << maxProcessInputBlockSize
-              << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl;
 }
 
-PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher()
+void
+PhaseVocoderTimeStretcher::calculateParameters()
 {
-    std::cerr << "PhaseVocoderTimeStretcher::~PhaseVocoderTimeStretcher" << std::endl;
+    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 (m_sharpen) {
+            m_wlen = 2048;
+        }
+        m_n2 = 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 (m_sharpen) {
+            if (m_wlen < 2048) m_wlen = 2048;
+        }
+        m_n1 = m_n2 / m_ratio;
+    }
+
+    m_transientThreshold = m_wlen / 4.5;
+}
+
+void
+PhaseVocoderTimeStretcher::cleanup()
+{
+    std::cerr << "PhaseVocoderTimeStretcher::cleanup" << std::endl;
 
     for (size_t c = 0; c < m_channels; ++c) {
 
@@ -177,6 +207,60 @@
     delete m_synthesisWindow;
 }	
 
+void
+PhaseVocoderTimeStretcher::setRatio(float ratio)
+{
+    QMutexLocker locker(m_mutex);
+
+    float formerRatio = m_ratio;
+    size_t formerWlen = m_wlen;
+
+    m_ratio = ratio;
+
+    calculateParameters();
+
+    if (m_wlen == formerWlen) {
+
+        // This is the only container whose size depends on m_ratio
+
+        RingBuffer<float> **newout = new RingBuffer<float> *[m_channels];
+
+        size_t formerSize = m_outbuf[0]->getSize();
+        size_t newSize = lrintf((m_maxProcessInputBlockSize + m_wlen) * m_ratio);
+        size_t ready = m_outbuf[0]->getReadSpace();
+
+        for (size_t c = 0; c < m_channels; ++c) {
+            newout[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_outbuf[c]->read(tmp, ready);
+                newout[c]->write(tmp + ready - copy, copy);
+            }
+
+            delete[] tmp;
+        }
+
+        for (size_t c = 0; c < m_channels; ++c) {
+            delete m_outbuf[c];
+        }
+
+        delete[] m_outbuf;
+        m_outbuf = newout;
+
+    } else {
+        
+        std::cerr << "wlen changed" << std::endl;
+        cleanup();
+        initialise();
+    }
+}
+
 size_t
 PhaseVocoderTimeStretcher::getProcessingLatency() const
 {
@@ -193,6 +277,8 @@
 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();
 }
@@ -200,6 +286,8 @@
 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
@@ -343,12 +431,16 @@
 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();
--- a/audioio/PhaseVocoderTimeStretcher.h	Fri Sep 15 13:50:22 2006 +0000
+++ b/audioio/PhaseVocoderTimeStretcher.h	Fri Sep 15 15:35:06 2006 +0000
@@ -21,6 +21,8 @@
 
 #include <fftw3.h>
 
+#include <QMutex>
+
 /**
  * A time stretcher that alters the performance speed of audio,
  * preserving pitch.
@@ -87,6 +89,11 @@
     //!!! and reset?
 
     /**
+     * Change the time stretch ratio.
+     */
+    void setRatio(float ratio);
+
+    /**
      * Get the hop size for input.
      */
     size_t getInputIncrement() const { return m_n1; }
@@ -141,8 +148,13 @@
     void synthesiseBlock(size_t channel, float *out, float *modulation,
                          size_t lastStep);
 
+    void initialise();
+    void calculateParameters();
+    void cleanup();
+
     size_t m_sampleRate;
     size_t m_channels;
+    size_t m_maxProcessInputBlockSize;
     float m_ratio;
     bool m_sharpen;
     size_t m_n1;
@@ -173,6 +185,8 @@
     RingBuffer<float> **m_outbuf;
     float **m_mashbuf;
     float *m_modulationbuf;
+
+    QMutex *m_mutex;
 };
 
 #endif
--- a/main/MainWindow.cpp	Fri Sep 15 13:50:22 2006 +0000
+++ b/main/MainWindow.cpp	Fri Sep 15 15:35:06 2006 +0000
@@ -155,14 +155,14 @@
 
     m_playSpeed = new AudioDial(frame);
     m_playSpeed->setMinimum(0);
-    m_playSpeed->setMaximum(20);
-    m_playSpeed->setValue(10);
+    m_playSpeed->setMaximum(199);
+    m_playSpeed->setValue(100);
     m_playSpeed->setFixedWidth(24);
     m_playSpeed->setFixedHeight(24);
     m_playSpeed->setNotchesVisible(true);
-    m_playSpeed->setPageStep(1);
-    m_playSpeed->setToolTip(tr("Playback speed: Full"));
-    m_playSpeed->setDefaultValue(10);
+    m_playSpeed->setPageStep(10);
+    m_playSpeed->setToolTip(tr("Playback speed: +0%"));
+    m_playSpeed->setDefaultValue(100);
     connect(m_playSpeed, SIGNAL(valueChanged(int)),
 	    this, SLOT(playSpeedChanged(int)));
 
@@ -2870,25 +2870,44 @@
 void
 MainWindow::playSpeedChanged(int speed)
 {
-    static float factors[] = {
-        1.0, 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 3.0, 4.0, 6.0, 10.0
-    };
-    float factor = factors[speed >= 10 ? speed - 10 : 10 - speed];
+//    static float factors[] = {
+//        1.0, 1.1, 1.2, 1.3, 1.5, 1.7, 2.0, 3.0, 4.0, 6.0, 10.0
+//    };
+//    float factor = factors[speed >= 10 ? speed - 10 : 10 - speed];
+
+    bool slow = false;
+    bool something = false;
+    float factor;
+
+    if (speed < 100) {
+        slow = true;
+        speed = 100 - speed;
+    } else {
+        speed = speed - 100;
+    }
+
+    // speed is 0 -> 100
+
+    if (speed == 0) {
+        factor = 1.0;
+    } else {
+        factor = speed;
+        factor = 1.0 + (factor * factor) / 1000.f;
+        something = true;
+    }
 
     int pc = lrintf((factor - 1.0) * 100);
 
-    if (speed > 10) {
-        factor = 1.0 / factor;
-    }
-
-    std::cerr << "factor = " << factor << std::endl;
+    if (!slow) factor = 1.0 / factor;
+
+    std::cerr << "speed = " << speed << " factor = " << factor << std::endl;
 
     m_playSpeed->setToolTip(tr("Playback speed: %1%2%")
-                            .arg(speed >= 10 ? "+" : "-")
+                            .arg(!slow ? "+" : "-")
 			    .arg(pc));
 
-    m_playSharpen->setEnabled(speed != 10);
-    bool sharpen = (speed != 10 && m_playSharpen->isChecked());
+    m_playSharpen->setEnabled(something);
+    bool sharpen = (something && m_playSharpen->isChecked());
     m_playSource->setSlowdownFactor(factor, sharpen);
 }