From 597c96a20046bf2dbe43629a85b921a075bcd5fc Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 6 Nov 2007 21:41:16 +0000 Subject: [PATCH] * Initial import --- Makefile.in | 99 +++ configure.ac | 35 + rubberband/RubberBandStretcher.h | 106 +++ rubberband/TimeStretcher.h | 51 ++ src/AudioCurve.cpp | 31 + src/AudioCurve.h | 42 ++ src/ConstantAudioCurve.cpp | 47 ++ src/ConstantAudioCurve.h | 37 + src/FFT.cpp | 606 ++++++++++++++++ src/FFT.h | 70 ++ src/HighFrequencyAudioCurve.cpp | 62 ++ src/HighFrequencyAudioCurve.h | 42 ++ src/PercussiveAudioCurve.cpp | 80 +++ src/PercussiveAudioCurve.h | 41 ++ src/Resampler.cpp | 169 +++++ src/Resampler.h | 48 ++ src/RingBuffer.h | 628 +++++++++++++++++ src/RubberBandStretcher.cpp | 178 +++++ src/Scavenger.h | 198 ++++++ src/StretchCalculator.cpp | 750 ++++++++++++++++++++ src/StretchCalculator.h | 89 +++ src/StretcherChannelData.cpp | 264 +++++++ src/StretcherChannelData.h | 121 ++++ src/StretcherImpl.cpp | 978 ++++++++++++++++++++++++++ src/StretcherImpl.h | 184 +++++ src/StretcherProcess.cpp | 853 ++++++++++++++++++++++ src/Thread.cpp | 190 +++++ src/Thread.h | 89 +++ src/Window.h | 161 +++++ src/ladspa/RubberBandPitchShifter.cpp | 358 ++++++++++ src/ladspa/RubberBandPitchShifter.h | 91 +++ src/ladspa/libmain.cpp | 28 + src/main.cpp | 408 +++++++++++ src/sysutils.cpp | 53 ++ src/sysutils.h | 24 + src/vamp/RubberBandVampPlugin.cpp | 564 +++++++++++++++ src/vamp/RubberBandVampPlugin.h | 56 ++ src/vamp/libmain.cpp | 32 + 38 files changed, 7863 insertions(+) create mode 100644 Makefile.in create mode 100644 configure.ac create mode 100644 rubberband/RubberBandStretcher.h create mode 100644 rubberband/TimeStretcher.h create mode 100644 src/AudioCurve.cpp create mode 100644 src/AudioCurve.h create mode 100644 src/ConstantAudioCurve.cpp create mode 100644 src/ConstantAudioCurve.h create mode 100644 src/FFT.cpp create mode 100644 src/FFT.h create mode 100644 src/HighFrequencyAudioCurve.cpp create mode 100644 src/HighFrequencyAudioCurve.h create mode 100644 src/PercussiveAudioCurve.cpp create mode 100644 src/PercussiveAudioCurve.h create mode 100644 src/Resampler.cpp create mode 100644 src/Resampler.h create mode 100644 src/RingBuffer.h create mode 100644 src/RubberBandStretcher.cpp create mode 100644 src/Scavenger.h create mode 100644 src/StretchCalculator.cpp create mode 100644 src/StretchCalculator.h create mode 100644 src/StretcherChannelData.cpp create mode 100644 src/StretcherChannelData.h create mode 100644 src/StretcherImpl.cpp create mode 100644 src/StretcherImpl.h create mode 100644 src/StretcherProcess.cpp create mode 100644 src/Thread.cpp create mode 100644 src/Thread.h create mode 100644 src/Window.h create mode 100644 src/ladspa/RubberBandPitchShifter.cpp create mode 100644 src/ladspa/RubberBandPitchShifter.h create mode 100644 src/ladspa/libmain.cpp create mode 100644 src/main.cpp create mode 100644 src/sysutils.cpp create mode 100644 src/sysutils.h create mode 100644 src/vamp/RubberBandVampPlugin.cpp create mode 100644 src/vamp/RubberBandVampPlugin.h create mode 100644 src/vamp/libmain.cpp diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..bc6ed01 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,99 @@ + +all: bin lib bin/rubberband lib/librubberband.a lib/vamp-rubberband.so lib/ladspa-rubberband.so + +CXX = @CXX@ +CXXFLAGS = @CXXFLAGS@ @SRC_CFLAGS@ @SNDFILE_CFLAGS@ @FFTW_CFLAGS@ @FFTWF_CFLAGS@ -Irubberband -Isrc +LDFLAGS = @LDFLAGS@ @SRC_LIBS@ @SNDFILE_LIBS@ @FFTW_LIBS@ @FFTWF_LIBS@ + +MKDIR = mkdir +AR = ar + +PROGRAM_TARGET := bin/rubberband +STATIC_TARGET := lib/librubberband.a +DYNAMIC_TARGET := lib/librubberband.so +VAMP_TARGET := lib/vamp-rubberband.so +LADSPA_TARGET := lib/ladspa-rubberband.so + +PUBLIC_INCLUDES := \ + rubberband/TimeStretcher.h \ + rubberband/RubberBandStretcher.h + +LIBRARY_INCLUDES := \ + src/AudioCurve.h \ + src/ConstantAudioCurve.h \ + src/FFT.h \ + src/HighFrequencyAudioCurve.h \ + src/PercussiveAudioCurve.h \ + src/Resampler.h \ + src/RingBuffer.h \ + src/Scavenger.h \ + src/StretchCalculator.h \ + src/StretcherImpl.h \ + src/StretcherChannelData.h \ + src/Thread.h \ + src/Window.h \ + src/sysutils.h + +LIBRARY_SOURCES := \ + src/RubberBandStretcher.cpp \ + src/ConstantAudioCurve.cpp \ + src/HighFrequencyAudioCurve.cpp \ + src/PercussiveAudioCurve.cpp \ + src/AudioCurve.cpp \ + src/Resampler.cpp \ + src/StretchCalculator.cpp \ + src/StretcherImpl.cpp \ + src/StretcherProcess.cpp \ + src/StretcherChannelData.cpp \ + src/FFT.cpp \ + src/Thread.cpp \ + src/sysutils.cpp + +PROGRAM_SOURCES := \ + src/main.cpp + +VAMP_HEADERS := \ + src/vamp/RubberBandVampPlugin.h + +VAMP_SOURCES := \ + src/vamp/RubberBandVampPlugin.cpp \ + src/vamp/libmain.cpp + +LADSPA_HEADERS := \ + src/ladspa/RubberBandPitchShifter.h + +LADSPA_SOURCES := \ + src/ladspa/RubberBandPitchShifter.cpp \ + src/ladspa/libmain.cpp + +LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) +PROGRAM_OBJECTS := $(PROGRAM_SOURCES:.cpp=.o) +VAMP_OBJECTS := $(VAMP_SOURCES:.cpp=.o) +LADSPA_OBJECTS := $(LADSPA_SOURCES:.cpp=.o) + +$(PROGRAM_TARGET): $(LIBRARY_OBJECTS) $(PROGRAM_OBJECTS) + $(CXX) -o $@ $^ $(LDFLAGS) + +$(STATIC_TARGET): $(LIBRARY_OBJECTS) + $(AR) rsc $@ $^ + +$(DYNAMIC_TARGET): $(LIBRARY_OBJECTS) + $(CXX) -shared -Wl,-Bsymbolic $^ -o $@ $(LDFLAGS) + +$(VAMP_TARGET): $(LIBRARY_OBJECTS) $(VAMP_OBJECTS) + $(CXX) -shared -Wl,-Bsymbolic -o $@ $^ $(VAMP_PLUGIN_LIBS) $(LDFLAGS) + +$(LADSPA_TARGET): $(LIBRARY_OBJECTS) $(LADSPA_OBJECTS) + $(CXX) -shared -Wl,-Bsymbolic -o $@ $^ $(LDFLAGS) + +bin: + $(MKDIR) $@ +lib: + $(MKDIR) $@ + +clean: + rm -f $(LIBRARY_OBJECTS) $(PROGRAM_OBJECTS) $(LADSPA_OBJECTS) $(VAMP_OBJECTS) + +distclean: clean + rm -f $(PROGRAM_TARGET) $(STATIC_TARGET) $(DYNAMIC_TARGET) $(VAMP_TARGET) $(LADSPA_TARGET) + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..635e12d --- /dev/null +++ b/configure.ac @@ -0,0 +1,35 @@ + +AC_INIT(RubberBand, 0.1, cannam@all-day-breakfast.com) + +AC_CONFIG_SRCDIR(src/StretcherImpl.h) +AC_PROG_CXX +AC_HEADER_STDC +AC_C_BIGENDIAN + +PKG_CHECK_MODULES([SRC],[samplerate]) +AC_SUBST(SRC_CFLAGS) +AC_SUBST(SRC_LIBS) + +PKG_CHECK_MODULES([SNDFILE],[sndfile]) +AC_SUBST(SNDFILE_CFLAGS) +AC_SUBST(SNDFILE_LIBS) + +PKG_CHECK_MODULES([FFTWF],[fftw3f]) +AC_SUBST(FFTWF_CFLAGS) +AC_SUBST(FFTWF_LIBS) + +PKG_CHECK_MODULES([FFTW],[fftw3]) +AC_SUBST(FFTW_CFLAGS) +AC_SUBST(FFTW_LIBS) + +changequote(,)dnl +if test "x$GCC" = "xyes"; then + case " $CXXFLAGS " in + *[\ \ ]-Wall[\ \ ]*) ;; + *) CXXFLAGS="$CXXFLAGS -Wall" ;; + esac +fi +changequote([,])dnl + +AC_OUTPUT([Makefile]) + diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h new file mode 100644 index 0000000..4221bdc --- /dev/null +++ b/rubberband/RubberBandStretcher.h @@ -0,0 +1,106 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBANDSTRETCHER_H_ +#define _RUBBERBANDSTRETCHER_H_ + +#include "TimeStretcher.h" + +#include + +namespace RubberBand +{ + +class RubberBandStretcher : public TimeStretcher +{ +public: + + typedef int Options; + + static const int OptionProcessOffline = 0x00000000; + static const int OptionProcessRealTime = 0x00000001; + + static const int OptionStretchElastic = 0x00000000; + static const int OptionStretchPrecise = 0x00000010; + + static const int OptionTransientsCrisp = 0x00000000; + static const int OptionTransientsSmooth = 0x00000100; + + static const int OptionPhasePeakLocked = 0x00000000; + static const int OptionPhaseIndependent = 0x00001000; + + static const int OptionThreadingAuto = 0x00000000; + static const int OptionThreadingNone = 0x00010000; + + static const int OptionWindowStandard = 0x00000000; + static const int OptionWindowShort = 0x00100000; + static const int OptionWindowLong = 0x00200000; + + static const int DefaultOptions = 0x00000000; + static const int PercussiveOptions = OptionWindowShort | \ + OptionPhaseIndependent; + + RubberBandStretcher(size_t sampleRate, + size_t channels, + Options options = DefaultOptions, + double initialTimeRatio = 1.0, + double initialPitchScale = 1.0); + virtual ~RubberBandStretcher(); + + virtual void reset(); + virtual void setTimeRatio(double ratio); + virtual void setPitchScale(double scale); //!!!??? pitch ratio? + + virtual double getTimeRatio() const; + virtual double getPitchScale() const; + + virtual size_t getLatency() const; + + virtual void setTransientsOption(Options options); + virtual void setPhaseOption(Options options); + + virtual void setExpectedInputDuration(size_t samples); + virtual void setMaxProcessBlockSize(size_t samples); + virtual size_t getSamplesRequired() const; + + // if samples == 0, input may be null + virtual void study(const float *const *input, size_t samples, bool final); + virtual void process(const float *const *input, size_t samples, bool final); + + virtual int available() const; // returns -1 if all data processed and nothing further will be available + virtual size_t retrieve(float *const *output, size_t samples) const; + + virtual float getFrequencyCutoff(int n) const; + virtual void setFrequencyCutoff(int n, float f); + + //!!! ideally, this stuff wouldn't be here... + + virtual size_t getInputIncrement() const; + virtual std::vector getOutputIncrements() const; //!!! document particular meaning in RT mode + virtual std::vector getLockCurve() const; //!!! document particular meaning in RT mode + + virtual size_t getChannelCount() const; + + virtual void calculateStretch(); + + virtual void setDebugLevel(int level); + +protected: + class Impl; + Impl *m_d; +}; + +} + +#endif diff --git a/rubberband/TimeStretcher.h b/rubberband/TimeStretcher.h new file mode 100644 index 0000000..9e810df --- /dev/null +++ b/rubberband/TimeStretcher.h @@ -0,0 +1,51 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_TIMESTRETCHER_H_ +#define _RUBBERBAND_TIMESTRETCHER_H_ + +#include + +namespace RubberBand +{ + +class TimeStretcher +{ +public: + TimeStretcher(size_t sampleRate, size_t channels) : + m_sampleRate(sampleRate), + m_channels(channels) + { } + virtual ~TimeStretcher() + { } + + virtual void reset() = 0; + virtual void setTimeRatio(double ratio) = 0; + virtual size_t getLatency() const = 0; + + virtual void study(const float *const *input, size_t samples, bool final) = 0; + virtual size_t getSamplesRequired() const = 0; // to cause processing to happen + virtual void process(const float *const *input, size_t samples, bool final) = 0; + virtual int available() const = 0; + virtual size_t retrieve(float *const *output, size_t samples) const = 0; + +protected: + size_t m_sampleRate; + size_t m_channels; +}; + +} + +#endif + diff --git a/src/AudioCurve.cpp b/src/AudioCurve.cpp new file mode 100644 index 0000000..a88ad93 --- /dev/null +++ b/src/AudioCurve.cpp @@ -0,0 +1,31 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "AudioCurve.h" + +namespace RubberBand +{ + +AudioCurve::AudioCurve(size_t sampleRate, size_t blockSize) : + m_sampleRate(sampleRate), + m_blockSize(blockSize) +{ +} + +AudioCurve::~AudioCurve() +{ +} + + +} diff --git a/src/AudioCurve.h b/src/AudioCurve.h new file mode 100644 index 0000000..7d64348 --- /dev/null +++ b/src/AudioCurve.h @@ -0,0 +1,42 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _AUDIO_CURVE_H_ +#define _AUDIO_CURVE_H_ + +#include + +namespace RubberBand +{ + +class AudioCurve +{ +public: + AudioCurve(size_t sampleRate, size_t blockSize); + virtual ~AudioCurve(); + + virtual void setBlockSize(size_t newSize) = 0; + + virtual float process(float *mag, size_t increment) = 0; + virtual void reset() = 0; + +protected: + size_t m_sampleRate; + size_t m_blockSize; +}; + +} + +#endif + diff --git a/src/ConstantAudioCurve.cpp b/src/ConstantAudioCurve.cpp new file mode 100644 index 0000000..c0617d6 --- /dev/null +++ b/src/ConstantAudioCurve.cpp @@ -0,0 +1,47 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "ConstantAudioCurve.h" + +namespace RubberBand +{ + +ConstantAudioCurve::ConstantAudioCurve(size_t sampleRate, size_t blockSize) : + AudioCurve(sampleRate, blockSize) +{ +} + +ConstantAudioCurve::~ConstantAudioCurve() +{ +} + +void +ConstantAudioCurve::reset() +{ +} + +void +ConstantAudioCurve::setBlockSize(size_t newSize) +{ + m_blockSize = newSize; +} + +float +ConstantAudioCurve::process(float *, size_t) +{ + return 1.f; +} + +} + diff --git a/src/ConstantAudioCurve.h b/src/ConstantAudioCurve.h new file mode 100644 index 0000000..04ecb76 --- /dev/null +++ b/src/ConstantAudioCurve.h @@ -0,0 +1,37 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _CONSTANT_AUDIO_CURVE_H_ +#define _CONSTANT_AUDIO_CURVE_H_ + +#include "AudioCurve.h" + +namespace RubberBand +{ + +class ConstantAudioCurve : public AudioCurve +{ +public: + ConstantAudioCurve(size_t sampleRate, size_t blockSize); + virtual ~ConstantAudioCurve(); + + virtual void setBlockSize(size_t newSize); + + virtual float process(float *mag, size_t increment); + virtual void reset(); +}; + +} + +#endif diff --git a/src/FFT.cpp b/src/FFT.cpp new file mode 100644 index 0000000..c5a3df7 --- /dev/null +++ b/src/FFT.cpp @@ -0,0 +1,606 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "FFT.h" + + +#include + +#include +#include +#include +#include + +class FFTImpl +{ +public: + virtual ~FFTImpl() { } + + virtual void initFloat() = 0; + virtual void initDouble() = 0; + + virtual void forward(double *realIn, double *realOut, double *imagOut) = 0; + virtual void forwardPolar(double *realIn, double *magOut, double *phaseOut) = 0; + virtual void forwardMagnitude(double *realIn, double *magOut) = 0; + + virtual void forward(float *realIn, float *realOut, float *imagOut) = 0; + virtual void forwardPolar(float *realIn, float *magOut, float *phaseOut) = 0; + virtual void forwardMagnitude(float *realIn, float *magOut) = 0; + + virtual void inverse(double *realIn, double *imagIn, double *realOut) = 0; + virtual void inversePolar(double *magIn, double *phaseIn, double *realOut) = 0; + + virtual void inverse(float *realIn, float *imagIn, float *realOut) = 0; + virtual void inversePolar(float *magIn, float *phaseIn, float *realOut) = 0; +}; + + + + +class D_FFTW : public FFTImpl +{ +public: + D_FFTW(unsigned int size) : m_size(size), m_fplanf(0), m_dplanf(0) { + } + + ~D_FFTW() { + if (m_fplanf) { + fftwf_destroy_plan(m_fplanf); + fftwf_destroy_plan(m_fplani); + fftwf_free(m_fbuf); + fftwf_free(m_fpacked); + } + if (m_dplanf) { + fftw_destroy_plan(m_dplanf); + fftw_destroy_plan(m_dplani); + fftw_free(m_dbuf); + fftw_free(m_dpacked); + } + } + + //!!! check return values + + void initFloat() { + if (m_fplanf) return; + m_fbuf = (float *)fftw_malloc(m_size * sizeof(float)); + m_fpacked = (fftwf_complex *)fftw_malloc + ((m_size/2 + 1) * sizeof(fftwf_complex)); + m_fplanf = fftwf_plan_dft_r2c_1d + (m_size, m_fbuf, m_fpacked, FFTW_MEASURE); + m_fplani = fftwf_plan_dft_c2r_1d + (m_size, m_fpacked, m_fbuf, FFTW_MEASURE); + } + + void initDouble() { + if (m_dplanf) return; + m_dbuf = (double *)fftw_malloc(m_size * sizeof(double)); + m_dpacked = (fftw_complex *)fftw_malloc + ((m_size/2 + 1) * sizeof(fftw_complex)); + m_dplanf = fftw_plan_dft_r2c_1d + (m_size, m_dbuf, m_dpacked, FFTW_MEASURE); + m_dplani = fftw_plan_dft_c2r_1d + (m_size, m_dpacked, m_dbuf, FFTW_MEASURE); + } + + void packFloat(float *re, float *im) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + m_fpacked[i][0] = re[i]; + m_fpacked[i][1] = im[i]; + } + } + + void packDouble(double *re, double *im) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + m_dpacked[i][0] = re[i]; + m_dpacked[i][1] = im[i]; + } + } + + void unpackFloat(float *re, float *im) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + re[i] = m_fpacked[i][0]; + im[i] = m_fpacked[i][1]; + } + } + + void unpackDouble(double *re, double *im) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + re[i] = m_dpacked[i][0]; + im[i] = m_dpacked[i][1]; + } + } + + void forward(double *realIn, double *realOut, double *imagOut) { + if (!m_dplanf) initDouble(); + for (unsigned int i = 0; i < m_size; ++i) { + m_dbuf[i] = realIn[i]; + } + fftw_execute(m_dplanf); + unpackDouble(realOut, imagOut); + } + + void forwardPolar(double *realIn, double *magOut, double *phaseOut) { + if (!m_dplanf) initDouble(); + for (unsigned int i = 0; i < m_size; ++i) { + m_dbuf[i] = realIn[i]; + } + fftw_execute(m_dplanf); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_dpacked[i][0] * m_dpacked[i][0] + + m_dpacked[i][1] * m_dpacked[i][1]); + phaseOut[i] = atan2(m_dpacked[i][1], m_dpacked[i][0]); + } + } + + void forwardMagnitude(double *realIn, double *magOut) { + if (!m_dplanf) initDouble(); + for (unsigned int i = 0; i < m_size; ++i) { + m_dbuf[i] = realIn[i]; + } + fftw_execute(m_dplanf); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_dpacked[i][0] * m_dpacked[i][0] + + m_dpacked[i][1] * m_dpacked[i][1]); + } + } + + void forward(float *realIn, float *realOut, float *imagOut) { + if (!m_fplanf) initFloat(); + for (unsigned int i = 0; i < m_size; ++i) { + m_fbuf[i] = realIn[i]; + } + fftwf_execute(m_fplanf); + unpackFloat(realOut, imagOut); + } + + void forwardPolar(float *realIn, float *magOut, float *phaseOut) { + if (!m_fplanf) initFloat(); + for (unsigned int i = 0; i < m_size; ++i) { + m_fbuf[i] = realIn[i]; + } + fftwf_execute(m_fplanf); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrtf(m_fpacked[i][0] * m_fpacked[i][0] + + m_fpacked[i][1] * m_fpacked[i][1]); + phaseOut[i] = atan2f(m_fpacked[i][1], m_fpacked[i][0]) ; + } + } + + void forwardMagnitude(float *realIn, float *magOut) { + if (!m_fplanf) initFloat(); + for (unsigned int i = 0; i < m_size; ++i) { + m_fbuf[i] = realIn[i]; + } + fftwf_execute(m_fplanf); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrtf(m_fpacked[i][0] * m_fpacked[i][0] + + m_fpacked[i][1] * m_fpacked[i][1]); + } + } + + void inverse(double *realIn, double *imagIn, double *realOut) { + if (!m_dplanf) initDouble(); + packDouble(realIn, imagIn); + fftw_execute(m_dplani); + for (unsigned int i = 0; i < m_size; ++i) { + realOut[i] = m_dbuf[i]; + } + } + + void inversePolar(double *magIn, double *phaseIn, double *realOut) { + if (!m_dplanf) initDouble(); + for (unsigned int i = 0; i <= m_size/2; ++i) { + m_dpacked[i][0] = magIn[i] * cos(phaseIn[i]); + m_dpacked[i][1] = magIn[i] * sin(phaseIn[i]); + } + fftw_execute(m_dplani); + for (unsigned int i = 0; i < m_size; ++i) { + realOut[i] = m_dbuf[i]; + } + } + + void inverse(float *realIn, float *imagIn, float *realOut) { + if (!m_fplanf) initFloat(); + packFloat(realIn, imagIn); + fftwf_execute(m_fplani); + for (unsigned int i = 0; i < m_size; ++i) { + realOut[i] = m_fbuf[i]; + } + } + + void inversePolar(float *magIn, float *phaseIn, float *realOut) { + if (!m_fplanf) initFloat(); + for (unsigned int i = 0; i <= m_size/2; ++i) { + m_fpacked[i][0] = magIn[i] * cosf(phaseIn[i]); + m_fpacked[i][1] = magIn[i] * sinf(phaseIn[i]); + } + fftwf_execute(m_fplani); + for (unsigned int i = 0; i < m_size; ++i) { + realOut[i] = m_fbuf[i]; + } + } + +private: + unsigned int m_size; + fftwf_plan m_fplanf; + fftwf_plan m_fplani; + fftw_plan m_dplanf; + fftw_plan m_dplani; + float *m_fbuf; + fftwf_complex *m_fpacked; + double *m_dbuf; + fftw_complex *m_dpacked; +}; + + +class D_Cross : public FFTImpl +{ +public: + D_Cross(unsigned int size) : m_size(size), m_table(0) { + + m_a = new double[size]; + m_b = new double[size]; + m_c = new double[size]; + m_d = new double[size]; + + m_table = new int[m_size]; + + unsigned int bits; + unsigned int i, j, k, m; + + for (i = 0; ; ++i) { + if (m_size & (1 << i)) { + bits = i; + break; + } + } + + for (i = 0; i < m_size; ++i) { + + m = i; + + for (j = k = 0; j < bits; ++j) { + k = (k << 1) | (m & 1); + m >>= 1; + } + + m_table[i] = k; + } + } + + ~D_Cross() { + delete[] m_table; + delete[] m_a; + delete[] m_b; + delete[] m_c; + delete[] m_d; + } + + void initFloat() { } + void initDouble() { } + + void forward(double *realIn, double *realOut, double *imagOut) { + basefft(false, realIn, 0, m_c, m_d); + for (size_t i = 0; i <= m_size/2; ++i) realOut[i] = m_c[i]; + for (size_t i = 0; i <= m_size/2; ++i) imagOut[i] = m_d[i]; + } + + void forwardPolar(double *realIn, double *magOut, double *phaseOut) { + basefft(false, realIn, 0, m_c, m_d); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_c[i] * m_c[i] + m_d[i] * m_d[i]); + phaseOut[i] = atan2(m_d[i], m_c[i]) ; + } + } + + void forwardMagnitude(double *realIn, double *magOut) { + basefft(false, realIn, 0, m_c, m_d); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_c[i] * m_c[i] + m_d[i] * m_d[i]); + } + } + + void forward(float *realIn, float *realOut, float *imagOut) { + for (size_t i = 0; i < m_size; ++i) m_a[i] = realIn[i]; + basefft(false, m_a, 0, m_c, m_d); + for (size_t i = 0; i <= m_size/2; ++i) realOut[i] = m_c[i]; + for (size_t i = 0; i <= m_size/2; ++i) imagOut[i] = m_d[i]; + } + + void forwardPolar(float *realIn, float *magOut, float *phaseOut) { + for (size_t i = 0; i < m_size; ++i) m_a[i] = realIn[i]; + basefft(false, m_a, 0, m_c, m_d); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_c[i] * m_c[i] + m_d[i] * m_d[i]); + phaseOut[i] = atan2(m_d[i], m_c[i]) ; + } + } + + void forwardMagnitude(float *realIn, float *magOut) { + for (size_t i = 0; i < m_size; ++i) m_a[i] = realIn[i]; + basefft(false, m_a, 0, m_c, m_d); + for (unsigned int i = 0; i <= m_size/2; ++i) { + magOut[i] = sqrt(m_c[i] * m_c[i] + m_d[i] * m_d[i]); + } + } + + void inverse(double *realIn, double *imagIn, double *realOut) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + double real = realIn[i]; + double imag = imagIn[i]; + m_a[i] = real; + m_b[i] = imag; + if (i > 0) { + m_a[m_size-i] = real; + m_b[m_size-i] = -imag; + } + } + basefft(true, m_a, m_b, realOut, m_d); + } + + void inversePolar(double *magIn, double *phaseIn, double *realOut) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + double real = magIn[i] * cos(phaseIn[i]); + double imag = magIn[i] * sin(phaseIn[i]); + m_a[i] = real; + m_b[i] = imag; + if (i > 0) { + m_a[m_size-i] = real; + m_b[m_size-i] = -imag; + } + } + basefft(true, m_a, m_b, realOut, m_d); + } + + void inverse(float *realIn, float *imagIn, float *realOut) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + float real = realIn[i]; + float imag = imagIn[i]; + m_a[i] = real; + m_b[i] = imag; + if (i > 0) { + m_a[m_size-i] = real; + m_b[m_size-i] = -imag; + } + } + basefft(true, m_a, m_b, m_c, m_d); + for (unsigned int i = 0; i < m_size; ++i) realOut[i] = m_c[i]; + } + + void inversePolar(float *magIn, float *phaseIn, float *realOut) { + for (unsigned int i = 0; i <= m_size/2; ++i) { + float real = magIn[i] * cosf(phaseIn[i]); + float imag = magIn[i] * sinf(phaseIn[i]); + m_a[i] = real; + m_b[i] = imag; + if (i > 0) { + m_a[m_size-i] = real; + m_b[m_size-i] = -imag; + } + } + basefft(true, m_a, m_b, m_c, m_d); + for (unsigned int i = 0; i < m_size; ++i) realOut[i] = m_c[i]; + } + +private: + unsigned int m_size; + int *m_table; + double *m_a; + double *m_b; + double *m_c; + double *m_d; + void basefft(bool inverse, double *ri, double *ii, double *ro, double *io); +}; + +void +D_Cross::basefft(bool inverse, double *ri, double *ii, double *ro, double *io) +{ + if (!ri || !ro || !io) return; + + unsigned int i, j, k, m; + unsigned int blockSize, blockEnd; + + double tr, ti; + + double angle = 2.0 * M_PI; + if (inverse) angle = -angle; + + const unsigned int n = m_size; + + if (ii) { + for (i = 0; i < n; ++i) { + ro[m_table[i]] = ri[i]; + io[m_table[i]] = ii[i]; + } + } else { + for (i = 0; i < n; ++i) { + ro[m_table[i]] = ri[i]; + io[m_table[i]] = 0.0; + } + } + + blockEnd = 1; + + for (blockSize = 2; blockSize <= n; blockSize <<= 1) { + + double delta = angle / (double)blockSize; + double sm2 = -sin(-2 * delta); + double sm1 = -sin(-delta); + double cm2 = cos(-2 * delta); + double cm1 = cos(-delta); + double w = 2 * cm1; + double ar[3], ai[3]; + + for (i = 0; i < n; i += blockSize) { + + ar[2] = cm2; + ar[1] = cm1; + + ai[2] = sm2; + ai[1] = sm1; + + for (j = i, m = 0; m < blockEnd; j++, m++) { + + ar[0] = w * ar[1] - ar[2]; + ar[2] = ar[1]; + ar[1] = ar[0]; + + ai[0] = w * ai[1] - ai[2]; + ai[2] = ai[1]; + ai[1] = ai[0]; + + k = j + blockEnd; + tr = ar[0] * ro[k] - ai[0] * io[k]; + ti = ar[0] * io[k] + ai[0] * ro[k]; + + ro[k] = ro[j] - tr; + io[k] = io[j] - ti; + + ro[j] += tr; + io[j] += ti; + } + } + + blockEnd = blockSize; + } + +/* fftw doesn't rescale, so nor will we + + if (inverse) { + + double denom = (double)n; + + for (i = 0; i < n; i++) { + ro[i] /= denom; + io[i] /= denom; + } + } +*/ +} + +int +FFT::m_method = -1; + +FFT::FFT(unsigned int size) +{ + if (size < 2) throw InvalidSize; + if (size & (size-1)) throw InvalidSize; + + if (m_method == -1) { + m_method = 1; + } + + switch (m_method) { + + case 0: + d = new D_Cross(size); + break; + + case 1: + std::cerr << "FFT::FFT(" << size << "): using FFTW3 implementation" + << std::endl; + d = new D_FFTW(size); + break; + + default: + std::cerr << "FFT::FFT(" << size << "): using built-in implementation" + << std::endl; + d = new D_Cross(size); + break; + } +} + +FFT::~FFT() +{ + delete d; +} + +void +FFT::forward(double *realIn, double *realOut, double *imagOut) +{ + d->forward(realIn, realOut, imagOut); +} + +void +FFT::forwardPolar(double *realIn, double *magOut, double *phaseOut) +{ + d->forwardPolar(realIn, magOut, phaseOut); +} + +void +FFT::forwardMagnitude(double *realIn, double *magOut) +{ + d->forwardMagnitude(realIn, magOut); +} + +void +FFT::forward(float *realIn, float *realOut, float *imagOut) +{ + d->forward(realIn, realOut, imagOut); +} + +void +FFT::forwardPolar(float *realIn, float *magOut, float *phaseOut) +{ + d->forwardPolar(realIn, magOut, phaseOut); +} + +void +FFT::forwardMagnitude(float *realIn, float *magOut) +{ + d->forwardMagnitude(realIn, magOut); +} + +void +FFT::inverse(double *realIn, double *imagIn, double *realOut) +{ + d->inverse(realIn, imagIn, realOut); +} + +void +FFT::inversePolar(double *magIn, double *phaseIn, double *realOut) +{ + d->inversePolar(magIn, phaseIn, realOut); +} + +void +FFT::inverse(float *realIn, float *imagIn, float *realOut) +{ + d->inverse(realIn, imagIn, realOut); +} + +void +FFT::inversePolar(float *magIn, float *phaseIn, float *realOut) +{ + d->inversePolar(magIn, phaseIn, realOut); +} + +void +FFT::initFloat() +{ + d->initFloat(); +} + +void +FFT::initDouble() +{ + d->initDouble(); +} + + +void +FFT::tune() +{ +} + diff --git a/src/FFT.h b/src/FFT.h new file mode 100644 index 0000000..d73d0df --- /dev/null +++ b/src/FFT.h @@ -0,0 +1,70 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_FFT_H_ +#define _RUBBERBAND_FFT_H_ + +class FFTImpl; + + +/** + * Provide the basic FFT computations we need, using one of a set of + * candidate FFT implementations (depending on compile flags). + * + * Implements real->complex FFTs of power-of-two sizes only. Note + * that only the first half of the output signal is returned (the + * complex conjugates half is omitted), so the "complex" arrays need + * room for size/2+1 elements. + * + * Not thread safe: use a separate instance per thread. + */ + +class FFT +{ +public: + enum Exception { InvalidSize }; + + FFT(unsigned int size); // may throw InvalidSize + ~FFT(); + + void forward(double *realIn, double *realOut, double *imagOut); + void forwardPolar(double *realIn, double *magOut, double *phaseOut); + void forwardMagnitude(double *realIn, double *magOut); + + void forward(float *realIn, float *realOut, float *imagOut); + void forwardPolar(float *realIn, float *magOut, float *phaseOut); + void forwardMagnitude(float *realIn, float *magOut); + + void inverse(double *realIn, double *imagIn, double *realOut); + void inversePolar(double *magIn, double *phaseIn, double *realOut); + + void inverse(float *realIn, float *imagIn, float *realOut); + void inversePolar(float *magIn, float *phaseIn, float *realOut); + + // Calling one or both of these is optional -- if neither is + // called, the first call to a forward or inverse method will call + // init(). You only need call these if you don't want to risk + // expensive allocations etc happening in forward or inverse. + void initFloat(); + void initDouble(); + + static void tune(); + +protected: + FFTImpl *d; + static int m_method; +}; + +#endif + diff --git a/src/HighFrequencyAudioCurve.cpp b/src/HighFrequencyAudioCurve.cpp new file mode 100644 index 0000000..630156d --- /dev/null +++ b/src/HighFrequencyAudioCurve.cpp @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "HighFrequencyAudioCurve.h" + +namespace RubberBand +{ + +HighFrequencyAudioCurve::HighFrequencyAudioCurve(size_t sampleRate, size_t blockSize) : + AudioCurve(sampleRate, blockSize) +{ + m_prevMag = new double[m_blockSize/2 + 1]; + + for (size_t i = 0; i <= m_blockSize/2; ++i) { + m_prevMag[i] = 0.f; + } +} + +HighFrequencyAudioCurve::~HighFrequencyAudioCurve() +{ + delete[] m_prevMag; +} + +void +HighFrequencyAudioCurve::reset() +{ + for (size_t i = 0; i <= m_blockSize/2; ++i) { + m_prevMag[i] = 0; + } +} + +void +HighFrequencyAudioCurve::setBlockSize(size_t newSize) +{ + m_blockSize = newSize; +} + +float +HighFrequencyAudioCurve::process(float *mag, size_t increment) +{ + float result = 0.0; + + for (size_t n = 0; n <= m_blockSize / 2; ++n) { + result += mag[n]; + } + + return result; +} + +} + diff --git a/src/HighFrequencyAudioCurve.h b/src/HighFrequencyAudioCurve.h new file mode 100644 index 0000000..a7bedbc --- /dev/null +++ b/src/HighFrequencyAudioCurve.h @@ -0,0 +1,42 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _HIGHFREQUENCY_AUDIO_CURVE_H_ +#define _HIGHFREQUENCY_AUDIO_CURVE_H_ + +#include "AudioCurve.h" +#include "Window.h" + +namespace RubberBand +{ + +class HighFrequencyAudioCurve : public AudioCurve +{ +public: + HighFrequencyAudioCurve(size_t sampleRate, size_t blockSize); + + virtual ~HighFrequencyAudioCurve(); + + virtual void setBlockSize(size_t newSize); + + virtual float process(float *mag, size_t increment); + virtual void reset(); + +protected: + double *m_prevMag; +}; + +} + +#endif diff --git a/src/PercussiveAudioCurve.cpp b/src/PercussiveAudioCurve.cpp new file mode 100644 index 0000000..6eefa8c --- /dev/null +++ b/src/PercussiveAudioCurve.cpp @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "PercussiveAudioCurve.h" + +#include + +namespace RubberBand +{ + +PercussiveAudioCurve::PercussiveAudioCurve(size_t sampleRate, size_t blockSize) : + AudioCurve(sampleRate, blockSize) +{ + m_prevMag = new double[m_blockSize/2 + 1]; + + for (size_t i = 0; i <= m_blockSize/2; ++i) { + m_prevMag[i] = 0.f; + } +} + +PercussiveAudioCurve::~PercussiveAudioCurve() +{ + delete[] m_prevMag; +} + +void +PercussiveAudioCurve::reset() +{ + for (size_t i = 0; i <= m_blockSize/2; ++i) { + m_prevMag[i] = 0; + } +} + +void +PercussiveAudioCurve::setBlockSize(size_t newSize) +{ + delete[] m_prevMag; + m_blockSize = newSize; + + m_prevMag = new double[m_blockSize/2 + 1]; + + reset(); +} + +float +PercussiveAudioCurve::process(float *mag, size_t increment) +{ + static float threshold = pow(10, 0.3); + static float zeroThresh = pow(10, -16); + + size_t count = 0; + size_t nonZeroCount = 0; + + for (size_t n = 1; n <= m_blockSize / 2; ++n) { + //!!! adjust threshold so that this multiplication is unnecessary + float sqrmag = mag[n] * mag[n]; + bool above = ((sqrmag / m_prevMag[n]) >= threshold); + if (above) ++count; + if (sqrmag > zeroThresh) ++nonZeroCount; + m_prevMag[n] = sqrmag; + } + +//!!! return float(count) / float(m_blockSize); + if (nonZeroCount == 0) return 0; + else return float(count) / float(nonZeroCount); +} + +} + diff --git a/src/PercussiveAudioCurve.h b/src/PercussiveAudioCurve.h new file mode 100644 index 0000000..0980f80 --- /dev/null +++ b/src/PercussiveAudioCurve.h @@ -0,0 +1,41 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _PERCUSSIVE_AUDIO_CURVE_H_ +#define _PERCUSSIVE_AUDIO_CURVE_H_ + +#include "AudioCurve.h" + +namespace RubberBand +{ + +class PercussiveAudioCurve : public AudioCurve +{ +public: + PercussiveAudioCurve(size_t sampleRate, size_t blockSize); + + virtual ~PercussiveAudioCurve(); + + virtual void setBlockSize(size_t newSize); + + virtual float process(float *mag, size_t increment); + virtual void reset(); + +protected: + double *m_prevMag; +}; + +} + +#endif diff --git a/src/Resampler.cpp b/src/Resampler.cpp new file mode 100644 index 0000000..304eb4b --- /dev/null +++ b/src/Resampler.cpp @@ -0,0 +1,169 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "Resampler.h" + +#include +#include + +#include + + +#include + +namespace RubberBand { + +class Resampler::D +{ +public: + D(Quality quality, size_t channels, size_t maxBufferSize); + ~D(); + + size_t resample(float **in, float **out, + size_t incount, float ratio, bool final); + + void reset(); + +protected: + SRC_STATE *m_src; + float *m_iin; + float *m_iout; + size_t m_channels; + size_t m_iinsize; + size_t m_ioutsize; +}; + +Resampler::D::D(Quality quality, size_t channels, size_t maxBufferSize) : + m_src(0), + m_iin(0), + m_iout(0), + m_channels(channels), + m_iinsize(0), + m_ioutsize(0) +{ + std::cerr << "Resampler::Resampler: using libsamplerate implementation" + << std::endl; + + int err = 0; + m_src = src_new(quality == Best ? SRC_SINC_BEST_QUALITY : + quality == Fastest ? SRC_LINEAR : + SRC_SINC_FASTEST, + channels, &err); + + //!!! check err, throw + + if (maxBufferSize > 0 && m_channels > 1) { + //!!! alignment? + m_iinsize = maxBufferSize * m_channels; + m_ioutsize = maxBufferSize * m_channels * 2; + m_iin = (float *)malloc(m_iinsize * sizeof(float)); + m_iout = (float *)malloc(m_ioutsize * sizeof(float)); + } +} + +Resampler::D::~D() +{ + src_delete(m_src); + if (m_iinsize > 0) { + free(m_iin); + } + if (m_ioutsize > 0) { + free(m_iout); + } +} + +size_t +Resampler::D::resample(float **in, float **out, size_t incount, float ratio, + bool final) +{ + SRC_DATA data; + + size_t outcount = lrintf(ceilf(incount * ratio)); + + if (m_channels == 1) { + data.data_in = *in; + data.data_out = *out; + } else { + if (incount * m_channels > m_iinsize) { + m_iinsize = incount * m_channels; + m_iin = (float *)realloc(m_iin, m_iinsize * sizeof(float)); + } + if (outcount * m_channels > m_ioutsize) { + m_ioutsize = outcount * m_channels; + m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float)); + } + for (size_t i = 0; i < incount; ++i) { + for (size_t c = 0; c < m_channels; ++c) { + m_iin[i * m_channels + c] = in[c][i]; + } + } + data.data_in = m_iin; + data.data_out = m_iout; + } + + data.input_frames = incount; + data.output_frames = outcount; + data.src_ratio = ratio; + data.end_of_input = (final ? 1 : 0); + + int err = src_process(m_src, &data); + + //!!! check err, respond appropriately + + if (m_channels > 1) { + for (size_t i = 0; i < data.output_frames_gen; ++i) { + for (size_t c = 0; c < m_channels; ++c) { + out[c][i] = m_iout[i * m_channels + c]; + } + } + } + + return data.output_frames_gen; +} + +void +Resampler::D::reset() +{ + src_reset(m_src); +} + +} // end namespace + + +namespace RubberBand { + +Resampler::Resampler(Quality quality, size_t channels, size_t maxBufferSize) +{ + m_d = new D(quality, channels, maxBufferSize); +} + +Resampler::~Resampler() +{ + delete m_d; +} + +size_t +Resampler::resample(float **in, float **out, + size_t incount, float ratio, bool final) +{ + return m_d->resample(in, out, incount, ratio, final); +} + +void +Resampler::reset() +{ + m_d->reset(); +} + +} diff --git a/src/Resampler.h b/src/Resampler.h new file mode 100644 index 0000000..bc07c58 --- /dev/null +++ b/src/Resampler.h @@ -0,0 +1,48 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_RESAMPLER_H_ +#define _RUBBERBAND_RESAMPLER_H_ + +#include + +namespace RubberBand { + +class Resampler +{ +public: + enum Quality { Best, FastestTolerable, Fastest }; + + /** + * Construct a resampler with the given quality level and channel + * count. maxBufferSize gives a bound on the maximum incount size + * that may be passed to the resample function before the + * resampler needs to reallocate its internal buffers. + */ + Resampler(Quality quality, size_t channels, size_t maxBufferSize = 0); + ~Resampler(); + + size_t resample(float **in, float **out, + size_t incount, float ratio, bool final = false); + + void reset(); + +protected: + class D; + D *m_d; +}; + +} + +#endif diff --git a/src/RingBuffer.h b/src/RingBuffer.h new file mode 100644 index 0000000..62a884a --- /dev/null +++ b/src/RingBuffer.h @@ -0,0 +1,628 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_RINGBUFFER_H_ +#define _RUBBERBAND_RINGBUFFER_H_ + +#include +#include + +#include "Scavenger.h" + +//#define DEBUG_RINGBUFFER 1 + +#define MLOCK(a,b) ::mlock(a,b) +#define MUNLOCK(a,b) ::munlock(a,b) + +#ifdef DEBUG_RINGBUFFER +#include +#endif + +namespace RubberBand { + +/** + * RingBuffer implements a lock-free ring buffer for one writer and N + * readers, that is to be used to store a sample type T. + */ + +template +class RingBuffer +{ +public: + /** + * Create a ring buffer with room to write n samples. + * + * Note that the internal storage size will actually be n+1 + * samples, as one element is unavailable for administrative + * reasons. Since the ring buffer performs best if its size is a + * power of two, this means n should ideally be some power of two + * minus one. + */ + RingBuffer(size_t n); + + virtual ~RingBuffer(); + + /** + * Return the total capacity of the ring buffer in samples. + * (This is the argument n passed to the constructor.) + */ + size_t getSize() const; + + /** + * Resize the ring buffer. This also empties it; use resized() + * below if you do not want this to happen. Actually swaps in a + * new, larger buffer; the old buffer is scavenged after a seemly + * delay. Should be called from the write thread. + */ + void resize(size_t newSize); + + /** + * Return a new ring buffer (allocated with "new" -- called must + * delete when no longer needed) of the given size, containing the + * same data as this one. If another thread reads from or writes + * to this buffer during the call, the results may be incomplete + * or inconsistent. If this buffer's data will not fit in the new + * size, the contents are undefined. + */ + RingBuffer *resized(size_t newSize, int R = 0) const; + + /** + * Lock the ring buffer into physical memory. Returns true + * for success. + */ + bool mlock(); + + /** + * Reset read and write pointers, thus emptying the buffer. + * Should be called from the write thread. + */ + void reset(); + + /** + * Return the amount of data available for reading by reader R, in + * samples. + */ + size_t getReadSpace(int R = 0) const; + + /** + * Return the amount of space available for writing, in samples. + */ + size_t getWriteSpace() const; + + /** + * Read n samples from the buffer, for reader R. If fewer than n + * are available, the remainder will be zeroed out. Returns the + * number of samples actually read. + */ + size_t read(T *destination, size_t n, int R = 0); + + /** + * Read n samples from the buffer, for reader R, adding them to + * the destination. If fewer than n are available, the remainder + * will be left alone. Returns the number of samples actually + * read. + */ + size_t readAdding(T *destination, size_t n, int R = 0); + + /** + * Read one sample from the buffer, for reader R. If no sample is + * available, this will silently return zero. Calling this + * repeatedly is obviously slower than calling read once, but it + * may be good enough if you don't want to allocate a buffer to + * read into. + */ + T readOne(int R = 0); + + /** + * Read n samples from the buffer, if available, for reader R, + * without advancing the read pointer -- i.e. a subsequent read() + * or skip() will be necessary to empty the buffer. If fewer than + * n are available, the remainder will be zeroed out. Returns the + * number of samples actually read. + */ + size_t peek(T *destination, size_t n, int R = 0) const; + + /** + * Read one sample from the buffer, if available, without + * advancing the read pointer -- i.e. a subsequent read() or + * skip() will be necessary to empty the buffer. Returns zero if + * no sample was available. + */ + T peekOne(int R = 0) const; + + /** + * Pretend to read n samples from the buffer, for reader R, + * without actually returning them (i.e. discard the next n + * samples). Returns the number of samples actually available for + * discarding. + */ + size_t skip(size_t n, int R = 0); + + /** + * Write n samples to the buffer. If insufficient space is + * available, not all samples may actually be written. Returns + * the number of samples actually written. + */ + size_t write(const T *source, size_t n); + + /** + * Write n zero-value samples to the buffer. If insufficient + * space is available, not all zeros may actually be written. + * Returns the number of zeroes actually written. + */ + size_t zero(size_t n); + +protected: + T *m_buffer; + volatile size_t m_writer; + volatile size_t m_readers[N]; + size_t m_size; + bool m_mlocked; + + static Scavenger > m_scavenger; + +private: + RingBuffer(const RingBuffer &); // not provided + RingBuffer &operator=(const RingBuffer &); // not provided +}; + +template +Scavenger > RingBuffer::m_scavenger; + +template +RingBuffer::RingBuffer(size_t n) : + m_buffer(new T[n + 1]), + m_writer(0), + m_size(n + 1), + m_mlocked(false) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::RingBuffer(" << n << ")" << std::endl; +#endif + + for (int i = 0; i < N; ++i) m_readers[i] = 0; + + m_scavenger.scavenge(); +} + +template +RingBuffer::~RingBuffer() +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::~RingBuffer" << std::endl; +#endif + + if (m_mlocked) { + MUNLOCK((void *)m_buffer, m_size * sizeof(T)); + } + delete[] m_buffer; + + m_scavenger.scavenge(); +} + +template +size_t +RingBuffer::getSize() const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getSize(): " << m_size-1 << std::endl; +#endif + + return m_size - 1; +} + +template +void +RingBuffer::resize(size_t newSize) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::resize(" << newSize << ")" << std::endl; +#endif + + m_scavenger.scavenge(); + + if (m_mlocked) { + MUNLOCK((void *)m_buffer, m_size * sizeof(T)); + } + + m_scavenger.claim(new ScavengerArrayWrapper(m_buffer)); + + reset(); + m_buffer = new T[newSize + 1]; + m_size = newSize + 1; + + if (m_mlocked) { + if (MLOCK((void *)m_buffer, m_size * sizeof(T))) { + m_mlocked = false; + } + } +} + +template +RingBuffer * +RingBuffer::resized(size_t newSize, int R) const +{ + RingBuffer *newBuffer = new RingBuffer(newSize); + + size_t w = m_writer; + size_t r = m_readers[R]; + + while (r != w) { + T value = m_buffer[r]; + newBuffer->write(&value, 1); + if (++r == m_size) r = 0; + } + + return newBuffer; +} + +template +bool +RingBuffer::mlock() +{ + if (MLOCK((void *)m_buffer, m_size * sizeof(T))) return false; + m_mlocked = true; + return true; +} + +template +void +RingBuffer::reset() +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::reset" << std::endl; +#endif + + m_writer = 0; + for (int i = 0; i < N; ++i) m_readers[i] = 0; +} + +template +size_t +RingBuffer::getReadSpace(int R) const +{ + size_t writer = m_writer; + size_t reader = m_readers[R]; + size_t space; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getReadSpace(" << R << "): reader " << reader << ", writer " << writer << std::endl; +#endif + + if (writer > reader) space = writer - reader; + else if (writer < reader) space = (writer + m_size) - reader; + else space = 0; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getReadSpace(" << R << "): " << space << std::endl; +#endif + + return space; +} + +template +size_t +RingBuffer::getWriteSpace() const +{ + size_t space = 0; + for (int i = 0; i < N; ++i) { + size_t writer = m_writer; + size_t reader = m_readers[i]; + size_t here = (reader + m_size - writer - 1); + if (here >= m_size) here -= m_size; + if (i == 0 || here < space) space = here; + } + +#ifdef DEBUG_RINGBUFFER + size_t rs(getReadSpace()), rp(m_readers[0]); + + std::cerr << "RingBuffer: write space " << space << ", read space " + << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl; + std::cerr << "RingBuffer: reader " << rp << ", writer " << m_writer << std::endl; +#endif + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::getWriteSpace(): " << space << std::endl; +#endif + + return space; +} + +template +size_t +RingBuffer::read(T *destination, size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::read(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + for (size_t i = available; i < n; ++i) { + destination[i] = 0; + } + n = available; + } + if (n == 0) return n; + + size_t reader = m_readers[R]; + size_t here = m_size - reader; + + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + destination[i] = (m_buffer + reader)[i]; + } + } else { + for (size_t i = 0; i < here; ++i) { + destination[i] = (m_buffer + reader)[i]; + } + for (size_t i = 0; i < (n - here); ++i) { + destination[i + here] = m_buffer[i]; + } + } + + reader += n; + while (reader >= m_size) reader -= m_size; + m_readers[R] = reader; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::read: read " << n << ", reader now " << m_readers[R] << std::endl; +#endif + + return n; +} + +template +size_t +RingBuffer::readAdding(T *destination, size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::readAdding(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t reader = m_readers[R]; + size_t here = m_size - reader; + + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + destination[i] += (m_buffer + reader)[i]; + } + } else { + for (size_t i = 0; i < here; ++i) { + destination[i] += (m_buffer + reader)[i]; + } + for (size_t i = 0; i < (n - here); ++i) { + destination[i + here] += m_buffer[i]; + } + } + + reader += n; + while (reader >= m_size) reader -= m_size; + m_readers[R] = reader; + return n; +} + +template +T +RingBuffer::readOne(int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::readOne(" << R << ")" << std::endl; +#endif + + if (m_writer == m_readers[R]) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: No sample available" + << std::endl; +#endif + return 0; + } + size_t reader = m_readers[R]; + T value = m_buffer[reader]; + if (++reader == m_size) reader = 0; + m_readers[R] = reader; + return value; +} + +template +size_t +RingBuffer::peek(T *destination, size_t n, int R) const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek(dest, " << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + memset(destination + available, 0, (n - available) * sizeof(T)); + n = available; + } + if (n == 0) return n; + + size_t reader = m_readers[R]; + size_t here = m_size - reader; + + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + destination[i] = (m_buffer + reader)[i]; + } + } else { + for (size_t i = 0; i < here; ++i) { + destination[i] = (m_buffer + reader)[i]; + } + for (size_t i = 0; i < (n - here); ++i) { + destination[i + here] = m_buffer[i]; + } + } + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek: read " << n << std::endl; +#endif + + return n; +} + +template +T +RingBuffer::peekOne(int R) const +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::peek(" << R << ")" << std::endl; +#endif + + if (m_writer == m_readers[R]) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: No sample available" + << std::endl; +#endif + return 0; + } + T value = m_buffer[m_readers[R]]; + return value; +} + +template +size_t +RingBuffer::skip(size_t n, int R) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::skip(" << n << ", " << R << ")" << std::endl; +#endif + + size_t available = getReadSpace(R); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only " << available << " samples available" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t reader = m_readers[R]; + reader += n; + while (reader >= m_size) reader -= m_size; + m_readers[R] = reader; + return n; +} + +template +size_t +RingBuffer::write(const T *source, size_t n) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::write(" << n << ")" << std::endl; +#endif + + size_t available = getWriteSpace(); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only room for " << available << " samples" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t writer = m_writer; + size_t here = m_size - writer; + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + (m_buffer + writer)[i] = source[i]; + } + } else { + for (size_t i = 0; i < here; ++i) { + (m_buffer + writer)[i] = source[i]; + } + for (size_t i = 0; i < (n - here); ++i) { + m_buffer[i] = (source + here)[i]; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::write: wrote " << n << ", writer now " << m_writer << std::endl; +#endif + + return n; +} + +template +size_t +RingBuffer::zero(size_t n) +{ +#ifdef DEBUG_RINGBUFFER + std::cerr << "RingBuffer[" << this << "]::zero(" << n << ")" << std::endl; +#endif + + size_t available = getWriteSpace(); + if (n > available) { +#ifdef DEBUG_RINGBUFFER + std::cerr << "WARNING: Only room for " << available << " samples" + << std::endl; +#endif + n = available; + } + if (n == 0) return n; + + size_t writer = m_writer; + size_t here = m_size - writer; + if (here >= n) { + for (size_t i = 0; i < n; ++i) { + (m_buffer + writer)[i] = 0; + } + } else { + for (size_t i = 0; i < here; ++i) { + (m_buffer + writer)[i] = 0; + } + for (size_t i = 0; i < (n - here); ++i) { + m_buffer[i] = 0; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + +#ifdef DEBUG_RINGBUFFER + std::cerr << "writer -> " << m_writer << std::endl; +#endif + + return n; +} + +} + +#endif // _RINGBUFFER_H_ diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp new file mode 100644 index 0000000..7d67f90 --- /dev/null +++ b/src/RubberBandStretcher.cpp @@ -0,0 +1,178 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "StretcherImpl.h" + +namespace RubberBand { + + +RubberBandStretcher::RubberBandStretcher(size_t sampleRate, + size_t channels, + Options options, + double initialTimeRatio, + double initialPitchScale) : + TimeStretcher(sampleRate, channels), + m_d(new Impl(this, sampleRate, channels, options, + initialTimeRatio, initialPitchScale)) +{ +} + +RubberBandStretcher::~RubberBandStretcher() +{ + delete m_d; +} + +void +RubberBandStretcher::reset() +{ + m_d->reset(); +} + +void +RubberBandStretcher::setTimeRatio(double ratio) +{ + m_d->setTimeRatio(ratio); +} + +void +RubberBandStretcher::setPitchScale(double scale) +{ + m_d->setPitchScale(scale); +} + +double +RubberBandStretcher::getTimeRatio() const +{ + return m_d->getTimeRatio(); +} + +double +RubberBandStretcher::getPitchScale() const +{ + return m_d->getPitchScale(); +} + +size_t +RubberBandStretcher::getLatency() const +{ + return m_d->getLatency(); +} + +void +RubberBandStretcher::setTransientsOption(Options options) +{ + m_d->setTransientsOption(options); +} + +void +RubberBandStretcher::setPhaseOption(Options options) +{ + m_d->setPhaseOption(options); +} + +void +RubberBandStretcher::setExpectedInputDuration(size_t samples) +{ + m_d->setExpectedInputDuration(samples); +} + +void +RubberBandStretcher::setMaxProcessBlockSize(size_t samples) +{ + m_d->setMaxProcessBlockSize(samples); +} + +size_t +RubberBandStretcher::getSamplesRequired() const +{ + return m_d->getSamplesRequired(); +} + +void +RubberBandStretcher::study(const float *const *input, size_t samples, + bool final) +{ + m_d->study(input, samples, final); +} + +void +RubberBandStretcher::process(const float *const *input, size_t samples, + bool final) +{ + m_d->process(input, samples, final); +} + +int +RubberBandStretcher::available() const +{ + return m_d->available(); +} + +size_t +RubberBandStretcher::retrieve(float *const *output, size_t samples) const +{ + return m_d->retrieve(output, samples); +} + +float +RubberBandStretcher::getFrequencyCutoff(int n) const +{ + return m_d->getFrequencyCutoff(n); +} + +void +RubberBandStretcher::setFrequencyCutoff(int n, float f) +{ + m_d->setFrequencyCutoff(n, f); +} + +size_t +RubberBandStretcher::getInputIncrement() const +{ + return m_d->getInputIncrement(); +} + +std::vector +RubberBandStretcher::getOutputIncrements() const +{ + return m_d->getOutputIncrements(); +} + +std::vector +RubberBandStretcher::getLockCurve() const +{ + return m_d->getLockCurve(); +} + +size_t +RubberBandStretcher::getChannelCount() const +{ + return m_d->getChannelCount(); +} + +void +RubberBandStretcher::calculateStretch() +{ + m_d->calculateStretch(); +} + +void +RubberBandStretcher::setDebugLevel(int level) +{ + m_d->setDebugLevel(level); +} + + +} + diff --git a/src/Scavenger.h b/src/Scavenger.h new file mode 100644 index 0000000..e4385f9 --- /dev/null +++ b/src/Scavenger.h @@ -0,0 +1,198 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_SCAVENGER_H_ +#define _RUBBERBAND_SCAVENGER_H_ + +#include +#include +#include +#include +#include + +namespace RubberBand { + +/** + * A very simple class that facilitates running things like plugins + * without locking, by collecting unwanted objects and deleting them + * after a delay so as to be sure nobody's in the middle of using + * them. Requires scavenge() to be called regularly from a non-RT + * thread. + * + * This is currently not at all suitable for large numbers of objects + * -- it's just a quick hack for use with things like plugins. + */ + +template +class Scavenger +{ +public: + Scavenger(int sec = 2, int defaultObjectListSize = 200); + ~Scavenger(); + + /** + * Call from an RT thread etc., to pass ownership of t to us. + * Only one thread should be calling this on any given scavenger. + */ + void claim(T *t); + + /** + * Call from a non-RT thread. + * Only one thread should be calling this on any given scavenger. + */ + void scavenge(bool clearNow = false); + +protected: + typedef std::pair ObjectTimePair; + typedef std::vector ObjectTimeList; + ObjectTimeList m_objects; + int m_sec; + + typedef std::list ObjectList; + ObjectList m_excess; + int m_lastExcess; + pthread_mutex_t m_excessMutex; + void pushExcess(T *); + void clearExcess(int); + + unsigned int m_claimed; + unsigned int m_scavenged; +}; + +/** + * A wrapper to permit arrays to be scavenged. + */ + +template +class ScavengerArrayWrapper +{ +public: + ScavengerArrayWrapper(T *array) : m_array(array) { } + ~ScavengerArrayWrapper() { delete[] m_array; } + +private: + T *m_array; +}; + + +template +Scavenger::Scavenger(int sec, int defaultObjectListSize) : + m_objects(ObjectTimeList(defaultObjectListSize)), + m_sec(sec), + m_claimed(0), + m_scavenged(0) +{ + pthread_mutex_init(&m_excessMutex, 0); +} + +template +Scavenger::~Scavenger() +{ + if (m_scavenged < m_claimed) { + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (pair.first != 0) { + T *ot = pair.first; + pair.first = 0; + delete ot; + ++m_scavenged; + } + } + } + + clearExcess(0); +} + +template +void +Scavenger::claim(T *t) +{ +// std::cerr << "Scavenger::claim(" << t << ")" << std::endl; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + int sec = tv.tv_sec; + + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (pair.first == 0) { + pair.second = sec; + pair.first = t; + ++m_claimed; + return; + } + } + + std::cerr << "WARNING: Scavenger::claim(" << t << "): run out of slots, " + << "using non-RT-safe method" << std::endl; + pushExcess(t); +} + +template +void +Scavenger::scavenge(bool clearNow) +{ +// std::cerr << "Scavenger::scavenge: scavenged " << m_scavenged << ", claimed " << m_claimed << std::endl; + + if (m_scavenged >= m_claimed) return; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + int sec = tv.tv_sec; + + for (size_t i = 0; i < m_objects.size(); ++i) { + ObjectTimePair &pair = m_objects[i]; + if (clearNow || + (pair.first != 0 && pair.second + m_sec < sec)) { + T *ot = pair.first; + pair.first = 0; + delete ot; + ++m_scavenged; + } + } + + if (sec > m_lastExcess + m_sec) { + clearExcess(sec); + } +} + +template +void +Scavenger::pushExcess(T *t) +{ + pthread_mutex_lock(&m_excessMutex); + m_excess.push_back(t); + struct timeval tv; + (void)gettimeofday(&tv, 0); + m_lastExcess = tv.tv_sec; + pthread_mutex_unlock(&m_excessMutex); +} + +template +void +Scavenger::clearExcess(int sec) +{ + pthread_mutex_lock(&m_excessMutex); + for (typename ObjectList::iterator i = m_excess.begin(); + i != m_excess.end(); ++i) { + delete *i; + } + m_excess.clear(); + m_lastExcess = sec; + pthread_mutex_unlock(&m_excessMutex); +} + +} + +#endif diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp new file mode 100644 index 0000000..caf72ef --- /dev/null +++ b/src/StretchCalculator.cpp @@ -0,0 +1,750 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "StretchCalculator.h" + +#include +#include +#include +#include +#include + +namespace RubberBand +{ + +StretchCalculator::StretchCalculator(size_t sampleRate, + size_t inputIncrement, + bool useHardPeaks) : + m_sampleRate(sampleRate), + m_increment(inputIncrement), + m_prevDf(0), + m_divergence(0), + m_recovery(0), + m_prevRatio(1.0), + m_wasTransient(false), + m_useHardPeaks(useHardPeaks) +{ + std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl; +} + +StretchCalculator::~StretchCalculator() +{ +} + +std::vector +StretchCalculator::calculate(double ratio, size_t inputDuration, + const std::vector &lockDf, + const std::vector &stretchDf) +{ + // Method: + + //!!! This description is out of date. + + //!!! Rationalise naming -- generally wise to avoid the word + //"frame" and instead use "block" / "sample" for processing frame / + // audio frame. + + // 1. Pre-process the df array, and for each (say) one second's + // worth of values, calculate the number of peaks that would + // qualify for phase locking given the default threshold. Then + // reduce or increase the threshold by stages until that number is + // in a sensible range (say 1-10 peaks per second -- the low end + // is harder to estimate than the high end, so it may be better to + // start with a high sensitivity and reduce it). + + // 2. Record the positions of peaks, and separately the positions + // of those peaks that qualify for locking using the sliding + // threshold window. Don't permit two locked peaks within a very + // short time frame (e.g. 30-50ms). + + // 3. Map each of the locked peaks (or any peaks that are over a + // given intensity?), as well as the start and end points, to a + // proportionate position in the newly stretched array so as to + // ensure that their timing is strictly "correct". + + // 4. Calculate how much time is left in the stretch total, after + // each of the locked frames has been allocated its static + // allowance. Also count the non-locked frames. + + // 5. For each region between two locked frames, calculate the + // number of samples to allocate that region given the time + // available for stretch and the number of non-locked frames. + // Then divvy them up... how exactly? + + + assert(lockDf.size() == stretchDf.size()); + + std::vector peaks = findPeaks(lockDf); + size_t totalCount = lockDf.size(); + + std::vector increments; + + size_t outputDuration = lrint(inputDuration * ratio); + + std::cerr << "debug level: " << m_debugLevel << std::endl; + + if (m_debugLevel > 0) { + std::cerr << "StretchCalculator::calculate(): inputDuration " << inputDuration << ", ratio " << ratio << ", outputDuration " << outputDuration; + } + + //!!! round down? + outputDuration = lrint((lockDf.size() * m_increment) * ratio); + + if (m_debugLevel > 0) { + std::cerr << " (rounded up to " << outputDuration << ")"; + std::cerr << ", df size " << lockDf.size() << std::endl; + } + +// size_t stretchable = outputDuration - lockCount * m_increment; + + std::vector fixedAudioFrames; + for (size_t i = 0; i < peaks.size(); ++i) { + fixedAudioFrames.push_back + //!!! this should be rounding down, shouldn't it? not lrint? + (lrint((double(peaks[i].frame) * outputDuration) / totalCount)); + } + +// size_t lockIndex = 0; + + if (m_debugLevel > 1) { + std::cerr << "have " << peaks.size() << " fixed positions" << std::endl; + } + + size_t totalInput = 0, totalOutput = 0; + + // so for each inter-lock region, we want to take the number of + // output frames to be allocated and the detection function values + // within the range, and produce a series of increments that sum + // to the number of output frames, such that each increment is + // displaced from the input increment by an amount inversely + // proportional to the magnitude of the detection function at that + // input step. Ideally the detection function would have been + // somewhat smoothed for this purpose but we'll start raw. + + //!!! Actually, we would possibly be better off using a fixed + // smooth curve than the detection function itself. + + size_t regionTotalFrames = 0; + + for (size_t i = 0; i <= peaks.size(); ++i) { + + size_t regionStart, regionStartBlock, regionEnd, regionEndBlock; + bool phaseLock = false; + + if (i == 0) { + regionStartBlock = 0; + regionStart = 0; + } else { + regionStartBlock = peaks[i-1].frame; + regionStart = fixedAudioFrames[i-1]; + phaseLock = peaks[i-1].hard; + } + + if (i == peaks.size()) { + regionEndBlock = totalCount; + regionEnd = outputDuration; + } else { + regionEndBlock = peaks[i].frame; + regionEnd = fixedAudioFrames[i]; + } + + size_t regionDuration = regionEnd - regionStart; + regionTotalFrames += regionDuration; + + std::vector dfRegion; + + for (size_t j = regionStartBlock; j != regionEndBlock; ++j) { + dfRegion.push_back(stretchDf[j]); + } + + if (m_debugLevel > 1) { + std::cerr << "distributeRegion from " << regionStartBlock << " to " << regionEndBlock << " (frames " << regionStart << " to " << regionEnd << ")" << std::endl; + } + + dfRegion = smoothDF(dfRegion); + + std::vector regionIncrements = distributeRegion + (dfRegion, regionDuration, ratio, phaseLock); + + size_t totalForRegion = 0; + + for (size_t j = 0; j < regionIncrements.size(); ++j) { + + int incr = regionIncrements[j]; + + if (j == 0 && phaseLock) increments.push_back(-incr); + else increments.push_back(incr); + + if (incr > 0) totalForRegion += incr; + else totalForRegion += -incr; + + totalInput += m_increment; + } + + if (totalForRegion != regionDuration) { + std::cerr << "*** WARNING: distributeRegion returned wrong duration " << totalForRegion << ", expected " << regionDuration << std::endl; + } + + totalOutput += totalForRegion; + } + + if (m_debugLevel > 0) { + std::cerr << "total input increment = " << totalInput << " (= " << totalInput / m_increment << " blocks), output = " << totalOutput << ", ratio = " << double(totalOutput)/double(totalInput) << ", ideal output " << ceil(totalInput * ratio) << std::endl; + std::cerr << "(region total = " << regionTotalFrames << ")" << std::endl; + } + return increments; +} + +int +StretchCalculator::calculateSingle(double ratio, + size_t inputDurationSoFar, + float df) +{ + bool isTransient = false; + + //!!! We want to ensure, as close as possible, that the lock + // points appear at _exactly_ the right frame numbers + + //!!! depends on block size. larger block sizes need higher + //thresholds. since block size depends on ratio, I suppose we + //could in theory calculate the threshold from the ratio directly. + //For now we just frig it to work OK for a couple of common cases + float transientThreshold = 0.35; + if (ratio > 1) transientThreshold = 0.25; + + if (m_useHardPeaks && df > m_prevDf * 1.1 && df > transientThreshold) { + isTransient = true; + } + + if (m_debugLevel > 2) { + std::cerr << "df = " << df << ", prevDf = " << m_prevDf + << ", thresh = " << transientThreshold << std::endl; + } + + m_prevDf = df; + + if (isTransient && !m_wasTransient) { + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: transient found at " + << inputDurationSoFar << std::endl; + } + m_divergence += m_increment - (m_increment * ratio); + m_wasTransient = true; + m_recovery = m_divergence / ((m_sampleRate / 10.0) / m_increment); + return -m_increment; + } + + if (m_prevRatio != ratio) { + m_recovery = m_divergence / ((m_sampleRate / 10.0) / m_increment); + m_prevRatio = ratio; + } + + //!!! want transient amnesty as above (hard peak amnesty) + m_wasTransient = false; + + int incr = lrint(m_increment * ratio - m_recovery); + if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { + std::cerr << "divergence = " << m_divergence << ", recovery = " << m_recovery << ", incr = " << incr << ", "; + } + if (incr < (m_increment * ratio) / 2) { + incr = (m_increment * ratio) / 2; + } else if (incr > m_increment * ratio * 2) { + incr = m_increment * ratio * 2; + } + + double divdiff = (m_increment * ratio) - incr; + + if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { + std::cerr << "divdiff = " << divdiff << std::endl; + } + + double prevDivergence = m_divergence; + m_divergence -= divdiff; + if ((prevDivergence < 0 && m_divergence > 0) || + (prevDivergence > 0 && m_divergence < 0)) { + m_recovery = m_divergence / ((m_sampleRate / 10.0) / m_increment); + } + + return incr; +} + +void +StretchCalculator::reset() +{ + m_prevDf = 0; + m_divergence = 0; +} + +std::vector +StretchCalculator::findPeaks(const std::vector &rawDf) +{ + std::vector df = smoothDF(rawDf); + + std::set hardPeakCandidates; + std::set softPeakCandidates; + + if (m_useHardPeaks) { + + //!!! this should depend on duration based on output increment surely? + size_t hardPeakAmnesty = lrint(ceil(double(m_sampleRate) / + (20 * double(m_increment)))); // 0.05 sec ish +// size_t hardPeakAmnesty = 5; + + size_t prevHardPeak = 0; + std::cerr << "hardPeakAmnesty = " << hardPeakAmnesty << std::endl; + for (size_t i = 1; i + 1 < df.size(); ++i) { + + //!!! this ratio configurable? dependent on block size and sr? + + if (df[i] < 0.1) continue; + if (df[i] <= df[i-1] * 1.2) continue; + + if (df[i] > df[i-1] * 1.4 || + (df[i+1] > df[i] && df[i+1] > df[i-1] * 1.8) || + df[i] > 0.4) { + if (!hardPeakCandidates.empty() && + i < prevHardPeak + hardPeakAmnesty) { + continue; + } + size_t peakLocation = i; + if (i + 1 < rawDf.size() && + rawDf[i + 1] > rawDf[i] * 1.4) { + ++peakLocation; + } + if (m_debugLevel > 1) { + std::cerr << "hard peak at " << peakLocation << " (" << df[peakLocation] << " > " << df[peakLocation-1] << " * " << 1.4 << ")" << std::endl; + } + hardPeakCandidates.insert(peakLocation); + prevHardPeak = peakLocation; + } + } + } + + //!!! we don't yet do the right thing with soft peaks. if + //!useHardPeaks, we should be locking on soft peaks; if + //useHardPeaks, we should be ignoring soft peaks if they occur + //shortly after hard ones, otherwise either locking on the, or at + //least making sure they fall at the correct sample time + +// int mediansize = lrint(ceil(double(m_sampleRate) / +// (4 * double(m_increment)))); // 0.25 sec ish + size_t medianmaxsize = lrint(ceil(double(m_sampleRate) / + double(m_increment))); // 1 sec ish +// int mediansize = lrint(ceil(double(m_sampleRate) / +// (2 * double(m_increment)))); // 0.5 sec ish + + if (m_debugLevel > 1) { + std::cerr << "mediansize = " << medianmaxsize << std::endl; + } + if (medianmaxsize < 7) { + medianmaxsize = 7; + if (m_debugLevel > 1) { + std::cerr << "adjusted mediansize = " << medianmaxsize << std::endl; + } + } + + int minspacing = lrint(ceil(double(m_sampleRate) / + (20 * double(m_increment)))); // 0.05 sec ish + + std::deque medianwin; + std::vector sorted; + int softPeakAmnesty = 0; + + for (size_t i = 0; i < medianmaxsize/2; ++i) { + medianwin.push_back(0); + } + for (size_t i = 0; i < medianmaxsize/2 && i < df.size(); ++i) { + medianwin.push_back(df[i]); + } + + size_t lastSoftPeak = 0; + + for (size_t i = 0; i < df.size(); ++i) { + + size_t mediansize = medianmaxsize; + + if (medianwin.size() < mediansize) { + mediansize = medianwin.size(); + } + + if (mediansize < 2) { + if (i > medianmaxsize) { + medianwin.pop_front(); + } + if (i < df.size()) medianwin.push_back(df[i]); + continue; + } + + if (m_debugLevel > 2) { +// std::cerr << "have " << mediansize << " in median buffer" << std::endl; + } + + sorted.clear(); + for (size_t j = 0; j < mediansize; ++j) { + sorted.push_back(medianwin[j]); + } + std::sort(sorted.begin(), sorted.end()); + + size_t n = 90; // percentile above which we pick peaks + size_t index = (sorted.size() * n) / 100; + if (index >= sorted.size()) index = sorted.size()-1; + if (index == sorted.size()-1 && index > 0) --index; + float thresh = sorted[index]; + + size_t middle = medianmaxsize / 2; + if (middle >= mediansize) middle = mediansize-1; + + if (m_debugLevel > 2) { + std::cerr << "medianwin[" << middle << "] = " << medianwin[middle] << ", thresh = " << thresh << std::endl; + } + + if (medianwin[middle] > thresh && + medianwin[middle] > medianwin[middle-1] && + medianwin[middle] > medianwin[middle+1] && + softPeakAmnesty == 0) { + + size_t maxindex = middle; + float maxval = medianwin[middle]; + + for (size_t j = middle+1; j < mediansize; ++j) { + if (medianwin[j] > maxval) { + maxval = medianwin[j]; + maxindex = j; + } else if (medianwin[j] < medianwin[middle]) { + break; + } + } + + //!!! we should distinguish between soft peaks (any found + //using the above method) and hard peaks, which also show + //a very rapid rise in detection function prior to the + //peak (the first value after the rise is not necessarily + //the peak itself, but it is probably where we should + //locate the lock). For hard peaks we need to lock in + //time to preserve the shape of the transient (unless some + //option is set to soft mode), for soft peaks we just want + //to avoid phase drift so we build up to the lock at the + //exact peak moment. + +// size_t peak = i + maxindex - mediansize; + size_t peak = i + maxindex - middle; + +// if (peak > 0) --peak; //!!! that's a fudge + + if (softPeakCandidates.empty() || lastSoftPeak != peak) { + + if (m_debugLevel > 1) { + std::cerr << "soft peak at " << peak << " (" << peak * m_increment << "): " + << medianwin[middle] << " > " << thresh << " and " + << medianwin[middle] << " > " << medianwin[middle-1] << " and " + << medianwin[middle] << " > " << medianwin[middle+1] + << std::endl; + } + + if (peak >= df.size()) { + if (m_debugLevel > 2) { + std::cerr << "peak is beyond end" << std::endl; + } + } else { + softPeakCandidates.insert(peak); + lastSoftPeak = peak; + } + } + + softPeakAmnesty = minspacing + maxindex - middle; + if (m_debugLevel > 2) { + std::cerr << "amnesty = " << softPeakAmnesty << std::endl; + } + + } else if (softPeakAmnesty > 0) --softPeakAmnesty; + + if (medianwin.size() >= medianmaxsize) { + medianwin.pop_front(); + } + if (i < df.size()) medianwin.push_back(df[i]); + } + + std::vector peaks; + + //!!! +// if (!softPeakCandidates.empty()) { +// std::cerr << "clearing " << softPeakCandidates.size() << " soft peak candidates" << std::endl; +// } +// softPeakCandidates.clear(); + + + while (!hardPeakCandidates.empty() || !softPeakCandidates.empty()) { + bool haveHardPeak = !hardPeakCandidates.empty(); + bool haveSoftPeak = !softPeakCandidates.empty(); + size_t hardPeak = (haveHardPeak ? *hardPeakCandidates.begin() : 0); + size_t softPeak = (haveSoftPeak ? *softPeakCandidates.begin() : 0); + Peak peak; + peak.hard = false; + peak.frame = softPeak; + if (haveHardPeak && + (!haveSoftPeak || hardPeak <= softPeak)) { + if (m_debugLevel > 2) { + std::cerr << "Hard peak: " << hardPeak << std::endl; + } + peak.hard = true; + peak.frame = hardPeak; + hardPeakCandidates.erase(hardPeakCandidates.begin()); + } else { + if (m_debugLevel > 2) { + std::cerr << "Soft peak: " << softPeak << std::endl; + } + } + + if (haveSoftPeak && peak.frame == softPeak) { + softPeakCandidates.erase(softPeakCandidates.begin()); + } + + peaks.push_back(peak); + } + + return peaks; +} + +std::vector +StretchCalculator::smoothDF(const std::vector &df) +{ + std::vector smoothedDF; + + for (size_t i = 0; i < df.size(); ++i) { + // three-value moving mean window for simple smoothing + float total = 0.f, count = 0; + if (i > 0) { total += df[i-1]; ++count; } + total += df[i]; ++count; + if (i+1 < df.size()) { total += df[i+1]; ++count; } + float mean = total / count; +// if (isnan(mean)) { +// std::cerr << "ERROR: mean at " << i << " (of " << df.size() << ") is NaN: dfs are: " +// << df[i-1] << ", " << df[i] << ", " << df[i+1] << std::endl; +// } + smoothedDF.push_back(mean); + } + + return smoothedDF; +} + +std::vector +StretchCalculator::distributeRegion(const std::vector &dfIn, + size_t duration, float ratio, bool lock) +{ + std::vector df(dfIn); + std::vector increments; + + // The peak for the stretch detection function may appear after + // the peak that we're using to calculate the start of the region. + // We don't want that. If we find a peak in the first half of + // the region, we should set all the values up to that point to + // the same value as the peak. + + //!!! this is not subtle enough, especially if the region is long + //-- we want a bound that corresponds to acoustic perception of + //the audible bounce + + for (size_t i = 1; i < df.size()/2; ++i) { + if (df[i] < df[i-1]) { + if (m_debugLevel > 1) { + std::cerr << "stretch peak offset: " << i-1 << " (peak " << df[i-1] << ")" << std::endl; + } + for (size_t j = 0; j < i-1; ++j) { + df[j] = df[i-1]; + } + break; + } + } + +// for (size_t i = 0; i < df.size(); ++i) { +// if (i == 0 || df[i] > maxDf) maxDf = df[i]; +// } + + long toAllot = long(duration) - long(m_increment * df.size()); +// bool negative = (toAllot < 0); + + if (m_debugLevel > 1) { + std::cerr << "region of " << df.size() << " blocks, output duration " << duration << ", toAllot " << toAllot << std::endl; + } + + size_t totalIncrement = 0; + + //!!! we need to place limits on the amount of displacement per + //chunk. if ratio < 0, no increment should be larger than + //increment*ratio or smaller than increment*ratio/2; if ratio > 0, + //none should be smaller than increment*ratio or larger than + //increment*ratio*2. We need to enforce this in the assignment of + //displacements to allotments, not by trying to respond if + //something turns out wrong + + //!!! ratio is only provided to this function for the purposes of + //establishing this bound to the displacement + + // so if maxDisplacement / totalDisplacement > increment * ratio*2 - increment (for ratio > 1) + // or maxDisplacement / totalDisplacement < increment * ratio/2 (for ratio < 1) + // then we need to adjust... what? + + bool acceptableSquashRange = false; + + double totalDisplacement = 0; + double maxDisplacement = 0; // min displacement will be 0 by definition + + float maxDf = 0; + float adj = 0; + + while (!acceptableSquashRange) { + + acceptableSquashRange = true; + calculateDisplacements(df, maxDf, totalDisplacement, maxDisplacement, + adj); + + if (m_debugLevel > 1) { + std::cerr << "totalDisplacement " << totalDisplacement << ", max " << maxDisplacement << " (maxDf " << maxDf << ", df count " << df.size() << ")" << std::endl; + } + + if (totalDisplacement == 0) { +// Not usually a problem, in fact +// std::cerr << "WARNING: totalDisplacement == 0 (duration " << duration << ", " << df.size() << " values in df)" << std::endl; + if (!df.empty() && adj == 0) { + acceptableSquashRange = false; + adj = 1; + } + continue; + } + + int extremeIncrement = m_increment + lrint((toAllot * maxDisplacement) / totalDisplacement); + if (ratio < 1.0) { + if (extremeIncrement > lrint(ceil(m_increment * ratio))) { + std::cerr << "ERROR: extreme increment " << extremeIncrement << " > " << m_increment * ratio << " (I thought this couldn't happen?)" << std::endl; + } else if (extremeIncrement < (m_increment * ratio) / 2) { + if (m_debugLevel > 0) { + std::cerr << "WARNING: extreme increment " << extremeIncrement << " < " << (m_increment * ratio) / 2 << std::endl; + } + acceptableSquashRange = false; + } + } else { + if (extremeIncrement > m_increment * ratio * 2) { + if (m_debugLevel > 0) { + std::cerr << "WARNING: extreme increment " << extremeIncrement << " > " << m_increment * ratio * 2 << std::endl; + } + acceptableSquashRange = false; + } else if (extremeIncrement < lrint(floor(m_increment * ratio))) { + std::cerr << "ERROR: extreme increment " << extremeIncrement << " < " << m_increment * ratio << " (I thought this couldn't happen?)" << std::endl; + } + } + + if (!acceptableSquashRange) { + // Need to make maxDisplacement smaller as a proportion of + // the total displacement, yet ensure that the + // displacements still sum to the total. How? + +// std::cerr << "Adjusting df values by " << maxDf/10 << "..." << std::endl; + +// std::cerr << "now: "; +// for (size_t i = 0; i < df.size(); ++i) { +// df[i] += maxDf/10; +// std::cerr << df[i] << " "; +// } +// std::cerr << std::endl; + adj += maxDf/10; + + //... + } + } + + for (size_t i = 0; i < df.size(); ++i) { + + double displacement = maxDf - df[i]; + if (displacement < 0) displacement -= adj; + else displacement += adj; + + if (i == 0 && lock) { + if (df.size() == 1) { + increments.push_back(duration); + totalIncrement += duration; + } else { + increments.push_back(m_increment); + totalIncrement += m_increment; + } + totalDisplacement -= displacement; + continue; + } + + double theoreticalAllotment = 0; + + if (totalDisplacement != 0) { + theoreticalAllotment = (toAllot * displacement) / totalDisplacement; + } + int allotment = lrint(theoreticalAllotment); + if (i + 1 == df.size()) allotment = toAllot; + + int increment = m_increment + allotment; + + if (increment <= 0) { + //!!! this is a serious problem, the allocation is quite wrong if it allows increment to diverge so far from the input increment + std::cerr << "*** WARNING: increment " << increment << " <= 0, rounding to zero" << std::endl; + increment = 0; + allotment = increment - m_increment; + } + + increments.push_back(increment); + totalIncrement += increment; + + toAllot -= allotment; + totalDisplacement -= displacement; + + if (m_debugLevel > 2) { + std::cerr << "df " << df[i] << ", smoothed " << df[i] << ", disp " << displacement << ", allot " << theoreticalAllotment << ", incr " << increment << ", remain " << toAllot << std::endl; + } + } + + if (m_debugLevel > 2) { + std::cerr << "total increment: " << totalIncrement << ", left over: " << toAllot << " to allot, displacement " << totalDisplacement << std::endl; + } + + if (totalIncrement != duration) { + std::cerr << "*** WARNING: calculated output duration " << totalIncrement << " != expected " << duration << std::endl; + } + + return increments; +} + +void +StretchCalculator::calculateDisplacements(const std::vector &df, + float &maxDf, + double &totalDisplacement, + double &maxDisplacement, + float adj) const +{ + totalDisplacement = maxDisplacement = 0; + + maxDf = 0; + + for (size_t i = 0; i < df.size(); ++i) { + if (i == 0 || df[i] > maxDf) maxDf = df[i]; + } + + for (size_t i = 0; i < df.size(); ++i) { + double displacement = maxDf - df[i]; + if (displacement < 0) displacement -= adj; + else displacement += adj; + totalDisplacement += displacement; + if (i == 0 || displacement > maxDisplacement) { + maxDisplacement = displacement; + } + } +} + +} + diff --git a/src/StretchCalculator.h b/src/StretchCalculator.h new file mode 100644 index 0000000..293293f --- /dev/null +++ b/src/StretchCalculator.h @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_STRETCH_CALCULATOR_H_ +#define _RUBBERBAND_STRETCH_CALCULATOR_H_ + +#include + +#include + +namespace RubberBand +{ + +class StretchCalculator +{ +public: + StretchCalculator(size_t sampleRate, size_t inputIncrement, bool useHardPeaks); + virtual ~StretchCalculator(); + + /** + * Calculate phase increments for a region of audio, given the + * overall target stretch ratio, input duration in audio samples, + * and the audio curves to use for identifying phase lock points + * (lockAudioCurve) and for allocating stretches to relatively + * less prominent points (stretchAudioCurve). + */ + virtual std::vector calculate(double ratio, size_t inputDuration, + const std::vector &lockAudioCurve, + const std::vector &stretchAudioCurve); + + /** + * Calculate the phase increment for a single audio block, given + * the overall target stretch ratio and the block's value on the + * phase-lock audio curve. State is retained between calls in the + * StretchCalculator object; call reset() to reset it. This uses + * a less sophisticated method than the offline calculate(). + */ + virtual int calculateSingle(double ratio, size_t inputDurationSoFar, + float curveValue); + + void reset(); + + void setDebugLevel(int level) { m_debugLevel = level; } + + std::vector smoothDF(const std::vector &df); + +protected: + struct Peak { + size_t frame; + bool hard; + }; + std::vector findPeaks(const std::vector &audioCurve); + + std::vector distributeRegion(const std::vector ®ionCurve, + size_t outputDuration, float ratio, + bool lock); + + void calculateDisplacements(const std::vector &df, + float &maxDf, + double &totalDisplacement, + double &maxDisplacement, + float adj) const; + + size_t m_sampleRate; + size_t m_blockSize; + size_t m_increment; + float m_prevDf; + double m_divergence; + float m_recovery; + float m_prevRatio; + bool m_wasTransient; + int m_debugLevel; + bool m_useHardPeaks; +}; + +} + +#endif diff --git a/src/StretcherChannelData.cpp b/src/StretcherChannelData.cpp new file mode 100644 index 0000000..14fcd5c --- /dev/null +++ b/src/StretcherChannelData.cpp @@ -0,0 +1,264 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "StretcherChannelData.h" + +#include "Resampler.h" + +namespace RubberBand +{ + +RubberBandStretcher::Impl::ChannelData::ChannelData(size_t blockSize, + size_t outbufSize) +{ + std::set s; + construct(s, blockSize, outbufSize); +} + +RubberBandStretcher::Impl::ChannelData::ChannelData(const std::set &blockSizes, + size_t initialBlockSize, + size_t outbufSize) +{ + construct(blockSizes, initialBlockSize, outbufSize); +} + +void +RubberBandStretcher::Impl::ChannelData::construct(const std::set &blockSizes, + size_t initialBlockSize, + size_t outbufSize) +{ + size_t maxSize = initialBlockSize; + + if (!blockSizes.empty()) { + // std::set is ordered by value + std::set::const_iterator i = blockSizes.end(); + maxSize = *--i; + } + if (blockSizes.find(initialBlockSize) == blockSizes.end()) { + if (initialBlockSize > maxSize) maxSize = initialBlockSize; + } + + size_t realSize = maxSize/2 + 1; // size of the real "half" of freq data + + std::cerr << "ChannelData::construct([" << blockSizes.size() << "], " << maxSize << ", " << outbufSize << ")" << std::endl; + + if (outbufSize < maxSize) outbufSize = maxSize; + + inbuf = new RingBuffer(maxSize); + outbuf = new RingBuffer(outbufSize); + + mag = new double[realSize]; + phase = new double[realSize]; + prevPhase = new double[realSize]; + unwrappedPhase = new double[realSize]; + freqPeak = new size_t[realSize]; + + accumulator = new float[maxSize]; + windowAccumulator = new float[maxSize]; + + fltbuf = new float[maxSize]; + dblbuf = new double[maxSize]; + + for (std::set::const_iterator i = blockSizes.begin(); + i != blockSizes.end(); ++i) { + ffts[*i] = new FFT(*i); + ffts[*i]->initDouble(); + } + if (blockSizes.find(initialBlockSize) == blockSizes.end()) { + ffts[initialBlockSize] = new FFT(initialBlockSize); + ffts[initialBlockSize]->initDouble(); + } + fft = ffts[initialBlockSize]; + + resampler = 0; + resamplebuf = 0; + resamplebufSize = 0; + + reset(); + + for (size_t i = 0; i < realSize; ++i) { + mag[i] = 0.0; + phase[i] = 0.0; + prevPhase[i] = 0.0; + unwrappedPhase[i] = 0.0; + freqPeak[i] = 0; + } + + for (size_t i = 0; i < maxSize; ++i) { + accumulator[i] = 0.f; + windowAccumulator[i] = 0.f; + dblbuf[i] = 0.0; + fltbuf[i] = 0.0; + } +} + +void +RubberBandStretcher::Impl::ChannelData::setBlockSize(size_t blockSize) +{ + size_t oldSize = inbuf->getSize(); + size_t realSize = blockSize/2 + 1; + + std::cerr << "ChannelData::setBlockSize(" << blockSize << ") [from " << oldSize << "]" << std::endl; + + if (oldSize >= blockSize) { + + // no need to reallocate buffers, just reselect fft + + //!!! we can't actually do this without locking against the + //process thread, can we? we need to zero the mag/phase + //buffers without interference + + if (ffts.find(blockSize) == ffts.end()) { + //!!! this also requires a lock, but it shouldn't occur in + //RT mode with proper initialisation + ffts[blockSize] = new FFT(blockSize); + ffts[blockSize]->initDouble(); + } + + fft = ffts[blockSize]; + + for (size_t i = 0; i < realSize; ++i) { + mag[i] = 0.0; + phase[i] = 0.0; + prevPhase[i] = 0.0; + unwrappedPhase[i] = 0.0; + freqPeak[i] = 0; + } + + return; + } + + //!!! at this point we need a lock in case a different client + //thread is calling process() -- we need this lock even if we + //aren't running in threaded mode ourselves -- if we're in RT + //mode, then the process call should trylock and fail if the lock + //is unavailable (since this should never normally be the case in + //general use in RT mode) + + RingBuffer *newbuf = inbuf->resized(blockSize); + delete inbuf; + inbuf = newbuf; + + // We don't want to preserve data in these arrays + + delete[] mag; + delete[] phase; + delete[] prevPhase; + delete[] unwrappedPhase; + delete[] freqPeak; + + mag = new double[realSize]; + phase = new double[realSize]; + prevPhase = new double[realSize]; + unwrappedPhase = new double[realSize]; + freqPeak = new size_t[realSize]; + + delete[] fltbuf; + delete[] dblbuf; + + fltbuf = new float[blockSize]; + dblbuf = new double[blockSize]; + + // But we do want to preserve data in these + + float *newAcc = new float[blockSize]; + for (size_t i = 0; i < oldSize; ++i) newAcc[i] = accumulator[i]; + delete[] accumulator; + accumulator = newAcc; + + newAcc = new float[blockSize]; + for (size_t i = 0; i < oldSize; ++i) newAcc[i] = windowAccumulator[i]; + delete[] windowAccumulator; + windowAccumulator = newAcc; + + //!!! and resampler? + + for (size_t i = 0; i < realSize; ++i) { + mag[i] = 0.0; + phase[i] = 0.0; + prevPhase[i] = 0.0; + unwrappedPhase[i] = 0.0; + freqPeak[i] = 0; + } + + for (size_t i = 0; i < blockSize; ++i) { + dblbuf[i] = 0.0; + fltbuf[i] = 0.0; + } + + for (size_t i = oldSize; i < blockSize; ++i) { + accumulator[i] = 0.f; + windowAccumulator[i] = 0.f; + } + + if (ffts.find(blockSize) == ffts.end()) { + ffts[blockSize] = new FFT(blockSize); + ffts[blockSize]->initDouble(); + } + + fft = ffts[blockSize]; +} + +void +RubberBandStretcher::Impl::ChannelData::setOutbufSize(size_t outbufSize) +{ + size_t oldSize = outbuf->getSize(); + + std::cerr << "ChannelData::setOutbufSize(" << outbufSize << ") [from " << oldSize << "]" << std::endl; + + if (oldSize < outbufSize) { + + //!!! at this point we need a lock in case a different client + //thread is calling process() + + //!!! this doesn't do what we want -- we want a locking resize + //that preserves the existing data +// outbuf->resize(outbufSize); + + RingBuffer *newbuf = outbuf->resized(outbufSize); + delete outbuf; + outbuf = newbuf; + } +} + +RubberBandStretcher::Impl::ChannelData::~ChannelData() +{ + delete resampler; + delete[] resamplebuf; + + delete inbuf; + delete outbuf; + delete[] mag; + delete[] phase; + delete[] prevPhase; + delete[] unwrappedPhase; + delete[] freqPeak; + delete[] accumulator; + delete[] windowAccumulator; + delete[] fltbuf; + delete[] dblbuf; + + for (std::map::iterator i = ffts.begin(); + i != ffts.end(); ++i) { + delete i->second; + } +} + +void +RubberBandStretcher::Impl::ChannelData::reset() +{ + inbuf->reset(); + outbuf->reset(); + + if (resampler) resampler->reset(); + + accumulatorFill = 0; + prevIncrement = 0; + blockCount = 0; + inCount = 0; + inputSize = -1; + outCount = 0; + draining = false; + outputComplete = false; +} + +} diff --git a/src/StretcherChannelData.h b/src/StretcherChannelData.h new file mode 100644 index 0000000..a0d7c72 --- /dev/null +++ b/src/StretcherChannelData.h @@ -0,0 +1,121 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_STRETCHERCHANNELDATA_H_ +#define _RUBBERBAND_STRETCHERCHANNELDATA_H_ + +#include "StretcherImpl.h" + +#include + +namespace RubberBand +{ + +class Resampler; + +class RubberBandStretcher::Impl::ChannelData +{ +public: + /** + * Construct a ChannelData structure. + * + * The block size passed in here is the size for the FFT + * calculation, and most of the buffer sizes also depend on + * it. In practice it is always a power of two and except for + * very extreme stretches is always either 1024, 2048 or 4096. + * + * The outbuf size depends on other factors as well, including + * the pitch scale factor and any maximum processing block + * size specified by the user of the code. + */ + ChannelData(size_t blockSize, size_t outbufSize); + + /** + * Construct a ChannelData structure that can process at + * different FFT sizes without requiring reallocation when the + * size changes. The size can subsequently be changed with a + * call to setBlockSize. Reallocation will only be necessary + * if setBlockSize is called with a value not equal to one of + * those passed in to the constructor. + * + * The outbufSize should be the maximum possible outbufSize to + * avoid reallocation, which will happen if setOutbufSize is + * called subsequently. + */ + ChannelData(const std::set &blockSizes, + size_t initialBlockSize, size_t outbufSize); + ~ChannelData(); + + /** + * Reset buffers + */ + void reset(); + + /** + * Set the FFT and buffer sizes from the given processing + * block size. If this ChannelData was constructed with a set + * of block sizes and the given block size here was among + * them, no reallocation will be required. + */ + void setBlockSize(size_t blockSize); + + /** + * Set the outbufSize for the channel data. Reallocation will + * occur. + */ + void setOutbufSize(size_t outbufSize); + + RingBuffer *inbuf; + RingBuffer *outbuf; + + double *mag; + double *phase; + + double *prevPhase; + double *unwrappedPhase; + + size_t *freqPeak; + + float *accumulator; + size_t accumulatorFill; + float *windowAccumulator; + + float *fltbuf; + double *dblbuf; + + size_t prevIncrement; // only used in RT mode + + size_t blockCount; + size_t inCount; + long inputSize; // set only after known (when data ended); -1 previously + size_t outCount; + + bool draining; + bool outputComplete; + + FFT *fft; + std::map ffts; + + Resampler *resampler; + float *resamplebuf; + size_t resamplebufSize; + +private: + void construct(const std::set &blockSizes, + size_t initialBlockSize, size_t outbufSize); +}; + +} + +#endif diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp new file mode 100644 index 0000000..c386231 --- /dev/null +++ b/src/StretcherImpl.cpp @@ -0,0 +1,978 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "StretcherImpl.h" +#include "PercussiveAudioCurve.h" +#include "HighFrequencyAudioCurve.h" +#include "ConstantAudioCurve.h" +#include "StretchCalculator.h" +#include "StretcherChannelData.h" +#include "Resampler.h" + +#include +#include +#include +#include + +using std::cerr; +using std::endl; +using std::vector; +using std::map; +using std::set; +using std::max; +using std::min; + +namespace RubberBand { + +static const size_t defaultIncrement = 256; +static const size_t defaultBlockSize = 2048; + +RubberBandStretcher::Impl::Impl(RubberBandStretcher *stretcher, + size_t sampleRate, + size_t channels, + Options options, + double initialTimeRatio, + double initialPitchScale) : + m_stretcher(stretcher), + m_channels(channels), + m_timeRatio(initialTimeRatio), + m_pitchScale(initialPitchScale), + m_blockSize(defaultBlockSize), + m_outbufSize(defaultBlockSize * 2), + m_increment(defaultIncrement), + m_maxProcessBlockSize(defaultBlockSize), + m_expectedInputDuration(0), + m_threaded(false), + m_realtime(false), + m_options(options), + m_debugLevel(1), + m_mode(JustCreated), + m_window(0), + m_studyFFT(0), + m_inputDuration(0), + m_lastProcessOutputIncrements(16), + m_lastProcessLockDf(16), + m_lockAudioCurve(0), + m_stretchAudioCurve(0), + m_stretchCalculator(0), + m_freq0(600), + m_freq1(1200), + m_freq2(12000), + m_baseBlockSize(defaultBlockSize) +{ + cerr << "RubberBandStretcher::Impl::Impl: options = " << options << endl; + + if ((options & OptionWindowShort) || (options & OptionWindowLong)) { + if ((options & OptionWindowShort) && (options & OptionWindowLong)) { + cerr << "RubberBandStretcher::Impl::Impl: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard" << endl; + } else if (options & OptionWindowShort) { + m_baseBlockSize = defaultBlockSize / 2; + cerr << "setting baseBlockSize to " << m_baseBlockSize << endl; + } else if (options & OptionWindowLong) { + m_baseBlockSize = defaultBlockSize * 2; + cerr << "setting baseBlockSize to " << m_baseBlockSize << endl; + } + m_blockSize = m_baseBlockSize; + m_outbufSize = m_baseBlockSize * 2; + m_maxProcessBlockSize = m_baseBlockSize; + } + + if (m_options & OptionProcessRealTime) { + + m_realtime = true; + + if (!(m_options & OptionStretchPrecise)) { + cerr << "RubberBandStretcher::Impl::Impl: Real-time mode: enabling OptionStretchPrecise" << endl; + m_options |= OptionStretchPrecise; + } + } + + if (m_channels > 1) { + + if (!m_realtime && + !(m_options & OptionThreadingNone) && + Thread::threadingAvailable() && + system_is_multiprocessor()) { + + m_threaded = true; + + if (m_debugLevel > 0) { + cerr << "Going multithreaded..." << endl; + } + } + } + + configure(); +} + +RubberBandStretcher::Impl::~Impl() +{ + if (m_threaded) { + MutexLocker locker(&m_threadSetMutex); + for (set::iterator i = m_threadSet.begin(); + i != m_threadSet.end(); ++i) { + if (m_debugLevel > 0) { + cerr << "RubberBandStretcher::~RubberBandStretcher: joining (channel " << *i << ")" << endl; + } + (*i)->wait(); + delete *i; + } + } + + for (size_t c = 0; c < m_channels; ++c) { + delete m_channelData[c]; + } + + delete m_lockAudioCurve; + delete m_stretchAudioCurve; + delete m_stretchCalculator; + delete m_studyFFT; + + for (map *>::iterator i = m_windows.begin(); + i != m_windows.end(); ++i) { + delete i->second; + } +} + +void +RubberBandStretcher::Impl::reset() +{ + //!!! does not do the right thing in threaded mode + + if (m_threaded) m_threadSetMutex.lock(); + + for (size_t c = 0; c < m_channels; ++c) { + delete m_channelData[c]; + m_channelData[c] = new ChannelData(m_blockSize, m_outbufSize); + } + m_mode = JustCreated; + if (m_lockAudioCurve) m_lockAudioCurve->reset(); + if (m_stretchAudioCurve) m_stretchAudioCurve->reset(); + m_inputDuration = 0; + + if (m_threaded) m_threadSetMutex.unlock(); +// m_done = false; +} + +void +RubberBandStretcher::Impl::setTimeRatio(double ratio) +{ + if (!m_realtime) { + if (m_mode == Studying || m_mode == Processing) { + cerr << "RubberBandStretcher::Impl::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode" << endl; + return; + } + } + + if (ratio == m_timeRatio) return; + m_timeRatio = ratio; + + reconfigure(); +} + +void +RubberBandStretcher::Impl::setPitchScale(double fs) +{ + if (!m_realtime) { + if (m_mode == Studying || m_mode == Processing) { + cerr << "RubberBandStretcher::Impl::setPitchScale: Cannot set ratio while studying or processing in non-RT mode" << endl; + return; + } + } + + if (fs == m_pitchScale) return; + m_pitchScale = fs; + + reconfigure(); +} + +double +RubberBandStretcher::Impl::getTimeRatio() const +{ + return m_timeRatio; +} + +double +RubberBandStretcher::Impl::getPitchScale() const +{ + return m_pitchScale; +} + +void +RubberBandStretcher::Impl::setExpectedInputDuration(size_t samples) +{ + if (samples == m_expectedInputDuration) return; + m_expectedInputDuration = samples; + + reconfigure(); +} + +void +RubberBandStretcher::Impl::setMaxProcessBlockSize(size_t samples) +{ + if (samples <= m_maxProcessBlockSize) return; + m_maxProcessBlockSize = samples; + + reconfigure(); +} + +float +RubberBandStretcher::Impl::getFrequencyCutoff(int n) const +{ + switch (n) { + case 0: return m_freq0; + case 1: return m_freq1; + case 2: return m_freq2; + } + return 0.f; +} + +void +RubberBandStretcher::Impl::setFrequencyCutoff(int n, float f) +{ + switch (n) { + case 0: m_freq0 = f; break; + case 1: m_freq1 = f; break; + case 2: m_freq2 = f; break; + } +} + +double +RubberBandStretcher::Impl::getEffectiveRatio() const +{ + // Returns the ratio that the internal time stretcher needs to + // achieve, not the resulting duration ratio of the output (which + // is simply m_timeRatio). + + // A frequency shift is achieved using an additional time shift, + // followed by resampling back to the original time shift to + // change the pitch. Note that the resulting frequency change is + // fixed, as it is effected by the resampler -- in contrast to + // time shifting, which is variable aiming to place the majority + // of the stretch or squash in low-interest regions of audio. + + return m_timeRatio * m_pitchScale; +} + +size_t +RubberBandStretcher::Impl::roundUp(size_t value) +{ + if (!(value & (value - 1))) return value; + int bits = 0; + while (value) { ++bits; value >>= 1; } + value = 1 << bits; + return value; +} + +void +RubberBandStretcher::Impl::calculateSizes() +{ + size_t inputIncrement = defaultIncrement; + size_t blockSize = m_baseBlockSize; + size_t outputIncrement; + + double r = getEffectiveRatio(); + + if (m_realtime) { + + // use a fixed input increment + + inputIncrement = defaultIncrement; + + if (r < 1) { + outputIncrement = int(floor(inputIncrement * r)); + if (outputIncrement < 1) { + outputIncrement = 1; + inputIncrement = roundUp(lrint(ceil(outputIncrement / r))); + blockSize = inputIncrement * 4; + } + } else { + outputIncrement = int(ceil(inputIncrement * r)); + while (outputIncrement > 1024 && inputIncrement > 1) { + inputIncrement /= 2; + outputIncrement = lrint(ceil(inputIncrement * r)); + } + blockSize = std::max(blockSize, roundUp(outputIncrement * 4.5)); + } + + } else { + + // use a variable increment + + if (r < 1) { + inputIncrement = blockSize / 4; + while (inputIncrement >= 512) inputIncrement /= 2; + outputIncrement = int(floor(inputIncrement * r)); + if (outputIncrement < 1) { + outputIncrement = 1; + inputIncrement = roundUp(lrint(ceil(outputIncrement / r))); + blockSize = inputIncrement * 4; + } + } else { + outputIncrement = blockSize / 6; + inputIncrement = int(outputIncrement / r); + while (outputIncrement > 1024 && inputIncrement > 1) { + outputIncrement /= 2; + inputIncrement = int(outputIncrement / r); + } + blockSize = std::max(blockSize, roundUp(outputIncrement * 6)); + } + } + + if (m_expectedInputDuration > 0) { + while (inputIncrement * 4 > m_expectedInputDuration && + inputIncrement > 1) { + inputIncrement /= 2; + } + } + + // blockSize can be almost anything, but it can't be greater than + // 4 * defaultBlockSize unless ratio is less than 1/1024. + + m_blockSize = blockSize; + m_increment = inputIncrement; + + // When squashing, the greatest theoretically possible output + // increment is the input increment. When stretching adaptively + // the sky's the limit in principle, but we expect + // StretchCalculator to restrict itself to using no more than + // twice the basic output increment (i.e. input increment times + // ratio) for any block. + + if (m_debugLevel > 0) { + cerr << "configure: effective ratio = " << getEffectiveRatio() << endl; + cerr << "configure: block size = " << m_blockSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl; + } + + static size_t maxBlockSize = 0; + + if (m_blockSize > maxBlockSize) { + //!!! + cerr << "configure: NOTE: max block size so far increased from " + << maxBlockSize << " to " << m_blockSize << endl; + maxBlockSize = m_blockSize; + } + + if (m_blockSize > m_maxProcessBlockSize) { + m_maxProcessBlockSize = m_blockSize; + } + + m_outbufSize = max + (size_t(ceil(m_maxProcessBlockSize / m_pitchScale)), + m_blockSize); + + if (m_realtime) { + // This headroom is so as to try to avoid reallocation when + // the pitch scale changes + m_outbufSize = m_outbufSize * 16; + } else { + if (m_threaded) { + // This headroom is to permit the processing threads to + // run ahead of the buffer output drainage; the exact + // amount of headroom is a question of tuning rather than + // results + m_outbufSize = m_outbufSize * 16; + } + } + + //!!! for very long stretches (e.g. x5), this is necessary; for + //even longer ones (e.g. x10), even more of an outbuf is + //necessary. clearly something wrong in our calculations... or do + //we just need to ensure client calls setMaxProcessBlockSize? + if (!m_realtime && !m_threaded) { +//!!! m_outbufSize = m_outbufSize * 2; + } +} + +void +RubberBandStretcher::Impl::configure() +{ + size_t prevBlockSize = m_blockSize; + size_t prevOutbufSize = m_outbufSize; + if (m_windows.empty()) { + prevBlockSize = 0; + prevOutbufSize = 0; + } + + calculateSizes(); + + bool blockSizeChanged = (prevBlockSize != m_blockSize); + bool outbufSizeChanged = (prevOutbufSize != m_outbufSize); + + // This function may be called at any time in non-RT mode, after a + // parameter has changed. It shouldn't be legal to call it after + // processing has already begun. + + // This function is only called once (on construction) in RT + // mode. After that reconfigure() does the work in a hopefully + // RT-safe way. + + set blockSizes; + if (m_realtime) { + blockSizes.insert(m_baseBlockSize); + blockSizes.insert(m_baseBlockSize * 2); + blockSizes.insert(m_baseBlockSize * 4); + } + blockSizes.insert(m_blockSize); + + if (blockSizeChanged) { + + for (set::const_iterator i = blockSizes.begin(); + i != blockSizes.end(); ++i) { + if (m_windows.find(*i) == m_windows.end()) { + m_windows[*i] = new Window(HanningWindow, *i); + } + } + m_window = m_windows[m_blockSize]; + + if (m_debugLevel > 0) { + cerr << "Window area: " << m_window->getArea() << "; synthesis window area: " << m_window->getArea() << endl; + } + } + + if (blockSizeChanged || outbufSizeChanged) { + + for (size_t c = 0; c < m_channelData.size(); ++c) { + delete m_channelData[c]; + } + m_channelData.clear(); + + for (size_t c = 0; c < m_channels; ++c) { + m_channelData.push_back + (new ChannelData(blockSizes, m_blockSize, m_outbufSize)); + } + } + + if (!m_realtime && blockSizeChanged) { + delete m_studyFFT; + m_studyFFT = new FFT(m_blockSize); + m_studyFFT->initFloat(); + } + + if (m_pitchScale != 1.0 || m_realtime) { + + for (size_t c = 0; c < m_channels; ++c) { + + if (m_channelData[c]->resampler) continue; + + m_channelData[c]->resampler = + new Resampler(Resampler::FastestTolerable, 1, 4096 * 16); + + m_channelData[c]->resamplebufSize = + lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale)); + m_channelData[c]->resamplebuf = + new float[m_channelData[c]->resamplebufSize]; + } + } + + delete m_lockAudioCurve; + m_lockAudioCurve = new PercussiveAudioCurve(m_stretcher->m_sampleRate, + m_blockSize); + + // stretchAudioCurve unused in RT mode; lockAudioCurve and + // stretchCalculator however are used in all modes + + if (!m_realtime) { + delete m_stretchAudioCurve; + if (!(m_options & OptionStretchPrecise)) { + //!!! probably adaptively-whitened spectral difference curve + //would be better + m_stretchAudioCurve = new HighFrequencyAudioCurve + (m_stretcher->m_sampleRate, m_blockSize); + } else { + m_stretchAudioCurve = new ConstantAudioCurve + (m_stretcher->m_sampleRate, m_blockSize); + } + } + + delete m_stretchCalculator; + m_stretchCalculator = new StretchCalculator + (m_stretcher->m_sampleRate, m_increment, + !(m_options & OptionTransientsSmooth)); + + m_stretchCalculator->setDebugLevel(m_debugLevel); + m_inputDuration = 0; + + // Prepare the inbufs with half a block of emptiness. The centre + // point of the first processing block for the onset detector + // should be the first sample of the audio, and we continue until + // we can no longer centre a block within the input audio. The + // number of onset detector blocks will be the number of audio + // samples input, divided by the input increment, plus one. + + // In real-time mode, we don't do this prefill -- it's better to + // start with a swoosh than introduce more latency, and we don't + // want gaps when the ratio changes. + + if (!m_realtime) { + for (size_t c = 0; c < m_channels; ++c) { + m_channelData[c]->reset(); + m_channelData[c]->inbuf->zero(m_blockSize/2); + } + } +} + + +//!!! separated out from configure() for the moment so we can look at +// the logic of it +void +RubberBandStretcher::Impl::reconfigure() +{ + if (!m_realtime) { + if (m_mode == Studying) { + // stop and calculate the stretch curve so far, then reset + // the df vectors + calculateStretch(); + m_lockDf.clear(); + m_stretchDf.clear(); + m_inputDuration = 0; + } + configure(); + } + + size_t prevBlockSize = m_blockSize; + size_t prevOutbufSize = m_outbufSize; + + calculateSizes(); + + // There are various allocations in this function, but they should + // never happen in normal use -- they just recover from the case + // where not all of the things we need were correctly created when + // we first configured (for whatever reason). This is intended to + // be "effectively" realtime safe. The same goes for + // ChannelData::setOutbufSize and setBlockSize. + + if (m_blockSize != prevBlockSize) { + + if (m_windows.find(m_blockSize) == m_windows.end()) { + std::cerr << "WARNING: reconfigure(): window allocation (size " << m_blockSize << ") required in RT mode" << std::endl; + m_windows[m_blockSize] = new Window(HanningWindow, m_blockSize); + } + m_window = m_windows[m_blockSize]; + + for (size_t c = 0; c < m_channels; ++c) { + m_channelData[c]->setBlockSize(m_blockSize); + } + } + + if (m_outbufSize != prevOutbufSize) { + for (size_t c = 0; c < m_channels; ++c) { + m_channelData[c]->setOutbufSize(m_outbufSize); + } + } + + if (m_pitchScale != 1.0) { + for (size_t c = 0; c < m_channels; ++c) { + + if (m_channelData[c]->resampler) continue; + + std::cerr << "WARNING: reconfigure(): resampler construction required in RT mode" << std::endl; + + m_channelData[c]->resampler = + new Resampler(Resampler::FastestTolerable, 1, m_blockSize); + + m_channelData[c]->resamplebufSize = + lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale)); + m_channelData[c]->resamplebuf = + new float[m_channelData[c]->resamplebufSize]; + } + } + + if (m_blockSize != prevBlockSize) { + m_lockAudioCurve->setBlockSize(m_blockSize); + } +} + +size_t +RubberBandStretcher::Impl::getLatency() const +{ + if (!m_realtime) return 0; + return int((m_blockSize/2) / m_pitchScale + 1); +} + +void +RubberBandStretcher::Impl::setTransientsOption(Options options) +{ + if (options & OptionTransientsSmooth) { + m_options |= OptionTransientsSmooth; + } else { + m_options &= ~OptionTransientsSmooth; + } +} + +void +RubberBandStretcher::Impl::setPhaseOption(Options options) +{ + if (options & OptionPhaseIndependent) { + m_options |= OptionPhaseIndependent; + } else { + m_options &= ~OptionPhaseIndependent; + } +} + +void +RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool final) +{ + if (m_realtime) { + if (m_debugLevel > 1) { + cerr << "RubberBandStretcher::Impl::study: Not meaningful in realtime mode" << endl; + } + return; + } + + if (m_mode == Processing || m_mode == Finished) { + cerr << "RubberBandStretcher::Impl::study: Cannot study after processing" << endl; + return; + } + m_mode = Studying; + + size_t consumed = 0; + + ChannelData &cd = *m_channelData[0]; + RingBuffer &inbuf = *cd.inbuf; + + const float *mixdown; + float *mdalloc = 0; + + if (m_channels > 1 || final) { + // mix down into a single channel for analysis + mdalloc = new float[samples]; + for (size_t i = 0; i < samples; ++i) { + if (i < samples) { + mdalloc[i] = input[0][i]; + } else { + mdalloc[i] = 0.f; + } + } + for (size_t c = 1; c < m_channels; ++c) { + for (size_t i = 0; i < samples; ++i) { + mdalloc[i] += input[c][i]; + } + } + for (size_t i = 0; i < samples; ++i) { + mdalloc[i] /= m_channels; + } + mixdown = mdalloc; + } else { + mixdown = input[0]; + } + + while (consumed < samples) { + + size_t writable = inbuf.getWriteSpace(); + writable = min(writable, samples - consumed); + + if (writable == 0) { + // warn + cerr << "WARNING: writable == 0 (consumed = " << consumed << ", samples = " << samples << ")" << endl; + } else { + inbuf.write(mixdown + consumed, writable); + consumed += writable; + } + + while ((inbuf.getReadSpace() >= m_blockSize) || + (final && (inbuf.getReadSpace() >= m_blockSize/2))) { + + //!!! inconsistency throughout -- we are using "blocksize", + // but "chunk" instead of "block" + + // We know we have at least m_blockSize samples available + // in m_inbuf. We need to peek m_blockSize of them for + // processing, and then skip m_increment to advance the + // read pointer. + + // cd.accumulator is not otherwise used during studying, + // so we can use it as a temporary buffer here + + size_t got = inbuf.peek(cd.accumulator, m_blockSize); + assert(final || got == m_blockSize); + + m_window->cut(cd.accumulator); + + // We don't need the fftshift for studying, as we're only + // interested in magnitude + + m_studyFFT->forwardMagnitude(cd.accumulator, cd.fltbuf); + + float df = m_lockAudioCurve->process(cd.fltbuf, m_increment); + m_lockDf.push_back(df); + +// cout << m_lockDf.size() << " [" << final << "] -> " << df << " \t: "; + + df = m_stretchAudioCurve->process(cd.fltbuf, m_increment); + m_stretchDf.push_back(df); + +// cout << df << endl; + + // We have augmented the input by m_blockSize/2 so + // that the first block is centred on the first audio + // sample. We want to ensure that m_inputDuration + // contains the exact input duration without including + // this extra bit. We just add up all the increments + // here, and deduct the extra afterwards. + + m_inputDuration += m_increment; +// cerr << "incr input duration by increment: " << m_increment << " -> " << m_inputDuration << endl; + inbuf.skip(m_increment); + } + } + + if (final) { + int rs = inbuf.getReadSpace(); + m_inputDuration += rs; +// cerr << "incr input duration by read space: " << rs << " -> " << m_inputDuration << endl; + + if (m_inputDuration > m_blockSize/2) { // deducting the extra + m_inputDuration -= m_blockSize/2; + } + } + + if (m_channels > 1) delete[] mdalloc; +} + +vector +RubberBandStretcher::Impl::getOutputIncrements() const +{ + if (!m_realtime) { + return m_outputIncrements; + } else { + vector increments; + while (m_lastProcessOutputIncrements.getReadSpace() > 0) { + increments.push_back(m_lastProcessOutputIncrements.readOne()); + } + return increments; + } +} + +vector +RubberBandStretcher::Impl::getLockCurve() const +{ + if (!m_realtime) { + return m_lockDf; + } else { + vector df; + while (m_lastProcessLockDf.getReadSpace() > 0) { + df.push_back(m_lastProcessLockDf.readOne()); + } + return df; + } +} + +void +RubberBandStretcher::Impl::calculateStretch() +{ + std::vector increments = m_stretchCalculator->calculate + (getEffectiveRatio(), + m_inputDuration, + m_lockDf, + m_stretchDf); + + if (m_outputIncrements.empty()) m_outputIncrements = increments; + else { + for (size_t i = 0; i < increments.size(); ++i) { + m_outputIncrements.push_back(increments[i]); + } + } + + return; +} + +void +RubberBandStretcher::Impl::setDebugLevel(int level) +{ + m_debugLevel = level; + if (m_stretchCalculator) m_stretchCalculator->setDebugLevel(level); +} + +size_t +RubberBandStretcher::Impl::getSamplesRequired() const +{ + size_t reqd = 0; + + for (size_t c = 0; c < m_channels; ++c) { + + size_t reqdHere = 0; + + ChannelData &cd = *m_channelData[c]; + RingBuffer &inbuf = *cd.inbuf; + + size_t rs = inbuf.getReadSpace(); + + // See notes in testInbufReadSpace below + + if (rs < m_blockSize && !cd.draining) { + + if (cd.inputSize == -1) { + reqdHere = m_blockSize - rs; + if (reqdHere > reqd) reqd = reqdHere; + continue; + } + + if (rs == 0) { + reqdHere = m_blockSize; + if (reqdHere > reqd) reqd = reqdHere; + continue; + } + } + } + + return reqd; +} + +void +RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bool final) +{ + if (m_mode == Finished) { + cerr << "RubberBandStretcher::Impl::process: Cannot process again after final block" << endl; + return; + } + + if (m_mode == JustCreated || m_mode == Studying) { + + //!!! m_studying isn't the right test for "is this the first + //time we've processed?" + + if (m_mode == Studying) { + calculateStretch(); + } + + for (size_t c = 0; c < m_channels; ++c) { + m_channelData[c]->reset(); + m_channelData[c]->inbuf->zero(m_blockSize/2); + } + + if (m_threaded) { + MutexLocker locker(&m_threadSetMutex); + + for (size_t c = 0; c < m_channels; ++c) { + ProcessThread *thread = new ProcessThread(this, c); + m_threadSet.insert(thread); + thread->start(); + } + + if (m_debugLevel > 0) { + cerr << m_channels << " threads created" << endl; + } + } + + m_mode = Processing; + } + + bool allConsumed = false; + + map consumed; + for (size_t c = 0; c < m_channels; ++c) { + consumed[c] = 0; + } + + while (!allConsumed) { + +// cerr << "process looping" << endl; + +//#ifndef NO_THREADING +// if (m_threaded) { +// pthread_mutex_lock(&m_inputProcessedMutex); +// } +//#endif + + // In a threaded mode, our "consumed" counters only indicate + // the number of samples that have been taken into the input + // ring buffers waiting to be processed by the process thread. + // In non-threaded mode, "consumed" counts the number that + // have actually been processed. + + allConsumed = true; + for (size_t c = 0; c < m_channels; ++c) { + consumed[c] += consumeChannel(c, + input[c] + consumed[c], + samples - consumed[c]); + if (consumed[c] < samples) { + allConsumed = false; +// cerr << "process: waiting on input consumption for channel " << c << endl; + } else { + if (final) { + m_channelData[c]->inputSize = m_channelData[c]->inCount; + } +// cerr << "process: happy with channel " << c << endl; + } + if (!m_threaded && !m_realtime) { + processChunks(c); + } + } + + if (m_realtime) { + // When running in real time, we need to process both + // channels in step because we will need to use the sum of + // their frequency domain representations as the input to + // the realtime onset detector + processOneChunk(); + } + + if (m_threaded) { + m_dataAvailable.signal(); + m_spaceAvailable.lock(); + if (!allConsumed) { + m_spaceAvailable.wait(500); + } +/* + } else { + if (!allConsumed) { + cerr << "RubberBandStretcher::Impl::process: ERROR: Too much data provided to process() call -- either call setMaxProcessBlockSize() beforehand, or provide only getSamplesRequired() frames at a time" << endl; + for (size_t c = 0; c < m_channels; ++c) { + cerr << "channel " << c << ": " << samples << " provided, " << consumed[c] << " consumed" << endl; + } +// break; + } +*/ + } + } + +// cerr << "process returning" << endl; + + if (final) m_mode = Finished; +} + +size_t +RubberBandStretcher::Impl::consumeChannel(size_t c, const float *input, size_t samples) +{ + size_t consumed = 0; + + ChannelData &cd = *m_channelData[c]; + RingBuffer &inbuf = *cd.inbuf; + + while (consumed < samples) { + + size_t writable = inbuf.getWriteSpace(); + +// cerr << "channel " << c << ": samples remaining = " << samples - consumed << ", writable space = " << writable << endl; + + writable = min(writable, samples - consumed); + + if (writable == 0) { + // warn +// cerr << "WARNING: writable == 0 for ch " << c << " (consumed = " << consumed << ", samples = " << samples << ")" << endl; + return consumed; + } else { + inbuf.write(input + consumed, writable); + consumed += writable; + cd.inCount += writable; + } + } + + return samples; +} + + +} + diff --git a/src/StretcherImpl.h b/src/StretcherImpl.h new file mode 100644 index 0000000..64f7b3a --- /dev/null +++ b/src/StretcherImpl.h @@ -0,0 +1,184 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_STRETCHERIMPL_H_ +#define _RUBBERBAND_STRETCHERIMPL_H_ + +#include "RubberBandStretcher.h" + +#include "Window.h" +#include "Thread.h" +#include "RingBuffer.h" +#include "FFT.h" +#include "sysutils.h" + +#include + +namespace RubberBand +{ + +class AudioCurve; +class StretchCalculator; + +class RubberBandStretcher::Impl +{ +public: + Impl(RubberBandStretcher *stretcher, + size_t sampleRate, size_t channels, Options options, + double initialTimeRatio, double initialPitchScale); + ~Impl(); + + void reset(); + void setTimeRatio(double ratio); + void setPitchScale(double scale); + + double getTimeRatio() const; + double getPitchScale() const; + + size_t getLatency() const; + + void setTransientsOption(Options); + void setPhaseOption(Options); + + void setExpectedInputDuration(size_t samples); + void setMaxProcessBlockSize(size_t samples); + + size_t getSamplesRequired() const; + + void study(const float *const *input, size_t samples, bool final); + void process(const float *const *input, size_t samples, bool final); + + int available() const; + size_t retrieve(float *const *output, size_t samples) const; + + float getFrequencyCutoff(int n) const; + void setFrequencyCutoff(int n, float f); + + size_t getInputIncrement() const { + return m_increment; + } + + std::vector getOutputIncrements() const; + std::vector getLockCurve() const; + + size_t getChannelCount() const { + return m_channels; + } + + void calculateStretch(); + + void setDebugLevel(int level); + +protected: + RubberBandStretcher *m_stretcher; + size_t m_channels; + + size_t consumeChannel(size_t channel, const float *input, size_t samples); + bool processChunks(size_t channel); // returns "last" + bool processOneChunk(); // across all channels, for real time use + bool processChunkForChannel(size_t channel, size_t phaseIncrement, + size_t shiftIncrement, bool lock); + bool testInbufReadSpace(size_t channel); + void calculateIncrements(size_t &phaseIncrement, + size_t &shiftIncrement, bool &lock); + bool getIncrements(size_t channel, size_t &phaseIncrement, + size_t &shiftIncrement, bool &lock); + void analyseChunk(size_t channel); + void modifyChunk(size_t channel, size_t outputIncrement, bool lock); + void synthesiseChunk(size_t channel); + void writeChunk(size_t channel, size_t shiftIncrement, bool last); + + void calculateSizes(); + void configure(); + void reconfigure(); + + double getEffectiveRatio() const; + + size_t roundUp(size_t value); // to next power of two + + double m_timeRatio; + double m_pitchScale; + + size_t m_blockSize; + size_t m_outbufSize; + size_t m_increment; + + size_t m_maxProcessBlockSize; + size_t m_expectedInputDuration; + + bool m_threaded; + bool m_realtime; + Options m_options; + int m_debugLevel; + + enum ProcessMode { + JustCreated, + Studying, + Processing, + Finished + }; + + ProcessMode m_mode; + + std::map *> m_windows; + Window *m_window; + FFT *m_studyFFT; + + Condition m_dataAvailable; + Condition m_spaceAvailable; + + class ProcessThread : public Thread + { + public: + ProcessThread(Impl *s, size_t c) : m_s(s), m_channel(c) { } + void run(); + private: + Impl *m_s; + size_t m_channel; + }; + + mutable Mutex m_threadSetMutex; + std::set m_threadSet; + + + size_t m_inputDuration; + std::vector m_lockDf; + std::vector m_stretchDf; + + class ChannelData; + std::vector m_channelData; + + std::vector m_outputIncrements; + + mutable RingBuffer m_lastProcessOutputIncrements; + mutable RingBuffer m_lastProcessLockDf; + + AudioCurve *m_lockAudioCurve; + AudioCurve *m_stretchAudioCurve; + StretchCalculator *m_stretchCalculator; + + float m_freq0; + float m_freq1; + float m_freq2; + + size_t m_baseBlockSize; + + void writeOutput(RingBuffer &to, float *from, + size_t qty, size_t &outCount, size_t theoreticalOut); + +}; + +} + +#endif diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp new file mode 100644 index 0000000..076f992 --- /dev/null +++ b/src/StretcherProcess.cpp @@ -0,0 +1,853 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "StretcherImpl.h" +#include "PercussiveAudioCurve.h" +#include "HighFrequencyAudioCurve.h" +#include "ConstantAudioCurve.h" +#include "StretchCalculator.h" +#include "StretcherChannelData.h" +#include "Resampler.h" + +#include +#include +#include +#include + +using std::cerr; +using std::endl; + +namespace RubberBand { + +void +RubberBandStretcher::Impl::ProcessThread::run() +{ + if (m_s->m_debugLevel > 1) { + cerr << "thread " << m_channel << " getting going" << endl; + } + + ChannelData &cd = *m_s->m_channelData[m_channel]; + + while (cd.inputSize == -1 || + cd.inbuf->getReadSpace() > 0) { + + if (cd.inputSize != -1) { + cerr << "inputSize == " << cd.inputSize + << ", readSpace == " << cd.inbuf->getReadSpace() << endl; + } + + bool last = m_s->processChunks(m_channel); + + m_s->m_spaceAvailable.signal(); + + if (last) break; + + m_s->m_dataAvailable.lock(); + if (cd.inbuf->getReadSpace() == 0) { + m_s->m_dataAvailable.wait(); + } + } + + m_s->processChunks(m_channel); + m_s->m_spaceAvailable.signal(); + + if (m_s->m_debugLevel > 1) { + cerr << "thread " << m_channel << " done" << endl; + } +} + +bool +RubberBandStretcher::Impl::processChunks(size_t c) +{ + // Process as many chunks as there are available on the input + // buffer for channel c. This requires that the increments have + // already been calculated. + + ChannelData &cd = *m_channelData[c]; + + bool last = false; + + while (!last) { + + if (!testInbufReadSpace(c)) break; + + if (!cd.draining) { + size_t got = cd.inbuf->peek(cd.fltbuf, m_blockSize); + assert(got == m_blockSize || cd.inputSize >= 0); + cd.inbuf->skip(m_increment); + analyseChunk(c); + } + + bool lock = false; + size_t phaseIncrement, shiftIncrement; + getIncrements(c, phaseIncrement, shiftIncrement, lock); + + last = processChunkForChannel(c, phaseIncrement, shiftIncrement, lock); + cd.blockCount++; + if (m_debugLevel > 2) { + cerr << "channel " << c << ": last = " << last << ", blockCount = " << cd.blockCount << endl; + } + } + + return last; +} + +bool +RubberBandStretcher::Impl::processOneChunk() +{ + // Process a single chunk for all channels, provided there is + // enough data on each channel for at least one chunk. This is + // able to calculate increments as it goes along. + + for (size_t c = 0; c < m_channels; ++c) { + if (!testInbufReadSpace(c)) return false; + ChannelData &cd = *m_channelData[c]; + if (!cd.draining) { + size_t got = cd.inbuf->peek(cd.fltbuf, m_blockSize); + assert(got == m_blockSize || cd.inputSize >= 0); + cd.inbuf->skip(m_increment); + analyseChunk(c); + } + } + + bool lock = false; + size_t phaseIncrement, shiftIncrement; + if (!getIncrements(0, phaseIncrement, shiftIncrement, lock)) { + calculateIncrements(phaseIncrement, shiftIncrement, lock); + } + + bool last = false; + for (size_t c = 0; c < m_channels; ++c) { + last = processChunkForChannel(c, phaseIncrement, shiftIncrement, lock); + m_channelData[c]->blockCount++; + } + + return last; +} + +bool +RubberBandStretcher::Impl::testInbufReadSpace(size_t c) +{ + ChannelData &cd = *m_channelData[c]; + RingBuffer &inbuf = *cd.inbuf; + + size_t rs = inbuf.getReadSpace(); + + if (rs < m_blockSize && !cd.draining) { + + if (cd.inputSize == -1) { + + // Not all the input data has been written to the inbuf + // (that's why the input size is not yet set). We can't + // process, because we don't have a full block of data, so + // our process block would contain some empty padding in + // its input -- and that would give incorrect output, as + // we know there is more input to come. + + if (!m_threaded) { +// cerr << "WARNING: RubberBandStretcher: read space < block size (" +// << inbuf.getReadSpace() << " < " << m_blockSize +// << ") when not all input written, on processChunks for channel " << c << endl; + } + return false; + } + + if (rs == 0) { + + if (m_debugLevel > 1) { + cerr << "read space = 0, giving up" << endl; + } + return false; + + } else if (rs < m_blockSize/2) { + + if (m_debugLevel > 1) { + cerr << "read space = " << rs << ", setting draining true" << endl; + } + + cd.draining = true; + } + } + + return true; +} + +bool +RubberBandStretcher::Impl::processChunkForChannel(size_t c, + size_t phaseIncrement, + size_t shiftIncrement, + bool lock) +{ + // Process a single chunk on a single channel. This assumes + // enough input data is available; caller must have tested this + // using e.g. testInbufReadSpace first. Return true if this is + // the last chunk on the channel. + + if (lock && (m_debugLevel > 1)) { + cerr << "processChunkForChannel: lock found, incrs " + << phaseIncrement << ":" << shiftIncrement << endl; + } + + ChannelData &cd = *m_channelData[c]; + + if (!cd.draining) { + + // This is the normal processing case -- draining is only + // set when all the input has been used and we only need + // to write from the existing accumulator into the output. + + // We know we have enough samples available in m_inbuf -- + // this is usually m_blockSize, but we know that if fewer + // are available, it's OK to use zeroes for the rest + // (which the ring buffer will provide) because we've + // reached the true end of the data. + + // We need to peek m_blockSize samples for processing, and + // then skip m_increment to advance the read pointer. + + modifyChunk(c, phaseIncrement, lock); + synthesiseChunk(c); // reads from cd.mag, cd.phase + + if (m_debugLevel > 2) { + if (lock) { + for (int i = 0; i < 10; ++i) { + cd.accumulator[i] = ((drand48() * 2 - 1.0) > 0 ? 1 : -1); + } + } + } + } + + bool last = false; + + if (cd.draining) { + if (m_debugLevel > 1) { + cerr << "draining: accumulator fill = " << cd.accumulatorFill << " (shiftIncrement = " << shiftIncrement << ")" << endl; + } + if (shiftIncrement == 0) { + cerr << "WARNING: draining: shiftIncrement == 0, can't handle that in this context: setting to " << m_increment << endl; + shiftIncrement = m_increment; + } + if (cd.accumulatorFill <= shiftIncrement) { + if (m_debugLevel > 1) { + cerr << "reducing shift increment from " << shiftIncrement + << " to " << cd.accumulatorFill + << " and marking as last" << endl; + } + shiftIncrement = cd.accumulatorFill; + last = true; + } + } + + if (m_threaded) { + size_t required = shiftIncrement; + if (m_pitchScale != 1.0) { + required = int(required / m_pitchScale) + 1; + } + + if (cd.outbuf->getWriteSpace() < required) { + if (m_debugLevel > 0) { + cerr << "buffer overrun on output for channel " << c << endl; + } + + //!!! The only correct thing we can do here is resize the + // buffer. We can't wait for the client thread to read + // some data out from the buffer so as to make more space, + // because the client thread is probably stuck in a + // process() call waiting for us to stow away enough input + // increments to allow the process() call to complete. + + } + } + + writeChunk(c, shiftIncrement, last); + return last; +} + +void +RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, + size_t &shiftIncrementRtn, + bool &lock) +{ +// cerr << "calculateIncrements" << endl; + + // Calculate the next upcoming phase and shift increment, on the + // basis that both channels are in sync. This is in contrast to + // getIncrements, which requires that all the increments have been + // calculated in advance but can then return increments + // corresponding to different blocks in different channels. + + // Requires frequency domain representations of channel data in + // the mag and phase buffers in the channel. + + // This function is only used in real-time mode. + + phaseIncrementRtn = m_increment; + shiftIncrementRtn = m_increment; + lock = false; + + if (m_channels == 0) return; + + ChannelData &cd = *m_channelData[0]; + + size_t bc = cd.blockCount; + for (size_t c = 1; c < m_channels; ++c) { + if (m_channelData[c]->blockCount != bc) { + cerr << "ERROR: RubberBandStretcher::Impl::calculateIncrements: Channels are not in sync" << endl; + return; + } + } + + // Normally we would mix down the time-domain signal and apply a + // single FFT, or else mix down the Cartesian form of the + // frequency-domain signal. Both of those would be inefficient + // from this position. Fortunately, the onset detectors should + // work reasonably well (maybe even better?) if we just sum the + // magnitudes of the frequency-domain channel signals and forget + // about phase entirely. Normally we don't expect the channel + // phases to cancel each other, and broadband effects will still + // be apparent. + + for (size_t i = 0; i <= m_blockSize/2; ++i) { + cd.fltbuf[i] = 0.0; + } + + for (size_t c = 0; c < m_channels; ++c) { + for (size_t i = 0; i <= m_blockSize/2; ++i) { + cd.fltbuf[i] += m_channelData[c]->mag[i]; + } + } + + float df = m_lockAudioCurve->process(cd.fltbuf, m_increment); + + int incr = m_stretchCalculator->calculateSingle + (getEffectiveRatio(), + m_inputDuration, //!!! no, totally wrong... fortunately it doesn't matter atm + df); + + m_lastProcessLockDf.write(&df, 1); + m_lastProcessOutputIncrements.write(&incr, 1); + + if (incr < 0) { + lock = true; + incr = -incr; + } + + // The returned increment is the phase increment. The shift + // increment for one chunk is the same as the phase increment for + // the following chunk (see comment below). This means we don't + // actually know the shift increment until we see the following + // phase increment... which is a bit of a problem. + + // This implies we should use this increment for the shift + // increment, and make the following phase increment the same as + // it. This means in RT mode we'll be one block later with our + // phase lock than we would be in non-RT mode. The sensitivity of + // the broadband onset detector may mean that this isn't a problem + // -- test it and see. + + shiftIncrementRtn = incr; + + if (cd.prevIncrement == 0) { + phaseIncrementRtn = shiftIncrementRtn; + } else { + phaseIncrementRtn = cd.prevIncrement; + } + + cd.prevIncrement = shiftIncrementRtn; +} + +bool +RubberBandStretcher::Impl::getIncrements(size_t channel, + size_t &phaseIncrementRtn, + size_t &shiftIncrementRtn, + bool &lock) +{ + if (channel >= m_channels) { + phaseIncrementRtn = m_increment; + shiftIncrementRtn = m_increment; + lock = false; + return false; + } + + // There are two relevant output increments here. The first is + // the phase increment which we use when recalculating the phases + // for the current chunk; the second is the shift increment used + // to determine how far to shift the processing buffer after + // writing the chunk. The shift increment for one chunk is the + // same as the phase increment for the following chunk. + + // When an onset occurs for which we need to lock phases, the + // increment given will be negative. + + // When we lock phases, the previous shift increment (and so + // current phase increments) must have been m_increment to ensure + // consistency. + + // m_outputIncrements stores phase increments. + + ChannelData &cd = *m_channelData[channel]; + bool gotData = true; + + if (cd.blockCount >= m_outputIncrements.size()) { + //!!! this is an error if in non-realtime mode +// cerr << "*** ERROR: block count " << cd.blockCount << " >= " +// << m_outputIncrements.size() << endl; + if (m_outputIncrements.size() == 0) { + phaseIncrementRtn = m_increment; + shiftIncrementRtn = m_increment; + lock = false; + return false; + } else { + cd.blockCount = m_outputIncrements.size()-1; + gotData = false; + } + } + + int phaseIncrement = m_outputIncrements[cd.blockCount]; + + int shiftIncrement = phaseIncrement; + if (cd.blockCount + 1 < m_outputIncrements.size()) { + shiftIncrement = m_outputIncrements[cd.blockCount + 1]; + } + + if (phaseIncrement < 0) { + phaseIncrement = -phaseIncrement; + lock = true; + } + + if (shiftIncrement < 0) { + shiftIncrement = -shiftIncrement; + } + + if (shiftIncrement >= int(m_blockSize)) { + cerr << "*** ERROR: RubberBandStretcher::Impl::processChunks: shiftIncrement " << shiftIncrement << " >= blockSize " << m_blockSize << " at " << cd.blockCount << " (of " << m_outputIncrements.size() << ")" << endl; + shiftIncrement = m_blockSize; + } + + phaseIncrementRtn = phaseIncrement; + shiftIncrementRtn = shiftIncrement; + if (cd.blockCount == 0) lock = true; // don't mess with the first block + return gotData; +} + +void +RubberBandStretcher::Impl::analyseChunk(size_t channel) +{ + size_t i; + + ChannelData &cd = *m_channelData[channel]; + + // cd.fltbuf is known to contain m_blockSize samples + + m_window->cut(cd.fltbuf); + + for (i = 0; i < m_blockSize/2; ++i) { + cd.dblbuf[i] = cd.fltbuf[i + m_blockSize/2]; + cd.dblbuf[i + m_blockSize/2] = cd.fltbuf[i]; + } + + cd.fft->forwardPolar(cd.dblbuf, cd.mag, cd.phase); +} + +double mod(double x, double y) { return x - (y * floor(x / y)); } +double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; } + +void +RubberBandStretcher::Impl::modifyChunk(size_t channel, size_t outputIncrement, + bool lock) +{ + ChannelData &cd = *m_channelData[channel]; + + if (lock && m_debugLevel > 1) { + cerr << "lock: leaving phases unmodified" << endl; + } + + size_t count = m_blockSize/2; + size_t pfp = 0; + double rate = m_stretcher->m_sampleRate; + + if (!(m_options & OptionPhaseIndependent)) { + + cd.freqPeak[0] = 0; + + size_t limit0 = lrint((m_freq0 * m_blockSize) / rate); + size_t limit1 = lrint((m_freq1 * m_blockSize) / rate); + size_t limit2 = lrint((m_freq2 * m_blockSize) / rate); + size_t range = 0; + + if (limit1 < limit0) limit1 = limit0; + if (limit2 < limit1) limit2 = limit1; + +// cerr << "limit0 = " << limit0 << " limit1 = " << limit1 << " limit2 = " << limit2 << endl; + + int peakCount = 0; + + for (size_t i = 0; i <= count; ++i) { + + double mag = cd.mag[i]; + + //!!! N.B. if the stretch ratio is very long, it's generally + //better not to attempt this phase lamination -- stick with + //range==0 throughout. + + bool isPeak = true; + + for (size_t j = 1; j <= range; ++j) { + + if (mag < cd.mag[i-j]) { + isPeak = false; + break; + } + + if (mag < cd.mag[i+j]) { + isPeak = false; + break; + } + } + + if (isPeak) { + + // i is a peak bin. + + // The previous peak bin was at pfp; make freqPeak entries + // from pfp to half-way between pfp and i point at pfp, and + // those from the half-way mark to i point at i. + + size_t halfway = (pfp + i) / 2; + if (halfway == pfp) halfway = pfp + 1; + + for (size_t j = pfp + 1; j < halfway; ++j) { + cd.freqPeak[j] = pfp; + } + for (size_t j = halfway; j <= i; ++j) { + cd.freqPeak[j] = i; + } + + pfp = i; + + ++peakCount; + } + + if (i == limit0) range = 1; + if (i == limit1) range = 2; + if (i >= limit2) { + range = 3; + if (i + range + 1 > count) range = count - i; + } + } + +// cerr << "peakCount = " << peakCount << endl; + + cd.freqPeak[count-1] = count-1; + cd.freqPeak[count] = count; + } + + double peakInPhase = 0.0; + double peakOutPhase = 0.0; + size_t p, pp; + + for (size_t i = 0; i <= count; ++i) { + + if (m_options & OptionPhaseIndependent) { + p = i; + pp = i-1; + } else { + p = cd.freqPeak[i]; + pp = cd.freqPeak[i-1]; + } + + if (!lock) { + + if (i == 0 || p != pp) { + + double omega = (2 * M_PI * m_increment * p) / m_blockSize; + double expectedPhase = cd.prevPhase[p] + omega; + double phaseError = princarg(cd.phase[p] - expectedPhase); + double phaseIncrement = (omega + phaseError) / m_increment; + + double unwrappedPhase = cd.unwrappedPhase[p] + + outputIncrement * phaseIncrement; + + cd.prevPhase[p] = cd.phase[p]; + cd.phase[p] = unwrappedPhase; + cd.unwrappedPhase[p] = unwrappedPhase; + + peakInPhase = cd.prevPhase[p]; + peakOutPhase = unwrappedPhase; + } + + if (i != p) { + + double diffToPeak = peakInPhase - cd.phase[i]; + double unwrappedPhase = peakOutPhase - diffToPeak; + + cd.prevPhase[i] = cd.phase[i]; + cd.phase[i] = unwrappedPhase; + cd.unwrappedPhase[i] = unwrappedPhase; + } + + } else { + cd.prevPhase[i] = cd.phase[i]; + cd.unwrappedPhase[i] = cd.phase[i]; + } + } +} + +void +RubberBandStretcher::Impl::synthesiseChunk(size_t channel) +{ + ChannelData &cd = *m_channelData[channel]; + + cd.fft->inversePolar(cd.mag, cd.phase, cd.dblbuf); + + for (size_t i = 0; i < m_blockSize/2; ++i) { + cd.fltbuf[i] = cd.dblbuf[i + m_blockSize/2]; + cd.fltbuf[i + m_blockSize/2] = cd.dblbuf[i]; + } + + // our ffts produced unscaled results + for (size_t i = 0; i < m_blockSize; ++i) { + cd.fltbuf[i] = cd.fltbuf[i] / m_blockSize; + } + + m_window->cut(cd.fltbuf); + + for (size_t i = 0; i < m_blockSize; ++i) { + cd.accumulator[i] += cd.fltbuf[i]; + } + + cd.accumulatorFill = m_blockSize; + + //!!! not much cop, this bit + + float area = m_window->getArea(); + + for (size_t i = 0; i < m_blockSize; ++i) { + float val = m_window->getValue(i); + cd.windowAccumulator[i] += val * area; + } +} + +void +RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, bool last) +{ + ChannelData &cd = *m_channelData[channel]; + + if (m_debugLevel > 2) { + cerr << "writeChunk(" << channel << ", " << shiftIncrement << ", " << last << ")" << endl; + } + + static float windowaccmax = 0; //!!! + + for (int i = 0; i < shiftIncrement; ++i) { + if (cd.windowAccumulator[i] > 0.f) { + cd.accumulator[i] /= cd.windowAccumulator[i]; + if (fabsf(cd.windowAccumulator[i]) > windowaccmax) { +// cerr << "window accumulator max " << windowaccmax +// << " -> " << fabsf(cd.windowAccumulator[i]) << endl; + //!!! + windowaccmax = fabsf(cd.windowAccumulator[i]); + } + } + } + + // for exact sample scaling (probably not meaningful if we + // were running in RT mode) + size_t theoreticalOut = 0; + if (cd.inputSize >= 0) { +// cerr << "cd.inputSize = " << cd.inputSize << endl; + theoreticalOut = lrint(cd.inputSize * m_timeRatio); +// cerr << "theoreticalOut = " << theoreticalOut << endl; + } + + if (m_pitchScale != 1.0 && cd.resampler) { +// cerr << "resampler: input " << shiftIncrement << ", output max " << int(shiftIncrement / m_pitchScale) + 1 << endl; + + size_t reqSize = int(ceil(shiftIncrement / m_pitchScale)); + if (reqSize > cd.resamplebufSize) { + // This shouldn't normally happen -- the buffer is + // supposed to be initialised with enough space in the + // first place. But we retain this check in case the + // pitch scale has changed since then, or the stretch + // calculator has goneb mad, or something. + cerr << "resampler: resizing buffer from " + << cd.resamplebufSize << " to " << reqSize << endl; + cd.resamplebufSize = reqSize; + if (cd.resamplebuf) delete[] cd.resamplebuf; + cd.resamplebuf = new float[cd.resamplebufSize]; + } + + + size_t outframes = cd.resampler->resample(&cd.accumulator, + &cd.resamplebuf, + shiftIncrement, + 1.0 / m_pitchScale, + last); + + + writeOutput(*cd.outbuf, cd.resamplebuf, + outframes, cd.outCount, theoreticalOut); + + } else { + writeOutput(*cd.outbuf, cd.accumulator, + shiftIncrement, cd.outCount, theoreticalOut); + } + + for (size_t i = 0; i < m_blockSize - shiftIncrement; ++i) { + cd.accumulator[i] = cd.accumulator[i + shiftIncrement]; + } + + for (size_t i = m_blockSize - shiftIncrement; i < m_blockSize; ++i) { + cd.accumulator[i] = 0.0f; + } + + for (size_t i = 0; i < m_blockSize - shiftIncrement; ++i) { + cd.windowAccumulator[i] = cd.windowAccumulator[i + shiftIncrement]; + } + + for (size_t i = m_blockSize - shiftIncrement; i < m_blockSize; ++i) { + cd.windowAccumulator[i] = 0.0f; + } + + if (cd.accumulatorFill > shiftIncrement) { + cd.accumulatorFill -= shiftIncrement; + } else { + cd.accumulatorFill = 0; + if (cd.draining) { + if (m_debugLevel > 1) { + cerr << "RubberBandStretcher::Impl::processChunks: setting outputComplete to true" << endl; + } + cd.outputComplete = true; + } + } +} + +void +RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut) +{ + // In non-RT mode, we don't want to write the first startSkip + // samples, because the first block is centred on the start of the + // output. In RT mode we didn't apply any pre-padding in + // configure(), so we don't want to remove any here. + + size_t startSkip = 0; + if (!m_realtime) { + startSkip = lrintf((m_blockSize/2) / m_pitchScale); + } + + if (outCount > startSkip) { + if (theoreticalOut > 0) { + if (m_debugLevel > 1) { + cerr << "theoreticalOut = " << theoreticalOut << ", outCount = " << outCount << ", startSkip = " << startSkip << ", qty = " << qty << endl; + } + if (outCount - startSkip <= theoreticalOut && + outCount - startSkip + qty > theoreticalOut) { + qty = theoreticalOut - (outCount - startSkip); + if (m_debugLevel > 1) { + cerr << "reduce qty to " << qty << endl; + } + } + } + + if (m_debugLevel > 2) { + cerr << "writing " << qty << endl; + } + to.write(from, qty); + outCount += qty; + return; + } + + if (outCount + qty <= startSkip) { + if (m_debugLevel > 1) { + cerr << "qty = " << qty << ", startSkip = " << startSkip << ", outCount = " << outCount << ", discarding" << endl; + } + outCount += qty; + return; + } + + size_t off = startSkip - outCount; + if (m_debugLevel > 1) { + cerr << "qty = " << qty << ", startSkip = " << startSkip << ", outCount = " << outCount << ", writing " << qty - off << " from start offset " << off << endl; + } + to.write(from + off, qty - off); + outCount += qty; +} + +int +RubberBandStretcher::Impl::available() const +{ + if (m_threaded) { + MutexLocker locker(&m_threadSetMutex); + if (m_channelData.empty()) return 0; + } else { + if (m_channelData.empty()) return 0; + } + + if (!m_threaded) { + for (size_t c = 0; c < m_channels; ++c) { + if (m_channelData[c]->inputSize >= 0) { + cerr << "available: m_done true" << endl; + if (m_channelData[c]->inbuf->getReadSpace() > 0) { + cerr << "calling processChunks(" << c << ") from available" << endl; + //!!! do we ever actually do this? if so, this method should not be const + ((RubberBandStretcher::Impl *)this)->processChunks(c); + } + } + } + } + + size_t min = 0; + bool consumed = true; + bool haveResamplers = false; + + for (size_t i = 0; i < m_channels; ++i) { + size_t availIn = m_channelData[i]->inbuf->getReadSpace(); + size_t availOut = m_channelData[i]->outbuf->getReadSpace(); + if (m_debugLevel > 2) { + cerr << "available on channel " << i << ": " << availOut << " (waiting: " << availIn << ")" << endl; + } + if (i == 0 || availOut < min) min = availOut; + if (!m_channelData[i]->outputComplete) consumed = false; + if (m_channelData[i]->resampler) haveResamplers = true; + } + + if (min == 0 && consumed) return -1; + if (m_pitchScale == 1.0) return min; + + if (haveResamplers) return min; // resampling has already happened + return int(floor(min / m_pitchScale)); +} + +size_t +RubberBandStretcher::Impl::retrieve(float *const *output, size_t samples) const +{ + size_t got = samples; + + for (size_t c = 0; c < m_channels; ++c) { + size_t gotHere = m_channelData[c]->outbuf->read(output[c], got); + if (gotHere < got) { + if (c > 0) { + if (m_debugLevel > 0) { + cerr << "RubberBandStretcher::Impl::retrieve: WARNING: channel imbalance detected" << endl; + } + } + got = gotHere; + } + } + + return got; +} + +} + diff --git a/src/Thread.cpp b/src/Thread.cpp new file mode 100644 index 0000000..d096cb6 --- /dev/null +++ b/src/Thread.cpp @@ -0,0 +1,190 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "Thread.h" + +#include + +#include +#include + +using std::cerr; +using std::endl; + +namespace RubberBand +{ + + +Thread::Thread() : + m_id(0), + m_extant(false) +{ +} + +Thread::~Thread() +{ + if (m_extant) { + pthread_join(m_id, 0); + } +} + +void +Thread::start() +{ + if (pthread_create(&m_id, 0, staticRun, this)) { + //!!! + cerr << "ERROR: thread creation failed" << endl; + exit(1); + } else { + m_extant = true; + } +} + +void +Thread::wait() +{ + if (m_extant) { + pthread_join(m_id, 0); + m_extant = false; + } +} + +Thread::Id +Thread::id() +{ + return m_id; +} + +bool +Thread::threadingAvailable() +{ + return true; +} + +void * +Thread::staticRun(void *arg) +{ + Thread *thread = (Thread *)arg; + thread->run(); + return 0; +} + +Mutex::Mutex() +{ + pthread_mutex_init(&m_mutex, 0); +} + +Mutex::~Mutex() +{ + pthread_mutex_destroy(&m_mutex); +} + +void +Mutex::lock() +{ + pthread_mutex_lock(&m_mutex); +} + +void +Mutex::unlock() +{ + pthread_mutex_unlock(&m_mutex); +} + +bool +Mutex::trylock() +{ + if (pthread_mutex_trylock(&m_mutex)) { + return false; + } else { + return true; + } +} + +Condition::Condition() +{ + pthread_mutex_init(&m_mutex, 0); + m_locked = false; + pthread_cond_init(&m_condition, 0); +} + +Condition::~Condition() +{ + if (m_locked) pthread_mutex_unlock(&m_mutex); + pthread_cond_destroy(&m_condition); + pthread_mutex_destroy(&m_mutex); +} + +void +Condition::lock() +{ + if (m_locked) return; + pthread_mutex_lock(&m_mutex); + m_locked = true; +} + +void +Condition::wait(int us) +{ + lock(); + if (us == 0) { + + pthread_cond_wait(&m_condition, &m_mutex); + + } else { + + struct timeval now; + gettimeofday(&now, 0); + + now.tv_usec += us; + while (now.tv_usec > 1000000) { + now.tv_usec -= 1000000; + ++now.tv_sec; + } + + struct timespec timeout; + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + + pthread_cond_timedwait(&m_condition, &m_mutex, &timeout); + } + + pthread_mutex_unlock(&m_mutex); + m_locked = false; +} + +void +Condition::signal() +{ + pthread_cond_signal(&m_condition); +} + + +MutexLocker::MutexLocker(Mutex *mutex) : + m_mutex(mutex) +{ + if (m_mutex) { + m_mutex->lock(); + } +} + +MutexLocker::~MutexLocker() +{ + if (m_mutex) { + m_mutex->unlock(); + } +} + +} + diff --git a/src/Thread.h b/src/Thread.h new file mode 100644 index 0000000..a58c9f4 --- /dev/null +++ b/src/Thread.h @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_THREAD_H_ +#define _RUBBERBAND_THREAD_H_ + +#include + +namespace RubberBand +{ + +class Thread +{ +public: + typedef pthread_t Id; + + Thread(); + virtual ~Thread(); + + Id id(); + + void start(); + void wait(); + + static bool threadingAvailable(); + +protected: + virtual void run() = 0; + +private: + pthread_t m_id; + bool m_extant; + static void *staticRun(void *); +}; + +class Mutex +{ +public: + Mutex(); + ~Mutex(); + + void lock(); + void unlock(); + bool trylock(); + +private: + pthread_mutex_t m_mutex; +}; + +class MutexLocker +{ +public: + MutexLocker(Mutex *); + ~MutexLocker(); + +private: + Mutex *m_mutex; +}; + +class Condition +{ +public: + Condition(); + ~Condition(); + + void lock(); + void wait(int us = 0); + void signal(); + +private: + pthread_mutex_t m_mutex; + bool m_locked; + pthread_cond_t m_condition; +}; + +} + +#endif diff --git a/src/Window.h b/src/Window.h new file mode 100644 index 0000000..a89feb9 --- /dev/null +++ b/src/Window.h @@ -0,0 +1,161 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_WINDOW_H_ +#define _RUBBERBAND_WINDOW_H_ + +#include +#include +#include + +enum WindowType { + RectangularWindow, + BartlettWindow, + HammingWindow, + HanningWindow, + BlackmanWindow, + GaussianWindow, + ParzenWindow, + NuttallWindow, + BlackmanHarrisWindow +}; + +template +class Window +{ +public: + /** + * Construct a windower of the given type. + */ + Window(WindowType type, size_t size) : m_type(type), m_size(size) { encache(); } + Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); } + Window &operator=(const Window &w) { + if (&w == this) return *this; + m_type = w.m_type; + m_size = w.m_size; + encache(); + return *this; + } + virtual ~Window() { delete[] m_cache; } + + void cut(T *src) const { cut(src, src); } + void cut(T *src, T *dst) const { + for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + } + + T getArea() { return m_area; } + T getValue(size_t i) { return m_cache[i]; } + + WindowType getType() const { return m_type; } + size_t getSize() const { return m_size; } + +protected: + WindowType m_type; + size_t m_size; + T *m_cache; + T m_area; + + void encache(); + void cosinewin(T *, T, T, T, T); +}; + +template +void Window::encache() +{ + int n = int(m_size); + T *mult = new T[n]; + int i; + for (i = 0; i < n; ++i) mult[i] = 1.0; + + switch (m_type) { + + case RectangularWindow: + for (i = 0; i < n; ++i) { + mult[i] *= 0.5; + } + break; + + case BartlettWindow: + for (i = 0; i < n/2; ++i) { + mult[i] *= (i / T(n/2)); + mult[i + n/2] *= (1.0 - (i / T(n/2))); + } + break; + + case HammingWindow: + cosinewin(mult, 0.54, 0.46, 0.0, 0.0); + break; + + case HanningWindow: + cosinewin(mult, 0.50, 0.50, 0.0, 0.0); + break; + + case BlackmanWindow: + cosinewin(mult, 0.42, 0.50, 0.08, 0.0); + break; + + case GaussianWindow: + for (i = 0; i < n; ++i) { + mult[i] *= pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2)); + } + break; + + case ParzenWindow: + { + int N = n-1; + for (i = 0; i < N/4; ++i) { + T m = 2 * pow(1.0 - (T(N)/2 - i) / (T(N)/2), 3); + mult[i] *= m; + mult[N-i] *= m; + } + for (i = N/4; i <= N/2; ++i) { + int wn = i - N/2; + T m = 1.0 - 6 * pow(wn / (T(N)/2), 2) * (1.0 - abs(wn) / (T(N)/2)); + mult[i] *= m; + mult[N-i] *= m; + } + break; + } + + case NuttallWindow: + cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411); + break; + + case BlackmanHarrisWindow: + cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168); + break; + } + + m_cache = mult; + + m_area = 0; + for (int i = 0; i < n; ++i) { + m_area += m_cache[i]; + } + m_area /= n; +} + +template +void Window::cosinewin(T *mult, T a0, T a1, T a2, T a3) +{ + int n = int(m_size); + for (int i = 0; i < n; ++i) { + mult[i] *= (a0 + - a1 * cos(2 * M_PI * i / n) + + a2 * cos(4 * M_PI * i / n) + - a3 * cos(6 * M_PI * i / n)); + } +} + +#endif diff --git a/src/ladspa/RubberBandPitchShifter.cpp b/src/ladspa/RubberBandPitchShifter.cpp new file mode 100644 index 0000000..03117c9 --- /dev/null +++ b/src/ladspa/RubberBandPitchShifter.cpp @@ -0,0 +1,358 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "RubberBandPitchShifter.h" + +#include "RubberBandStretcher.h" + +#include +#include + +const char *const +RubberBandPitchShifter::portNamesMono[PortCountMono] = +{ + "_latency", + "Cents", + "Semitones", + "Octaves", + "Input", + "Output" +}; + +const char *const +RubberBandPitchShifter::portNamesStereo[PortCountStereo] = +{ + "_latency", + "Cents", + "Semitones", + "Octaves", + "Input L", + "Output L", + "Input R", + "Output R" +}; + +const LADSPA_PortDescriptor +RubberBandPitchShifter::portsMono[PortCountMono] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO +}; + +const LADSPA_PortDescriptor +RubberBandPitchShifter::portsStereo[PortCountStereo] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO +}; + +const LADSPA_PortRangeHint +RubberBandPitchShifter::hintsMono[PortCountMono] = +{ + { 0, 0, 0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + -100.0, 100.0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -12.0, 12.0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -4.0, 4.0 }, + { 0, 0, 0 }, + { 0, 0, 0 } +}; + +const LADSPA_PortRangeHint +RubberBandPitchShifter::hintsStereo[PortCountStereo] = +{ + { 0, 0, 0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + -100.0, 100.0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -12.0, 12.0 }, + { LADSPA_HINT_DEFAULT_0 | + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -4.0, 4.0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 } +}; + +const LADSPA_Properties +RubberBandPitchShifter::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; + +const LADSPA_Descriptor +RubberBandPitchShifter::ladspaDescriptorMono = +{ + 2979, // "Unique" ID + "rubberband-pitchshifter-mono", // Label + properties, + "Rubber Band Mono Pitch Shifter", // Name + "Chris Cannam", //!!! Maker + "GPL", //!!! Copyright + PortCountMono, + portsMono, + portNamesMono, + hintsMono, + 0, // Implementation data + instantiate, + connectPort, + activate, + run, + 0, // Run adding + 0, // Set run adding gain + deactivate, + cleanup +}; + +const LADSPA_Descriptor +RubberBandPitchShifter::ladspaDescriptorStereo = +{ + 9792, // "Unique" ID + "rubberband-pitchshifter-stereo", // Label + properties, + "Rubber Band Stereo Pitch Shifter", // Name + "Chris Cannam", //!!! Maker + "GPL", //!!! Copyright + PortCountStereo, + portsStereo, + portNamesStereo, + hintsStereo, + 0, // Implementation data + instantiate, + connectPort, + activate, + run, + 0, // Run adding + 0, // Set run adding gain + deactivate, + cleanup +}; + +const LADSPA_Descriptor * +RubberBandPitchShifter::getDescriptor(unsigned long index) +{ +// std::cerr << "RubberBandPitchShifter::getDescriptor(" << index << ")" << std::endl; + if (index == 0) return &ladspaDescriptorMono; + if (index == 1) return &ladspaDescriptorStereo; + else return 0; +} + +RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) : + m_latency(0), + m_cents(0), + m_semitones(0), + m_octaves(0), + m_ratio(1.0), + m_prevRatio(1.0), + m_extraLatency(8192), //!!! this should be at least the maximum possible displacement from linear at input rates, divided by the pitch scale factor. It could be very large + m_stretcher(new RubberBand::RubberBandStretcher + (sampleRate, channels, + RubberBand::RubberBandStretcher::OptionProcessRealTime | + RubberBand::RubberBandStretcher::OptionStretchPrecise | +// RubberBand::RubberBandStretcher::OptionTransientsSmooth | + RubberBand::RubberBandStretcher::OptionTransientsCrisp | + RubberBand::RubberBandStretcher::OptionPhasePeakLocked | + RubberBand::RubberBandStretcher::OptionThreadingNone)), + m_sampleRate(sampleRate), + m_channels(channels) +{ +// m_stretcher->setMaxProcessBlockSize(4096); + + for (size_t c = 0; c < m_channels; ++c) { + m_input[c] = 0; + m_output[c] = 0; + //!!! size must be at least max process size plus m_extraLatency: + m_outputBuffer[c] = new RubberBand::RingBuffer(8092); //!!! + m_outputBuffer[c]->zero(m_extraLatency); + //!!! size must be at least max process size: + m_scratch[c] = new float[16384];//!!! + } +} + +RubberBandPitchShifter::~RubberBandPitchShifter() +{ + delete m_stretcher; + for (size_t c = 0; c < m_channels; ++c) { + delete m_outputBuffer[c]; + delete[] m_scratch[c]; + } +} + +LADSPA_Handle +RubberBandPitchShifter::instantiate(const LADSPA_Descriptor *desc, unsigned long rate) +{ + if (desc->PortCount == ladspaDescriptorMono.PortCount) { + return new RubberBandPitchShifter(rate, 1); + } else if (desc->PortCount == ladspaDescriptorStereo.PortCount) { + return new RubberBandPitchShifter(rate, 2); + } + return 0; +} + +void +RubberBandPitchShifter::connectPort(LADSPA_Handle handle, + unsigned long port, LADSPA_Data *location) +{ + RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; + + float **ports[PortCountStereo] = { + &shifter->m_latency, + &shifter->m_cents, + &shifter->m_semitones, + &shifter->m_octaves, + &shifter->m_input[0], + &shifter->m_output[0], + &shifter->m_input[1], + &shifter->m_output[1] + }; + + *ports[port] = (float *)location; +} + +void +RubberBandPitchShifter::activate(LADSPA_Handle handle) +{ + RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; +//!!! QMutexLocker locker(&shifter->m_mutex); + + shifter->updateRatio(); + shifter->m_prevRatio = shifter->m_ratio; + shifter->m_stretcher->reset(); + shifter->m_stretcher->setPitchScale(shifter->m_ratio); +} + +void +RubberBandPitchShifter::run(LADSPA_Handle handle, unsigned long samples) +{ + RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; + shifter->runImpl(samples); +} + +void +RubberBandPitchShifter::updateRatio() +{ + double oct = *m_octaves; + oct += *m_semitones / 12; + oct += *m_cents / 1200; + m_ratio = pow(2.0, oct); +} + +void +RubberBandPitchShifter::runImpl(unsigned long insamples) +{ +// std::cerr << "RubberBandPitchShifter::runImpl(" << insamples << ")" << std::endl; + + updateRatio(); + if (m_ratio != m_prevRatio) { + m_stretcher->setPitchScale(m_ratio); + m_prevRatio = m_ratio; + } + + if (m_latency) { + *m_latency = m_stretcher->getLatency() + m_extraLatency; +// std::cerr << "latency = " << *m_latency << std::endl; + } + + int samples = insamples; + int processed = 0; + size_t outTotal = 0; + + float *ptrs[2]; + + //!!! We have to break up the input into chunks like this because + // insamples could be arbitrarily large + + while (processed < samples) { + + //!!! size_t: + int toCauseProcessing = m_stretcher->getSamplesRequired(); +// std::cout << "to-cause: " << toCauseProcessing << ", remain = " << samples - processed; + int inchunk = std::min(samples - processed, toCauseProcessing); + for (size_t c = 0; c < m_channels; ++c) { + ptrs[c] = &(m_input[c][processed]); + } + m_stretcher->process(ptrs, inchunk, false); + processed += inchunk; + + int avail = m_stretcher->available(); + int writable = m_outputBuffer[0]->getWriteSpace(); + int outchunk = std::min(avail, writable); + size_t actual = m_stretcher->retrieve(m_scratch, outchunk); + outTotal += actual; + +// std::cout << ", avail: " << avail << ", outchunk = " << outchunk; +// if (actual != outchunk) std::cout << " (" << actual << ")"; +// std::cout << std::endl; + + outchunk = actual; + + for (size_t c = 0; c < m_channels; ++c) { + if (int(m_outputBuffer[c]->getWriteSpace()) < outchunk) { + std::cerr << "RubberBandPitchShifter::runImpl: buffer overrun: chunk = " << outchunk << ", space = " << m_outputBuffer[c]->getWriteSpace() << std::endl; + } + m_outputBuffer[c]->write(m_scratch[c], outchunk); + } + } + +// std::cout << "processed = " << processed << " in, " << outTotal << " out" << ", fill = " << m_outputBuffer[0]->getReadSpace() << " of " << m_outputBuffer[0]->getSize() << std::endl; + + for (size_t c = 0; c < m_channels; ++c) { + int avail = m_outputBuffer[c]->getReadSpace(); +// std::cout << "avail: " << avail << std::endl; + if (avail < samples && c == 0) { + std::cerr << "RubberBandPitchShifter::runImpl: buffer underrun: required = " << samples << ", available = " << avail << std::endl; + } + int chunk = std::min(avail, samples); +// std::cout << "out chunk: " << chunk << std::endl; + m_outputBuffer[c]->read(m_output[c], chunk); + } +} + +void +RubberBandPitchShifter::deactivate(LADSPA_Handle handle) +{ + activate(handle); // both functions just reset the plugin +} + +void +RubberBandPitchShifter::cleanup(LADSPA_Handle handle) +{ + delete (RubberBandPitchShifter *)handle; +} + diff --git a/src/ladspa/RubberBandPitchShifter.h b/src/ladspa/RubberBandPitchShifter.h new file mode 100644 index 0000000..5c5ccb8 --- /dev/null +++ b/src/ladspa/RubberBandPitchShifter.h @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_PITCH_SHIFTER_H_ +#define _RUBBERBAND_PITCH_SHIFTER_H_ + +#include + +#include "RingBuffer.h" + +namespace RubberBand { +class RubberBandStretcher; +} + +class RubberBandPitchShifter +{ +public: + static const LADSPA_Descriptor *getDescriptor(unsigned long index); + +protected: + RubberBandPitchShifter(int sampleRate, size_t channels); + ~RubberBandPitchShifter(); + + enum { + LatencyPort = 0, + OctavesPort = 1, + SemitonesPort = 2, + CentsPort = 3, + InputPort1 = 4, + OutputPort1 = 5, + PortCountMono = 6, + InputPort2 = 6, + OutputPort2 = 7, + PortCountStereo = 8 + }; + + static const char *const portNamesMono[PortCountMono]; + static const LADSPA_PortDescriptor portsMono[PortCountMono]; + static const LADSPA_PortRangeHint hintsMono[PortCountMono]; + + static const char *const portNamesStereo[PortCountStereo]; + static const LADSPA_PortDescriptor portsStereo[PortCountStereo]; + static const LADSPA_PortRangeHint hintsStereo[PortCountStereo]; + + static const LADSPA_Properties properties; + + static const LADSPA_Descriptor ladspaDescriptorMono; + static const LADSPA_Descriptor ladspaDescriptorStereo; + + static LADSPA_Handle instantiate(const LADSPA_Descriptor *, unsigned long); + static void connectPort(LADSPA_Handle, unsigned long, LADSPA_Data *); + static void activate(LADSPA_Handle); + static void run(LADSPA_Handle, unsigned long); + static void deactivate(LADSPA_Handle); + static void cleanup(LADSPA_Handle); + + void runImpl(unsigned long); + void updateRatio(); + + float *m_input[2]; + float *m_output[2]; + float *m_latency; + float *m_cents; + float *m_semitones; + float *m_octaves; + double m_ratio; + double m_prevRatio; + + size_t m_extraLatency; + + RubberBand::RubberBandStretcher *m_stretcher; + RubberBand::RingBuffer *m_outputBuffer[2]; + float *m_scratch[2]; + + int m_sampleRate; + size_t m_channels; +}; + + +#endif diff --git a/src/ladspa/libmain.cpp b/src/ladspa/libmain.cpp new file mode 100644 index 0000000..6c40799 --- /dev/null +++ b/src/ladspa/libmain.cpp @@ -0,0 +1,28 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include + +#include "RubberBandPitchShifter.h" + +#include + +extern "C" { + +const LADSPA_Descriptor *ladspa_descriptor(unsigned long index) +{ + return RubberBandPitchShifter::getDescriptor(index); +} + +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..53cc2ed --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,408 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "RubberBandStretcher.h" + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace RubberBand; + +int main(int argc, char **argv) +{ + int c; + + double ratio = 1.0; + double pitchshift = 1.0; + double frequencyshift = 1.0; + int debug = 1; + bool realtime = false; + bool precise = false; + bool threaded = true; + bool transients = true; + bool peaklock = true; + bool longwin = false; + bool shortwin = false; + int crispness = -1; + bool help = false; + + float fthresh0 = -1.f; + float fthresh1 = -1.f; + float fthresh2 = -1.f; + + while (1) { + int thisOptind = optind ? optind : 1; + int optionIndex = 0; + + static struct option longOpts[] = { + { "help", 0, 0, 'h' }, + { "time", 1, 0, 't' }, + { "tempo", 1, 0, 'T' }, + { "pitch", 1, 0, 'p' }, + { "frequency", 1, 0, 'f' }, + { "crisp", 1, 0, 'c' }, + { "crispness", 1, 0, 'c' }, + { "debug", 1, 0, 'd' }, + { "realtime", 0, 0, 'R' }, + { "precise", 0, 0, 'P' }, + { "no-threads", 0, 0, '0' }, + { "no-transients", 0, 0, '1' }, + { "no-peaklock", 0, 0, '2' }, + { "window-long", 0, 0, '3' }, + { "window-short", 0, 0, '4' }, + { "thresh0", 1, 0, '5' }, + { "thresh1", 1, 0, '6' }, + { "thresh2", 1, 0, '7' }, + { 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "t:p:d:RPc:f:", longOpts, &optionIndex); + if (c == -1) break; + + switch (c) { + case 'h': help = true; break; + case 't': ratio *= atof(optarg); break; + case 'T': { double m = atof(optarg); if (m != 0.0) ratio /= m; } break; + case 'p': pitchshift = atof(optarg); break; + case 'f': frequencyshift = atof(optarg); break; + case 'd': debug = atoi(optarg); break; + case 'R': realtime = true; break; + case 'P': precise = true; break; + case '0': threaded = false; break; + case '1': transients = false; break; + case '2': peaklock = false; break; + case '3': longwin = true; break; + case '4': shortwin = true; break; + case '5': fthresh0 = atof(optarg); break; + case '6': fthresh1 = atof(optarg); break; + case '7': fthresh2 = atof(optarg); break; + case 'c': crispness = atoi(optarg); break; + default: break; + } + } + + if (help || optind + 2 != argc) { + cerr << endl; + cerr << "Rubber Band" << endl; + cerr << "An audio time-stretching and pitch-shifting library and utility program." << endl; + cerr << "Copyright 2007 Chris Cannam." << endl; + cerr << endl; + cerr << "Usage: " << argv[0] << " [options] " << endl; + cerr << endl; + cerr << "where options may be:" << endl; + cerr << endl; + cerr << " -t, --time Stretch to X times original duration, or" << endl; + cerr << " -T, --tempo Change tempo by multiple X (equivalent to --time 1/X)" << endl; + cerr << endl; + cerr << " -p, --pitch Raise pitch by X semitones, or" << endl; + cerr << " -f, --frequency Change frequency by multiple X" << endl; + cerr << endl; + cerr << " -c, --crisp Crispness (N = 0,1,2,3); default 2 (see below)" << endl; + cerr << endl; + cerr << "The following options adjust the processing mode and stretch algorithm." << endl; + cerr << "These are mostly included for test purposes; the default settings and standard" << endl; + cerr << "crispness parameter are intended to provide the best sounding set of options" << endl; + cerr << "for most situations." << endl; + cerr << endl; + cerr << " -P, --precise Aim for minimal time distortion (implied by -R)" << endl; + cerr << " -R, --realtime Select realtime mode (implies -P --no-threads)" << endl; + cerr << " --no-threads No extra threads regardless of cpus/channel count" << endl; + cerr << " --no-transients Disable phase resynchronisation at transients" << endl; + cerr << " --no-peaklock Disable phase locking to peak frequencies" << endl; + cerr << " --window-long Use longer processing window (actual size may vary)" << endl; + cerr << " --window-short Use shorter processing window" << endl; + cerr << " --thresh Set internal freq threshold N (N = 0,1,2) to F Hz" << endl; + cerr << endl; + cerr << " -d, --debug Select debug level (N = 0,1,2,3); default 1, full 3" << std::endl; + cerr << " (N.B. debug level 3 includes audible ticks in output)" << endl; + cerr << endl; + cerr << " -h, --help Show this help" << endl; + cerr << endl; + cerr << "\"Crispness\" levels:" << endl; + cerr << " -c 0 equivalent to --no-transients --no-peaklock" << endl; + cerr << " -c 1 equivalent to --no-peaklock" << endl; + cerr << " -c 2 default processing options" << endl; + cerr << " -c 3 equivalent to --no-peaklock --window-short (may be suitable for drums)" << endl; + cerr << endl; + return 2; + } + + switch (crispness) { + case -1: crispness = 2; break; + case 0: transients = false; peaklock = false; longwin = false; shortwin = false; break; + case 1: transients = true; peaklock = false; longwin = false; shortwin = false; break; + case 2: transients = true; peaklock = true; longwin = false; shortwin = false; break; + case 3: transients = true; peaklock = false; longwin = false; shortwin = true; break; + }; + + char *fileName = strdup(argv[optind++]); + char *fileNameOut = strdup(argv[optind++]); + + SNDFILE *sndfile; + SNDFILE *sndfileOut; + SF_INFO sfinfo; + SF_INFO sfinfoOut; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(fileName, SFM_READ, &sfinfo); + if (!sndfile) { + cerr << "ERROR: Failed to open input file \"" << fileName << "\": " + << sf_strerror(sndfile) << endl; + return 1; + } + + sfinfoOut.channels = sfinfo.channels; + sfinfoOut.format = sfinfo.format; + sfinfoOut.frames = int(sfinfo.frames * ratio + 0.1); + sfinfoOut.samplerate = sfinfo.samplerate; + sfinfoOut.sections = sfinfo.sections; + sfinfoOut.seekable = sfinfo.seekable; + + sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ; + if (!sndfileOut) { + cerr << "ERROR: Failed to open output file \"" << fileName << "\" for writing: " + << sf_strerror(sndfile) << endl; + return 1; + } + + int ibs = 1024; + size_t channels = sfinfo.channels; + + RubberBandStretcher::Options options = 0; + if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; + if (precise) options |= RubberBandStretcher::OptionStretchPrecise; + if (!transients) options |= RubberBandStretcher::OptionTransientsSmooth; + if (!peaklock) options |= RubberBandStretcher::OptionPhaseIndependent; + if (!threaded) options |= RubberBandStretcher::OptionThreadingNone; + if (longwin) options |= RubberBandStretcher::OptionWindowLong; + if (shortwin) options |= RubberBandStretcher::OptionWindowShort; + + if (pitchshift != 1.0) { + frequencyshift *= pow(2.0, pitchshift / 12); + } + + RubberBandStretcher ts(sfinfo.samplerate, channels, options, + ratio, frequencyshift); + + ts.setDebugLevel(debug); + + ts.setExpectedInputDuration(sfinfo.frames); + +// ts.setTimeRatio(ratio); +// ts.setPitchScale(pitchshift); + + float *fbuf = new float[channels * ibs]; + float **ibuf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) ibuf[i] = new float[ibs]; + + int frame = 0; + int percent = 0; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + + if (!realtime) { + + cerr << "First pass (studying)..." << endl; + + while (frame < sfinfo.frames) { + +// std::cout << "study frame " << frame << std::endl; + + int count = -1; + + if (sf_seek(sndfile, frame, SEEK_SET) < 0) break; + if ((count = sf_readf_float(sndfile, fbuf, ibs)) <= 0) break; + + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < count; ++i) { + float value = fbuf[i * channels + c]; + ibuf[c][i] = value; + } + } + + bool final = (frame + ibs >= sfinfo.frames); + + ts.study(ibuf, count, final); + + int p = int((double(frame) * 100.0) / sfinfo.frames); + if (p > percent || frame == 0) { + percent = p; + cerr << "\r" << percent << "% "; + } + + frame += ibs; + } + + cerr << endl; + + cerr << "Second pass (processing)..." << endl; + } + + frame = 0; + percent = 0; + + float inpeak = 0; + double insum = 0; + float outpeak = 0.0; + double outsum = 0.0; + size_t countIn = 0, countOut = 0; + + while (frame < sfinfo.frames) { + + int count = -1; + + if (sf_seek(sndfile, frame, SEEK_SET) < 0) break; + if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) break; + + countIn += count; + + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < count; ++i) { + float value = fbuf[i * channels + c]; + ibuf[c][i] = value; + if (fabsf(value) > inpeak) inpeak = fabsf(value); + insum += value * value; + } + } + + bool final = (frame + ibs >= sfinfo.frames); + + ts.process(ibuf, count, final); + +// if +// std::cerr << frame << " + " << ibs << " >= " << sfinfo.frames << ": calling ts.complete()!" << std::endl; +// ts.complete(); +// } + + int avail = ts.available(); + if (debug > 1) std::cerr << "available = " << avail << std::endl; + + if (avail > 0) { + float **obf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) { + obf[i] = new float[avail]; + } + ts.retrieve(obf, avail); + countOut += avail; + float *fobf = new float[channels * avail]; + for (size_t c = 0; c < channels; ++c) { + for (size_t i = 0; i < avail; ++i) { + float value = obf[c][i]; + if (fabsf(value) > outpeak) outpeak = fabsf(value); + outsum += value * value; + value *= 0.75; + if (value > 1.f) value = 1.f; + if (value < -1.f) value = -1.f; + fobf[i * channels + c] = value; + } + } +// std::cout << "fobf mean: "; +// double d = 0; +// for (int i = 0; i < avail; ++i) { +// d += fobf[i]; +// } +// d /= avail; +// std::cout << d << std::endl; + sf_writef_float(sndfileOut, fobf, avail); + delete[] fobf; + for (size_t i = 0; i < channels; ++i) { + delete[] obf[i]; + } + delete[] obf; + } + + int p = int((double(frame) * 100.0) / sfinfo.frames); + if (p > percent || frame == 0) { + percent = p; + cerr << "\r" << percent << "% "; + } + + frame += ibs; + } + + int avail; + + while ((avail = ts.available()) >= 0) { + + if (debug > 1) std::cerr << "(completing) available = " << avail << std::endl; + + if (avail > 0) { + float **obf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) { + obf[i] = new float[avail]; + } + ts.retrieve(obf, avail); + countOut += avail; + float *fobf = new float[channels * avail]; + for (size_t c = 0; c < channels; ++c) { + for (size_t i = 0; i < avail; ++i) { + float value = obf[c][i]; + if (fabsf(value) > outpeak) outpeak = fabsf(value); + outsum += value * value; + value *= 0.75; + if (value > 1.f) value = 1.f; + if (value < -1.f) value = -1.f; + fobf[i * channels + c] = value; + } + } + + sf_writef_float(sndfileOut, fobf, avail); + delete[] fobf; + for (size_t i = 0; i < channels; ++i) { + delete[] obf[i]; + } + delete[] obf; + } + } + + sf_close(sndfile); + sf_close(sndfileOut); + + double inmean = sqrt(insum / (sfinfo.frames * sfinfo.channels)); + double outmean = sqrt(outsum / (countOut * sfinfo.channels)); + + cerr << endl << "in: " << countIn << ", out: " << countOut << ", ratio: " << float(countOut)/float(countIn) << ", ideal output: " << lrint(countIn * ratio) << ", diff: " << abs(lrint(countIn * ratio) - int(countOut)) << endl; + + cerr << "input peak: " << inpeak << "; output peak " << outpeak << "; gain " << (inpeak > 0 ? outpeak/inpeak : 1) << endl; + cerr << "input rms: " << inmean << "; output rms " << outmean << "; gain " << (inmean > 0 ? outmean/inmean : 1) << endl; + + struct timeval etv; + (void)gettimeofday(&etv, 0); + + cerr << "\nstart: " << tv.tv_sec << ":" << tv.tv_usec << endl; + cerr << "finish: " << etv.tv_sec << ":" << etv.tv_usec << endl; + + etv.tv_sec -= tv.tv_sec; + if (etv.tv_usec < tv.tv_usec) { + etv.tv_usec += 1000000; + etv.tv_sec -= 1; + } + etv.tv_usec -= tv.tv_usec; + + cerr << "elapsed: " << etv.tv_sec << ":" << etv.tv_usec << endl; + + double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0); + cerr << "\nin/sec: " << countIn/sec << ", out/sec: " << countOut/sec << endl; + + return 0; +} + + diff --git a/src/sysutils.cpp b/src/sysutils.cpp new file mode 100644 index 0000000..784e455 --- /dev/null +++ b/src/sysutils.cpp @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "sysutils.h" + +#include +#include + +namespace RubberBand { + +bool +system_is_multiprocessor() +{ + static bool tested = false, mp = false; + + if (tested) return mp; + + + //... + + FILE *cpuinfo = fopen("/proc/cpuinfo", "r"); + if (!cpuinfo) return false; + + int count = 0; + char buf[256]; + while (!feof(cpuinfo)) { + fgets(buf, 256, cpuinfo); + if (!strncmp(buf, "processor", 9)) { + ++count; + } + if (count > 1) break; + } + + fclose(cpuinfo); + return (count > 1); + +} + +} + + + diff --git a/src/sysutils.h b/src/sysutils.h new file mode 100644 index 0000000..0f1ce6e --- /dev/null +++ b/src/sysutils.h @@ -0,0 +1,24 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_SYSINFO_H_ +#define _RUBBERBAND_SYSINFO_H_ + +namespace RubberBand { + +extern bool system_is_multiprocessor(); + +} + +#endif diff --git a/src/vamp/RubberBandVampPlugin.cpp b/src/vamp/RubberBandVampPlugin.cpp new file mode 100644 index 0000000..a735971 --- /dev/null +++ b/src/vamp/RubberBandVampPlugin.cpp @@ -0,0 +1,564 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include "RubberBandVampPlugin.h" + +#include "StretchCalculator.h" + +#include + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +class RubberBandVampPlugin::Impl +{ +public: + size_t m_stepSize; + size_t m_blockSize; + size_t m_sampleRate; + + float m_timeRatio; + float m_pitchRatio; + + bool m_realtime; + bool m_elasticTiming; + bool m_crispTransients; + bool m_threadingAllowed; + + RubberBand::RubberBandStretcher *m_stretcher; + + int m_incrementsOutput; + int m_aggregateIncrementsOutput; + int m_divergenceOutput; + int m_lockDfOutput; + int m_smoothedLockDfOutput; + int m_lockPointsOutput; + + size_t m_counter; + size_t m_accumulatedIncrement; + + FeatureSet processOffline(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeaturesOffline(); + + FeatureSet processRealTime(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeaturesRealTime(); + + FeatureSet createFeatures(size_t inputIncrement, + std::vector &outputIncrements, + std::vector &lockDf, + std::vector &smoothedDF, + size_t baseCount, + bool includeFinal); +}; + + +RubberBandVampPlugin::RubberBandVampPlugin(float inputSampleRate) : + Plugin(inputSampleRate) +{ + m_d = new Impl(); + m_d->m_stepSize = 0; + m_d->m_timeRatio = 1.0; + m_d->m_pitchRatio = 1.0; + m_d->m_realtime = false; + m_d->m_elasticTiming = true; + m_d->m_crispTransients = true; + m_d->m_threadingAllowed = true; + m_d->m_stretcher = 0; + m_d->m_sampleRate = lrintf(m_inputSampleRate); +} + +RubberBandVampPlugin::~RubberBandVampPlugin() +{ + delete m_d->m_stretcher; + delete m_d; +} + +string +RubberBandVampPlugin::getIdentifier() const +{ + return "rubberband"; +} + +string +RubberBandVampPlugin::getName() const +{ + return "Rubber Band Timestretch Analysis"; +} + +string +RubberBandVampPlugin::getDescription() const +{ + return "Carry out analysis phases of time stretcher process"; +} + +string +RubberBandVampPlugin::getMaker() const +{ + return "Rubber Band"; ///!!! +} + +int +RubberBandVampPlugin::getPluginVersion() const +{ + return 1; +} + +string +RubberBandVampPlugin::getCopyright() const +{ + return "";//!!! +} + +RubberBandVampPlugin::OutputList +RubberBandVampPlugin::getOutputDescriptors() const +{ + OutputList list; + + size_t rate = 0; + if (m_d->m_stretcher) { + rate = lrintf(m_inputSampleRate / m_d->m_stretcher->getInputIncrement()); + } + + OutputDescriptor d; + d.identifier = "increments"; + d.name = "Output Increments"; + d.description = ""; //!!! + d.unit = "samples"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = false; //!!! + d.isQuantized = true; + d.quantizeStep = 1.0; + d.sampleType = OutputDescriptor::VariableSampleRate; + d.sampleRate = rate; + m_d->m_incrementsOutput = list.size(); + list.push_back(d); + + d.identifier = "aggregate_increments"; + d.name = "Accumulated Output Increments"; + d.description = ""; //!!! + d.sampleRate = 0; + m_d->m_aggregateIncrementsOutput = list.size(); + list.push_back(d); + + d.identifier = "divergence"; + d.name = "Divergence from Linear"; + d.description = ""; //!!! + d.isQuantized = false; + d.sampleRate = 0; + m_d->m_divergenceOutput = list.size(); + list.push_back(d); + + d.identifier = "lockdf"; + d.name = "Lock Point Detection Function"; + d.description = ""; //!!! + d.unit = ""; + d.sampleRate = rate; + m_d->m_lockDfOutput = list.size(); + list.push_back(d); + + d.identifier = "smoothedlockdf"; + d.name = "Smoothed Lock Point Detection Function"; + d.description = ""; //!!! + d.unit = ""; + m_d->m_smoothedLockDfOutput = list.size(); + list.push_back(d); + + d.identifier = "lockpoints"; + d.name = "Phase Lock Points"; + d.description = ""; //!!! + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 0; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleRate = 0; + m_d->m_lockPointsOutput = list.size(); + list.push_back(d); + + return list; +} + +RubberBandVampPlugin::ParameterList +RubberBandVampPlugin::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor d; + d.identifier = "timeratio"; + d.name = "Timestretch Ratio"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0.0000001; + d.maxValue = 1000; + d.defaultValue = 1.0; + d.isQuantized = false; + list.push_back(d); + + d.identifier = "pitchratio"; + d.name = "Pitch Scaling Ratio"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0.0000001; + d.maxValue = 1000; + d.defaultValue = 1.0; + d.isQuantized = false; + list.push_back(d); + + d.identifier = "mode"; + d.name = "Processing Mode"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0; + d.maxValue = 1; + d.defaultValue = 0; + d.isQuantized = true; + d.quantizeStep = 1; + d.valueNames.clear(); + d.valueNames.push_back("Offline"); + d.valueNames.push_back("Real Time"); + list.push_back(d); + + d.identifier = "stretchtype"; + d.name = "Stretch Flexibility"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0; + d.maxValue = 1; + d.defaultValue = 0; + d.isQuantized = true; + d.quantizeStep = 1; + d.valueNames.clear(); + d.valueNames.push_back("Elastic"); + d.valueNames.push_back("Precise"); + list.push_back(d); + + d.identifier = "transientmode"; + d.name = "Transient Handling"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0; + d.maxValue = 1; + d.defaultValue = 0; + d.isQuantized = true; + d.quantizeStep = 1; + d.valueNames.clear(); + d.valueNames.push_back("Crisp"); + d.valueNames.push_back("Soft"); + list.push_back(d); + + d.identifier = "threadingmode"; + d.name = "Threading"; + d.description = ""; //!!! + d.unit = ""; + d.minValue = 0; + d.maxValue = 1; + d.defaultValue = 0; + d.isQuantized = true; + d.quantizeStep = 1; + d.valueNames.clear(); + d.valueNames.push_back("Enabled"); + d.valueNames.push_back("Disabled"); + list.push_back(d); + + return list; +} + +float +RubberBandVampPlugin::getParameter(std::string id) const +{ + if (id == "timeratio") return m_d->m_timeRatio; + if (id == "pitchratio") return m_d->m_pitchRatio; + if (id == "mode") return m_d->m_realtime ? 1 : 0; + if (id == "stretchtype") return m_d->m_elasticTiming ? 0 : 1; + if (id == "transientmode") return m_d->m_crispTransients ? 0 : 1; + if (id == "threadingmode") return m_d->m_threadingAllowed ? 0 : 1; + return 0.f; +} + +void +RubberBandVampPlugin::setParameter(std::string id, float value) +{ + if (id == "timeratio") { + m_d->m_timeRatio = value; + } else if (id == "pitchratio") { + m_d->m_pitchRatio = value; + } else { + bool set = (value > 0.5); + if (id == "mode") m_d->m_realtime = set; + else if (id == "stretchtype") m_d->m_elasticTiming = !set; + else if (id == "transientmode") m_d->m_crispTransients = !set; + else if (id == "threadingmode") m_d->m_threadingAllowed = !set; + } +} + +bool +RubberBandVampPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_d->m_stepSize = std::min(stepSize, blockSize); + m_d->m_blockSize = stepSize; + + RubberBand::RubberBandStretcher::Options options = 0; + + if (m_d->m_realtime) + options |= RubberBand::RubberBandStretcher::OptionProcessRealTime; + else options |= RubberBand::RubberBandStretcher::OptionProcessOffline; + + if (m_d->m_elasticTiming) + options |= RubberBand::RubberBandStretcher::OptionStretchElastic; + else options |= RubberBand::RubberBandStretcher::OptionStretchPrecise; + + if (m_d->m_crispTransients) + options |= RubberBand::RubberBandStretcher::OptionTransientsCrisp; + else options |= RubberBand::RubberBandStretcher::OptionTransientsSmooth; + + if (m_d->m_threadingAllowed) + options |= RubberBand::RubberBandStretcher::OptionThreadingAuto; + else options |= RubberBand::RubberBandStretcher::OptionThreadingNone; + + delete m_d->m_stretcher; + m_d->m_stretcher = new RubberBand::RubberBandStretcher + (m_d->m_sampleRate, channels, options); + m_d->m_stretcher->setDebugLevel(2); + m_d->m_stretcher->setTimeRatio(m_d->m_timeRatio); + m_d->m_stretcher->setPitchScale(m_d->m_pitchRatio); + + m_d->m_counter = 0; + m_d->m_accumulatedIncrement = 0; + + return true; +} + +void +RubberBandVampPlugin::reset() +{ +// delete m_stretcher; //!!! or just if (m_stretcher) m_stretcher->reset(); +// m_stretcher = new RubberBand::RubberBandStretcher(lrintf(m_inputSampleRate), channels); + if (m_d->m_stretcher) m_d->m_stretcher->reset(); +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (m_d->m_realtime) { + return m_d->processRealTime(inputBuffers, timestamp); + } else { + return m_d->processOffline(inputBuffers, timestamp); + } +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::getRemainingFeatures() +{ + if (m_d->m_realtime) { + return m_d->getRemainingFeaturesRealTime(); + } else { + return m_d->getRemainingFeaturesOffline(); + } +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::Impl::processOffline(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (!m_stretcher) { + cerr << "ERROR: RubberBandVampPlugin::processOffline: " + << "RubberBandVampPlugin has not been initialised" + << endl; + return FeatureSet(); + } + + m_stretcher->study(inputBuffers, m_blockSize, false); + return FeatureSet(); +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::Impl::getRemainingFeaturesOffline() +{ + m_stretcher->study(0, 0, true); + + m_stretcher->calculateStretch(); + + int rate = m_sampleRate; + + RubberBand::StretchCalculator sc(rate, + m_stretcher->getInputIncrement(), + true); + + size_t inputIncrement = m_stretcher->getInputIncrement(); + std::vector outputIncrements = m_stretcher->getOutputIncrements(); + std::vector lockDf = m_stretcher->getLockCurve(); + std::vector smoothedDf = sc.smoothDF(lockDf); + + FeatureSet features = createFeatures + (inputIncrement, outputIncrements, lockDf, smoothedDf, 0, true); + + return features; +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::Impl::processRealTime(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + // This function is not in any way a real-time function (i.e. it + // has no requirement to be RT safe); it simply operates the + // stretcher in RT mode. + + if (!m_stretcher) { + cerr << "ERROR: RubberBandVampPlugin::processRealTime: " + << "RubberBandVampPlugin has not been initialised" + << endl; + return FeatureSet(); + } + + m_stretcher->process(inputBuffers, m_blockSize, false); + + size_t inputIncrement = m_stretcher->getInputIncrement(); + std::vector outputIncrements = m_stretcher->getOutputIncrements(); + std::vector lockDf = m_stretcher->getLockCurve(); + std::vector smoothedDf; // not meaningful in RT mode + FeatureSet features = createFeatures(inputIncrement, outputIncrements, + lockDf, smoothedDf, m_counter, false); + m_counter += outputIncrements.size(); + + return features; +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::Impl::getRemainingFeaturesRealTime() +{ + return FeatureSet(); +} + +RubberBandVampPlugin::FeatureSet +RubberBandVampPlugin::Impl::createFeatures(size_t inputIncrement, + std::vector &outputIncrements, + std::vector &lockDf, + std::vector &smoothedDf, + size_t baseCount, + bool includeFinal) +{ + size_t actual = m_accumulatedIncrement; + + double overallRatio = m_timeRatio * m_pitchRatio; + + char label[200]; + + FeatureSet features; + + int rate = m_sampleRate; + + for (size_t i = 0; i < outputIncrements.size(); ++i) { + + size_t frame = (baseCount + i) * inputIncrement; + + int oi = outputIncrements[i]; + bool lock = false; + + if (oi < 0) { + oi = -oi; + lock = true; + } + + double linear = (frame * overallRatio); + + Vamp::RealTime t = Vamp::RealTime::frame2RealTime(frame, rate); + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = t; + feature.values.push_back(oi); + feature.label = Vamp::RealTime::frame2RealTime(oi, rate).toText(); + features[m_incrementsOutput].push_back(feature); + + feature.values.clear(); + feature.values.push_back(actual); + feature.label = Vamp::RealTime::frame2RealTime(actual, rate).toText(); + features[m_aggregateIncrementsOutput].push_back(feature); + + feature.values.clear(); + feature.values.push_back(actual - linear); + + sprintf(label, "expected %ld, actual %ld, difference %ld (%s ms)", + long(linear), long(actual), long(actual - linear), + // frame2RealTime expects an integer frame number, + // hence our multiplication factor + (Vamp::RealTime::frame2RealTime + (lrintf((actual - linear) * 1000), rate) / 1000) + .toText().c_str()); + feature.label = label; + + features[m_divergenceOutput].push_back(feature); + actual += oi; + + char buf[30]; + + if (i < lockDf.size()) { + feature.values.clear(); + feature.values.push_back(lockDf[i]); + sprintf(buf, "%d", baseCount + i); + feature.label = buf; + features[m_lockDfOutput].push_back(feature); + } + + if (i < smoothedDf.size()) { + feature.values.clear(); + feature.values.push_back(smoothedDf[i]); + features[m_smoothedLockDfOutput].push_back(feature); + } + + if (lock) { + feature.values.clear(); + feature.label = buf; + features[m_lockPointsOutput].push_back(feature); + } + } + + if (includeFinal) { + Vamp::RealTime t = Vamp::RealTime::frame2RealTime + (inputIncrement * (baseCount + outputIncrements.size()), rate); + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = t; + feature.label = Vamp::RealTime::frame2RealTime(actual, rate).toText(); + feature.values.clear(); + feature.values.push_back(actual); + features[m_aggregateIncrementsOutput].push_back(feature); + + float linear = ((baseCount + outputIncrements.size()) + * inputIncrement * overallRatio); + feature.values.clear(); + feature.values.push_back(actual - linear); + feature.label = // see earlier comment + (Vamp::RealTime::frame2RealTime //!!! update this as earlier label + (lrintf((actual - linear) * 1000), rate) / 1000) + .toText(); + features[m_divergenceOutput].push_back(feature); + } + + m_accumulatedIncrement = actual; + + return features; +} + diff --git a/src/vamp/RubberBandVampPlugin.h b/src/vamp/RubberBandVampPlugin.h new file mode 100644 index 0000000..f850a28 --- /dev/null +++ b/src/vamp/RubberBandVampPlugin.h @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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 _RUBBERBAND_VAMP_PLUGIN_H_ +#define _RUBBERBAND_VAMP_PLUGIN_H_ + +#include + +#include "RubberBandStretcher.h" + +class RubberBandVampPlugin : public Vamp::Plugin +{ +public: + RubberBandVampPlugin(float inputSampleRate); + virtual ~RubberBandVampPlugin(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string id) const; + void setParameter(std::string id, float value); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class Impl; + Impl *m_d; +}; + +#endif diff --git a/src/vamp/libmain.cpp b/src/vamp/libmain.cpp new file mode 100644 index 0000000..a535c20 --- /dev/null +++ b/src/vamp/libmain.cpp @@ -0,0 +1,32 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band + An audio time-stretching and pitch-shifting library. + Copyright 2007 Chris Cannam. + + 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. +*/ + +#include +#include + +#include "RubberBandVampPlugin.h" + +static Vamp::PluginAdapter rubberBandAdapter; + +const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, + unsigned int index) +{ + if (version < 1) return 0; + + switch (index) { + case 0: return rubberBandAdapter.getDescriptor(); + default: return 0; + } +} +