Merge from branch rblive

This commit is contained in:
Chris Cannam
2024-08-08 12:01:21 +01:00
24 changed files with 3466 additions and 47 deletions

View File

@@ -299,7 +299,7 @@ vDSP -Dfft=vdsp -DHAVE_VDSP Default on macOS/iOS (in
FFTW3 -Dfft=fftw -DHAVE_FFTW3 A bit faster than built-in,
a bit slower than vDSP.
GPL licence.
GPL with commercial option.
SLEEF -Dfft=sleef -DHAVE_SLEEF Usually very fast. Not as widely
distributed as FFTW3. Requires

44
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,44 @@
Rubber Band is an open source library with a commercial licence
option. It is developed and maintained with care, prioritising
reliability, output quality, compatibility across releases and
platforms, and performance, in roughly that order.
## Bug reports
We welcome and encourage bug reports for the Rubber Band
Library. We're happy to receive them by almost any channel. The most
usual are [as Github issues](https://github.com/breakfastquay/rubberband/issues)
or [by contacting us directly](https://breakfastquay.com/contact.html).
Please try to provide enough information for us to reproduce the
problem. At the minimum this includes details of platform, compiler,
and library configuration, as well as a description of the expected
and problematic behaviour. We appreciate that describing issues
relating to audio output can be tricky, and we may ask you to send or
provide further information such as audio examples.
## Pull requests
Please be aware that we **cannot directly merge** pull requests. This
is partly for technical reasons (the Github repository for the library
is a read-only mirror of an upstream repo) and partly for control of
copyright and provenance.
Github pull requests may still be appropriate if you have small fixes,
particularly fixes to the build system or documentation, that would be
convenient to upstream. We will gratefully read and consider PRs,
especially of this sort.
If you have any more substantial changes you would like to see
included, please contact us directly to discuss.
## Other ways to contribute
An excellent way to contribute to the continued development of Rubber
Band Library is to [buy a commercial licence](https://breakfastquay.com/technology/license.html).
For enhancements and other new development work, it may be possible to
engage [Particular Programs Ltd](https://particularprograms.co.uk/) at
a commercial rate. Please contact us for details.

View File

@@ -12,7 +12,6 @@ tempo and pitch of an audio recording independently of one another.
* About Rubber Band: https://breakfastquay.com/rubberband/
* Code repository: https://hg.sr.ht/~breakfastquay/rubberband
* Issue tracker: https://todo.sr.ht/~breakfastquay/rubberband or https://github.com/breakfastquay/rubberband/issues
* Github mirror: https://github.com/breakfastquay/rubberband
CI builds:
@@ -56,8 +55,8 @@ licences for some relevant library code are as follows, to the best of
our knowledge. See also the file [COMPILING.md](COMPILING.md) for more
details.
* FFTW3 - GPL; proprietary licence needed for redistribution
* Intel IPP - Proprietary; licence needed for redistribution
* FFTW3 - GPL with commercial proprietary option
* Intel IPP - Proprietary of some nature
* SLEEF - BSD-like
* KissFFT - BSD-like
* libsamplerate - BSD-like from version 0.1.9 onwards

View File

@@ -8,6 +8,9 @@ cpu = 'x86_64'
system = 'darwin'
endian = 'little'
[properties]
needs_exe_wrapper = true
[binaries]
c = 'cc'
cpp = 'c++'

View File

@@ -8,6 +8,9 @@ cpu = 'aarch64'
system = 'darwin'
endian = 'little'
[properties]
needs_exe_wrapper = true
[binaries]
c = 'cc'
cpp = 'c++'

View File

@@ -0,0 +1,564 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2023 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
#include "RubberBandLivePitchShifter.h"
#include "RubberBandLiveShifter.h"
#include <iostream>
#include <cmath>
using namespace RubberBand;
using std::cerr;
using std::endl;
using std::string;
#ifdef RB_PLUGIN_LADSPA
const char *const
RubberBandLivePitchShifter::portNamesMono[PortCountMono] =
{
"latency",
"Cents",
"Semitones",
"Octaves",
"Formant Preserving",
"Wet-Dry Mix",
"Input",
"Output"
};
const char *const
RubberBandLivePitchShifter::portNamesStereo[PortCountStereo] =
{
"latency",
"Cents",
"Semitones",
"Octaves",
"Formant Preserving",
"Wet-Dry Mix",
"Input L",
"Output L",
"Input R",
"Output R"
};
const LADSPA_PortDescriptor
RubberBandLivePitchShifter::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_CONTROL,
LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL,
LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO,
LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO
};
const LADSPA_PortDescriptor
RubberBandLivePitchShifter::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_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
RubberBandLivePitchShifter::hintsMono[PortCountMono] =
{
{ 0, 0, 0 }, // latency
{ LADSPA_HINT_DEFAULT_0 | // cents
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE,
-100.0, 100.0 },
{ LADSPA_HINT_DEFAULT_0 | // semitones
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_INTEGER,
-12.0, 12.0 },
{ LADSPA_HINT_DEFAULT_0 | // octaves
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_INTEGER,
-2.0, 2.0 },
{ LADSPA_HINT_DEFAULT_0 | // formant preserving
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_TOGGLED,
0.0, 1.0 },
{ LADSPA_HINT_DEFAULT_0 | // wet-dry mix
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE,
0.0, 1.0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
const LADSPA_PortRangeHint
RubberBandLivePitchShifter::hintsStereo[PortCountStereo] =
{
{ 0, 0, 0 }, // latency
{ LADSPA_HINT_DEFAULT_0 | // cents
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE,
-100.0, 100.0 },
{ LADSPA_HINT_DEFAULT_0 | // semitones
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_INTEGER,
-12.0, 12.0 },
{ LADSPA_HINT_DEFAULT_0 | // octaves
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_INTEGER,
-2.0, 2.0 },
{ LADSPA_HINT_DEFAULT_0 | // formant preserving
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE |
LADSPA_HINT_TOGGLED,
0.0, 1.0 },
{ LADSPA_HINT_DEFAULT_0 | // wet-dry mix
LADSPA_HINT_BOUNDED_BELOW |
LADSPA_HINT_BOUNDED_ABOVE,
0.0, 1.0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
const LADSPA_Properties
RubberBandLivePitchShifter::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE;
const LADSPA_Descriptor
RubberBandLivePitchShifter::ladspaDescriptorMono =
{
29791, // "Unique" ID
"rubberband-live-pitchshifter-mono", // Label
properties,
"Rubber Band Live Mono Pitch Shifter", // Name
"Breakfast Quay",
"GPL",
PortCountMono,
portsMono,
portNamesMono,
hintsMono,
nullptr, // Implementation data
instantiate,
connectPort,
activate,
run,
nullptr, // Run adding
nullptr, // Set run adding gain
deactivate,
cleanup
};
const LADSPA_Descriptor
RubberBandLivePitchShifter::ladspaDescriptorStereo =
{
97921, // "Unique" ID
"rubberband-live-pitchshifter-stereo", // Label
properties,
"Rubber Band Live Stereo Pitch Shifter", // Name
"Breakfast Quay",
"GPL",
PortCountStereo,
portsStereo,
portNamesStereo,
hintsStereo,
nullptr, // Implementation data
instantiate,
connectPort,
activate,
run,
nullptr, // Run adding
nullptr, // Set run adding gain
deactivate,
cleanup
};
const LADSPA_Descriptor *
RubberBandLivePitchShifter::getDescriptor(unsigned long index)
{
if (index == 0) return &ladspaDescriptorMono;
if (index == 1) return &ladspaDescriptorStereo;
else return 0;
}
#else
const LV2_Descriptor
RubberBandLivePitchShifter::lv2DescriptorMono =
{
"http://breakfastquay.com/rdf/lv2-rubberband-live#mono",
instantiate,
connectPort,
activate,
run,
deactivate,
cleanup,
nullptr
};
const LV2_Descriptor
RubberBandLivePitchShifter::lv2DescriptorStereo =
{
"http://breakfastquay.com/rdf/lv2-rubberband-live#stereo",
instantiate,
connectPort,
activate,
run,
deactivate,
cleanup,
nullptr
};
const LV2_Descriptor *
RubberBandLivePitchShifter::getDescriptor(uint32_t index)
{
if (index == 0) return &lv2DescriptorMono;
if (index == 1) return &lv2DescriptorStereo;
else return 0;
}
#endif
RubberBandLivePitchShifter::RubberBandLivePitchShifter(int sampleRate, size_t channels) :
m_latency(nullptr),
m_cents(nullptr),
m_semitones(nullptr),
m_octaves(nullptr),
m_formant(nullptr),
m_wetDry(nullptr),
m_ratio(1.0),
m_prevRatio(1.0),
m_currentFormant(false),
m_shifter(new RubberBandLiveShifter
(sampleRate, channels,
RubberBandLiveShifter::OptionChannelsTogether)),
m_sampleRate(sampleRate),
m_channels(channels),
m_blockSize(0),
m_bufferSize(65536),
m_delay(0)
{
m_input = new float *[m_channels];
m_output = new float *[m_channels];
m_irb = new RingBuffer<float> *[m_channels];
m_orb = new RingBuffer<float> *[m_channels];
m_ib = new float *[m_channels];
m_ob = new float *[m_channels];
m_delayMixBuffer = new RingBuffer<float> *[m_channels];
m_blockSize = m_shifter->getBlockSize();
m_delay = m_shifter->getStartDelay();
for (int c = 0; c < m_channels; ++c) {
m_irb[c] = new RingBuffer<float>(m_bufferSize);
m_orb[c] = new RingBuffer<float>(m_bufferSize);
m_irb[c]->zero(m_blockSize);
m_ib[c] = new float[m_blockSize];
m_ob[c] = new float[m_blockSize];
m_delayMixBuffer[c] = new RingBuffer<float>(m_bufferSize + m_delay);
m_irb[c]->zero(m_delay);
}
activateImpl();
}
RubberBandLivePitchShifter::~RubberBandLivePitchShifter()
{
delete m_shifter;
for (int c = 0; c < m_channels; ++c) {
delete m_irb[c];
delete m_orb[c];
delete[] m_ib[c];
delete[] m_ob[c];
delete m_delayMixBuffer[c];
}
delete[] m_irb;
delete[] m_orb;
delete[] m_ib;
delete[] m_ob;
delete[] m_delayMixBuffer;
delete[] m_output;
delete[] m_input;
}
#ifdef RB_PLUGIN_LADSPA
LADSPA_Handle
RubberBandLivePitchShifter::instantiate(const LADSPA_Descriptor *desc, unsigned long rate)
{
if (desc->PortCount == ladspaDescriptorMono.PortCount) {
return new RubberBandLivePitchShifter(rate, 1);
} else if (desc->PortCount == ladspaDescriptorStereo.PortCount) {
return new RubberBandLivePitchShifter(rate, 2);
}
return nullptr;
}
#else
LV2_Handle
RubberBandLivePitchShifter::instantiate(const LV2_Descriptor *desc, double rate,
const char *, const LV2_Feature *const *)
{
if (rate < 1.0) {
cerr << "RubberBandLivePitchShifter::instantiate: invalid sample rate "
<< rate << " provided" << endl;
return nullptr;
}
size_t srate = size_t(round(rate));
if (string(desc->URI) == lv2DescriptorMono.URI) {
return new RubberBandLivePitchShifter(srate, 1);
} else if (string(desc->URI) == lv2DescriptorStereo.URI) {
return new RubberBandLivePitchShifter(srate, 2);
} else {
cerr << "RubberBandLivePitchShifter::instantiate: unrecognised URI "
<< desc->URI << " requested" << endl;
return nullptr;
}
}
#endif
#ifdef RB_PLUGIN_LADSPA
void
RubberBandLivePitchShifter::connectPort(LADSPA_Handle handle,
unsigned long port, LADSPA_Data *location)
#else
void
RubberBandLivePitchShifter::connectPort(LV2_Handle handle,
uint32_t port, void *location)
#endif
{
RubberBandLivePitchShifter *shifter = (RubberBandLivePitchShifter *)handle;
float **ports[PortCountStereo] = {
&shifter->m_latency,
&shifter->m_cents,
&shifter->m_semitones,
&shifter->m_octaves,
&shifter->m_formant,
&shifter->m_wetDry,
&shifter->m_input[0],
&shifter->m_output[0],
&shifter->m_input[1],
&shifter->m_output[1]
};
if (shifter->m_channels == 1) {
if (port >= PortCountMono) return;
} else {
if (port >= PortCountStereo) return;
}
*ports[port] = (float *)location;
if (shifter->m_latency) {
*(shifter->m_latency) = shifter->getLatency();
}
}
#ifdef RB_PLUGIN_LADSPA
void
RubberBandLivePitchShifter::activate(LADSPA_Handle handle)
#else
void
RubberBandLivePitchShifter::activate(LV2_Handle handle)
#endif
{
RubberBandLivePitchShifter *shifter = (RubberBandLivePitchShifter *)handle;
shifter->activateImpl();
}
#ifdef RB_PLUGIN_LADSPA
void
RubberBandLivePitchShifter::run(LADSPA_Handle handle, unsigned long samples)
#else
void
RubberBandLivePitchShifter::run(LV2_Handle handle, uint32_t samples)
#endif
{
RubberBandLivePitchShifter *shifter = (RubberBandLivePitchShifter *)handle;
shifter->runImpl(samples);
}
#ifdef RB_PLUGIN_LADSPA
void
RubberBandLivePitchShifter::deactivate(LADSPA_Handle handle)
#else
void
RubberBandLivePitchShifter::deactivate(LV2_Handle handle)
#endif
{
activate(handle); // both functions just reset the plugin
}
#ifdef RB_PLUGIN_LADSPA
void
RubberBandLivePitchShifter::cleanup(LADSPA_Handle handle)
#else
void
RubberBandLivePitchShifter::cleanup(LV2_Handle handle)
#endif
{
delete (RubberBandLivePitchShifter *)handle;
}
void
RubberBandLivePitchShifter::activateImpl()
{
updateRatio();
m_prevRatio = m_ratio;
m_shifter->reset();
m_shifter->setPitchScale(m_ratio);
for (int c = 0; c < m_channels; ++c) {
m_irb[c]->reset();
m_irb[c]->zero(m_blockSize);
m_orb[c]->reset();
m_delayMixBuffer[c]->reset();
m_delayMixBuffer[c]->zero(m_delay);
}
}
void
RubberBandLivePitchShifter::updateRatio()
{
// The octaves, semitones, and cents parameters are supposed to be
// integral: we want to enforce that, just to avoid
// inconsistencies between hosts if some respect the hints more
// than others
double octaves = round(m_octaves ? *m_octaves : 0.0);
if (octaves < -2.0) octaves = -2.0;
if (octaves > 2.0) octaves = 2.0;
double semitones = round(m_semitones ? *m_semitones : 0.0);
if (semitones < -12.0) semitones = -12.0;
if (semitones > 12.0) semitones = 12.0;
double cents = round(m_cents ? *m_cents : 0.0);
if (cents < -100.0) cents = -100.0;
if (cents > 100.0) cents = 100.0;
m_ratio = pow(2.0,
octaves +
semitones / 12.0 +
cents / 1200.0);
}
void
RubberBandLivePitchShifter::updateFormant()
{
if (!m_formant) return;
bool f = (*m_formant > 0.5f);
if (f == m_currentFormant) return;
RubberBandLiveShifter *s = m_shifter;
s->setFormantOption(f ?
RubberBandLiveShifter::OptionFormantPreserved :
RubberBandLiveShifter::OptionFormantShifted);
m_currentFormant = f;
}
int
RubberBandLivePitchShifter::getLatency() const
{
return m_shifter->getStartDelay() + m_blockSize;
}
void
RubberBandLivePitchShifter::runImpl(uint32_t insamples)
{
updateRatio();
if (m_ratio != m_prevRatio) {
m_shifter->setPitchScale(m_ratio);
m_prevRatio = m_ratio;
}
updateFormant();
if (m_latency) {
*m_latency = getLatency();
}
for (int c = 0; c < m_channels; ++c) {
m_irb[c]->write(m_input[c], insamples);
m_delayMixBuffer[c]->write(m_input[c], insamples);
}
while (m_irb[0]->getReadSpace() >= m_blockSize) {
for (int c = 0; c < m_channels; ++c) {
m_irb[c]->read(m_ib[c], m_blockSize);
}
m_shifter->shift(m_ib, m_ob);
for (int c = 0; c < m_channels; ++c) {
m_orb[c]->write(m_ob[c], m_blockSize);
}
}
for (int c = 0; c < m_channels; ++c) {
m_orb[c]->read(m_output[c], insamples);
}
float mix = 0.0;
if (m_wetDry) mix = *m_wetDry;
for (int c = 0; c < m_channels; ++c) {
if (mix > 0.0) {
for (uint32_t i = 0; i < insamples; ++i) {
float dry = m_delayMixBuffer[c]->readOne();
m_output[c][i] *= (1.0 - mix);
m_output[c][i] += dry * mix;
}
} else {
m_delayMixBuffer[c]->skip(insamples);
}
}
}

View File

@@ -0,0 +1,148 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2023 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
#ifndef RUBBER_BAND_LIVE_SHIFTER_H
#define RUBBER_BAND_LIVE_SHIFTER_H
#ifdef RB_PLUGIN_LADSPA
#ifdef RB_PLUGIN_LV2
#error "Only one of RB_PLUGIN_LADSPA and RB_PLUGIN_LV2 may be defined at once"
#endif
#else
#ifndef RB_PLUGIN_LV2
#error "Including code must define either RB_PLUGIN_LADSPA or RB_PLUGIN_LV2"
#endif
#endif
#ifdef RB_PLUGIN_LADSPA
#include <ladspa.h>
#else
#include <lv2.h>
#endif
#include "common/RingBuffer.h"
namespace RubberBand {
class RubberBandLiveShifter;
}
class RubberBandLivePitchShifter
{
public:
#ifdef RB_PLUGIN_LADSPA
static const LADSPA_Descriptor *getDescriptor(unsigned long index);
#else
static const LV2_Descriptor *getDescriptor(uint32_t index);
#endif
protected:
RubberBandLivePitchShifter(int sampleRate, size_t channels);
~RubberBandLivePitchShifter();
enum {
LatencyPort = 0,
CentsPort = 1,
SemitonesPort = 2,
OctavesPort = 3,
FormantPort = 4,
WetDryPort = 5,
InputPort1 = 6,
OutputPort1 = 7,
PortCountMono = OutputPort1 + 1,
InputPort2 = 8,
OutputPort2 = 9,
PortCountStereo = OutputPort2 + 1
};
#ifdef RB_PLUGIN_LADSPA
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);
#else
static const LV2_Descriptor lv2DescriptorMono;
static const LV2_Descriptor lv2DescriptorStereo;
static LV2_Handle instantiate(const LV2_Descriptor *, double,
const char *, const LV2_Feature *const *);
static void connectPort(LV2_Handle, uint32_t, void *);
static void activate(LV2_Handle);
static void run(LV2_Handle, uint32_t);
static void deactivate(LV2_Handle);
static void cleanup(LV2_Handle);
#endif
void activateImpl();
void runImpl(uint32_t count);
void process();
void updateRatio();
void updateFormant();
float **m_input;
float **m_output;
float *m_latency;
float *m_cents;
float *m_semitones;
float *m_octaves;
float *m_formant;
float *m_wetDry;
double m_ratio;
double m_prevRatio;
bool m_currentFormant;
RubberBand::RubberBandLiveShifter *m_shifter;
RubberBand::RingBuffer<float> **m_irb;
RubberBand::RingBuffer<float> **m_orb;
float **m_ib;
float **m_ob;
RubberBand::RingBuffer<float> **m_delayMixBuffer;
int m_sampleRate;
int m_channels;
int m_blockSize;
int m_bufferSize;
int m_delay;
int getLatency() const;
};
#endif

View File

@@ -34,6 +34,7 @@ using std::cout;
using std::cerr;
using std::endl;
using std::min;
using std::string;
#ifdef RB_PLUGIN_LADSPA
@@ -356,18 +357,18 @@ RubberBandPitchShifter::instantiate(const LV2_Descriptor *desc, double rate,
const char *, const LV2_Feature *const *)
{
if (rate < 1.0) {
std::cerr << "RubberBandPitchShifter::instantiate: invalid sample rate "
<< rate << " provided" << std::endl;
cerr << "RubberBandPitchShifter::instantiate: invalid sample rate "
<< rate << " provided" << endl;
return nullptr;
}
size_t srate = size_t(round(rate));
if (std::string(desc->URI) == lv2DescriptorMono.URI) {
if (string(desc->URI) == lv2DescriptorMono.URI) {
return new RubberBandPitchShifter(srate, 1);
} else if (std::string(desc->URI) == lv2DescriptorStereo.URI) {
} else if (string(desc->URI) == lv2DescriptorStereo.URI) {
return new RubberBandPitchShifter(srate, 2);
} else {
std::cerr << "RubberBandPitchShifter::instantiate: unrecognised URI "
<< desc->URI << " requested" << std::endl;
cerr << "RubberBandPitchShifter::instantiate: unrecognised URI "
<< desc->URI << " requested" << endl;
return nullptr;
}
}
@@ -642,7 +643,6 @@ RubberBandPitchShifter::runImpl(uint32_t insamples, uint32_t offset)
const int samples = insamples;
int processed = 0;
size_t outTotal = 0;
while (processed < samples) {
@@ -671,7 +671,6 @@ RubberBandPitchShifter::runImpl(uint32_t insamples, uint32_t offset)
}
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
outTotal += actual;
for (size_t c = 0; c < m_channels; ++c) {
m_outputBuffer[c]->write(m_scratch[c], actual);

View File

@@ -30,10 +30,9 @@
using namespace RubberBand;
using std::cout;
using std::cerr;
using std::endl;
using std::min;
using std::string;
#ifdef RB_PLUGIN_LADSPA
@@ -341,18 +340,18 @@ RubberBandR3PitchShifter::instantiate(const LV2_Descriptor *desc, double rate,
const char *, const LV2_Feature *const *)
{
if (rate < 1.0) {
std::cerr << "RubberBandR3PitchShifter::instantiate: invalid sample rate "
<< rate << " provided" << std::endl;
cerr << "RubberBandR3PitchShifter::instantiate: invalid sample rate "
<< rate << " provided" << endl;
return nullptr;
}
size_t srate = size_t(round(rate));
if (std::string(desc->URI) == lv2DescriptorMono.URI) {
if (string(desc->URI) == lv2DescriptorMono.URI) {
return new RubberBandR3PitchShifter(srate, 1);
} else if (std::string(desc->URI) == lv2DescriptorStereo.URI) {
} else if (string(desc->URI) == lv2DescriptorStereo.URI) {
return new RubberBandR3PitchShifter(srate, 2);
} else {
std::cerr << "RubberBandR3PitchShifter::instantiate: unrecognised URI "
<< desc->URI << " requested" << std::endl;
cerr << "RubberBandR3PitchShifter::instantiate: unrecognised URI "
<< desc->URI << " requested" << endl;
return nullptr;
}
}
@@ -593,7 +592,6 @@ RubberBandR3PitchShifter::runImpl(uint32_t insamples, uint32_t offset)
const int samples = insamples;
int processed = 0;
size_t outTotal = 0;
while (processed < samples) {
@@ -622,7 +620,6 @@ RubberBandR3PitchShifter::runImpl(uint32_t insamples, uint32_t offset)
}
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
outTotal += actual;
for (size_t c = 0; c < m_channels; ++c) {
m_outputBuffer[c]->write(m_scratch[c], actual);

View File

@@ -2,3 +2,5 @@ ladspa:ladspa-rubberband:rubberband-pitchshifter-mono::Frequency > Pitch shifter
ladspa:ladspa-rubberband:rubberband-pitchshifter-stereo::Frequency > Pitch shifters
ladspa:ladspa-rubberband:rubberband-r3-pitchshifter-mono::Frequency > Pitch shifters
ladspa:ladspa-rubberband:rubberband-r3-pitchshifter-stereo::Frequency > Pitch shifters
ladspa:ladspa-rubberband:rubberband-live-pitchshifter-mono::Frequency > Pitch shifters
ladspa:ladspa-rubberband:rubberband-live-pitchshifter-stereo::Frequency > Pitch shifters

View File

@@ -9,6 +9,10 @@
<ladspa:PitchPlugin rdf:about="&ladspa;2979"/>
<ladspa:PitchPlugin rdf:about="&ladspa;9792"/>
<ladspa:PitchPlugin rdf:about="&ladspa;29790"/>
<ladspa:PitchPlugin rdf:about="&ladspa;97920"/>
<ladspa:PitchPlugin rdf:about="&ladspa;29791"/>
<ladspa:PitchPlugin rdf:about="&ladspa;97921"/>
</rdf:RDF>

View File

@@ -25,6 +25,7 @@
#undef RB_PLUGIN_LV2
#include "RubberBandPitchShifter.cpp"
#include "RubberBandR3PitchShifter.cpp"
#include "RubberBandLivePitchShifter.cpp"
#include <stdio.h>
@@ -34,8 +35,10 @@ const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
{
if (index < 2) {
return RubberBandPitchShifter::getDescriptor(index);
} else {
} else if (index < 4) {
return RubberBandR3PitchShifter::getDescriptor(index - 2);
} else {
return RubberBandLivePitchShifter::getDescriptor(index - 4);
}
}

View File

@@ -25,6 +25,7 @@
#undef RB_PLUGIN_LADSPA
#include "RubberBandPitchShifter.cpp"
#include "RubberBandR3PitchShifter.cpp"
#include "RubberBandLivePitchShifter.cpp"
#include <stdio.h>
@@ -35,8 +36,10 @@ const LV2_Descriptor *lv2_descriptor(uint32_t index)
{
if (index < 2) {
return RubberBandPitchShifter::getDescriptor(index);
} else {
} else if (index < 4) {
return RubberBandR3PitchShifter::getDescriptor(index - 2);
} else {
return RubberBandLivePitchShifter::getDescriptor(index - 4);
}
}

View File

@@ -201,6 +201,43 @@ rubberband:r3mono
lv2:designation pg:center ;
] .
rubberband:livemono
a doap:Project, lv2:Plugin, lv2:PitchPlugin ;
doap:name "Rubber Band Live Mono Pitch Shifter" ;
doap:license <http://usefulinc.com/doap/licenses/gpl> ;
foaf:maker :maker ;
doap:developer :maker ;
doap:maintainer :maker ;
# Minor version will be 2x the Rubber Band API minor version
lv2:minorVersion 4 ;
lv2:microVersion 1 ;
lv2:optionalFeature lv2:hardRTCapable ;
pg:mainInput rubberband:mono_in_group ;
pg:mainOutput rubberband:mono_out_group ;
dc:replaces <urn:ladspa:29791> ;
lv2:port :latencyPort ,
:centsPort ,
:semitonesPort ,
:octavesPort ,
:formantPortR3 ,
:wetDryPortR3 ,
[ a lv2:AudioPort, lv2:InputPort ;
lv2:index 6 ;
lv2:symbol "input" ;
lv2:name "Input" ;
lv2:shortName "Input" ;
pg:group rubberband:mono_in_group ;
lv2:designation pg:center ;
], [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 7 ;
lv2:symbol "output" ;
lv2:name "Output" ;
lv2:shortName "Output" ;
pg:group rubberband:mono_out_group ;
lv2:designation pg:center ;
] .
rubberband:stereo
a doap:Project, lv2:Plugin, lv2:PitchPlugin ;
doap:name "Rubber Band Stereo Pitch Shifter" ;
@@ -306,3 +343,55 @@ rubberband:r3stereo
lv2:designation pg:right ;
] .
rubberband:livestereo
a doap:Project, lv2:Plugin, lv2:PitchPlugin ;
doap:name "Rubber Band Live Stereo Pitch Shifter" ;
doap:license <http://usefulinc.com/doap/licenses/gpl> ;
foaf:maker :maker ;
doap:developer :maker ;
doap:maintainer :maker ;
# Minor version will be 2x the Rubber Band API minor version
lv2:minorVersion 4 ;
lv2:microVersion 1 ;
lv2:optionalFeature lv2:hardRTCapable ;
pg:mainInput rubberband:stereo_in_group ;
pg:mainOutput rubberband:stereo_out_group ;
dc:replaces <urn:ladspa:97921> ;
lv2:port :latencyPort ,
:centsPort ,
:semitonesPort ,
:octavesPort ,
:formantPortR3 ,
:wetDryPortR3 ,
[ a lv2:AudioPort, lv2:InputPort ;
lv2:index 6 ;
lv2:symbol "input_l" ;
lv2:name "Input L" ;
lv2:shortName "Input L" ;
pg:group rubberband:stereo_in_group ;
lv2:designation pg:left ;
], [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 7 ;
lv2:symbol "output_l" ;
lv2:name "Output L" ;
lv2:shortName "Output L" ;
pg:group rubberband:stereo_out_group ;
lv2:designation pg:left ;
], [ a lv2:AudioPort, lv2:InputPort ;
lv2:index 8 ;
lv2:symbol "input_r" ;
lv2:name "Input R" ;
lv2:shortName "Input R" ;
pg:group rubberband:stereo_in_group ;
lv2:designation pg:right ;
], [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 9 ;
lv2:symbol "output_r" ;
lv2:name "Output R" ;
lv2:shortName "Output R" ;
pg:group rubberband:stereo_out_group ;
lv2:designation pg:right ;
] .

View File

@@ -12,6 +12,11 @@ rubberband:r3mono
lv2:binary <lv2-rubberband.so> ;
rdfs:seeAlso <lv2-rubberband.ttl> .
rubberband:livemono
a lv2:Plugin ;
lv2:binary <lv2-rubberband.so> ;
rdfs:seeAlso <lv2-rubberband.ttl> .
rubberband:stereo
a lv2:Plugin ;
lv2:binary <lv2-rubberband.so> ;
@@ -22,3 +27,8 @@ rubberband:r3stereo
lv2:binary <lv2-rubberband.so> ;
rdfs:seeAlso <lv2-rubberband.ttl> .
rubberband:livestereo
a lv2:Plugin ;
lv2:binary <lv2-rubberband.so> ;
rdfs:seeAlso <lv2-rubberband.ttl> .

View File

@@ -29,11 +29,13 @@ pkg = import('pkgconfig')
public_headers = [
'rubberband/rubberband-c.h',
'rubberband/RubberBandStretcher.h',
'rubberband/RubberBandLiveShifter.h',
]
library_sources = [
'src/rubberband-c.cpp',
'src/RubberBandStretcher.cpp',
'src/RubberBandLiveShifter.cpp',
'src/faster/AudioCurveCalculator.cpp',
'src/faster/CompoundAudioCurve.cpp',
'src/faster/HighFrequencyAudioCurve.cpp',
@@ -52,6 +54,7 @@ library_sources = [
'src/common/mathmisc.cpp',
'src/common/Thread.cpp',
'src/finer/R3Stretcher.cpp',
'src/finer/R3LiveShifter.cpp',
]
jni_sources = [
@@ -93,6 +96,7 @@ lv2_sources = [
unit_test_sources = [
'src/test/TestAllocators.cpp',
'src/test/TestFFT.cpp',
'src/test/TestLiveShifter.cpp',
'src/test/TestResampler.cpp',
'src/test/TestVectorOpsComplex.cpp',
'src/test/TestVectorOps.cpp',

View File

@@ -0,0 +1,348 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2024 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Live Pitch Shifter obtained by agreement with the
copyright holders, you may redistribute and/or modify it under the
terms described in that licence.
If you wish to distribute code using Rubber Band Live under terms
other than those of the GNU General Public License, you must
obtain a valid commercial licence before doing so.
*/
#ifndef RUBBERBAND_LIVE_SHIFTER_H
#define RUBBERBAND_LIVE_SHIFTER_H
#define RUBBERBAND_LIVE_VERSION "0.0.1"
#define RUBBERBAND_LIVE_API_MAJOR_VERSION 0
#define RUBBERBAND_LIVE_API_MINOR_VERSION 0
#undef RUBBERBAND_LIVE_DLLEXPORT
#ifdef _MSC_VER
#define RUBBERBAND_LIVE_DLLEXPORT __declspec(dllexport)
#else
#define RUBBERBAND_LIVE_DLLEXPORT
#endif
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <cstddef>
namespace RubberBand
{
/**
* ### Summary
*
* RubberBand::RubberBandLiveShifter is an interface to the Rubber
* Band Library designed for applications that need to perform
* pitch-shifting only, without time-stretching, and to do so with the
* shortest available processing delay.
*
* RubberBandLiveShifter has a much simpler API than the general
* RubberBandStretcher. Its process function, called
* RubberBandLiveShifter::shift(), accepts a fixed number of sample
* frames on each call and always returns exactly the same number of
* sample frames. This is in contrast to the
* process/available/retrieve call sequence that RubberBandStretcher
* requires as a result of its variable output rate.
*
* The number of frames RubberBandLiveShifter::shift() accepts and
* returns is not under the caller's control: it always requires
* exactly the number given by RubberBandLiveShifter::getBlockSize().
* However, that number is fixed for the lifetime of the shifter, so
* it only needs to be queried once and then fixed-size buffers may be
* passed.
*
* Using RubberBandLiveShifter also gives a substantially shorter
* processing delay than a typical buffering setup using
* RubberBandStretcher, making it a useful choice for some live
* situations, although it is still not a low-latency effect (and
* never will be) with a delay of 50ms or more between input and
* output signals depending on configuration. The actual value may be
* queried via RubberBandLiveShifter::getStartDelay(). The shifter is
* real-time safe in the sense of avoiding allocation, locking, or
* blocking operations in the processing path.
*
* ### Thread safety
*
* Multiple instances of RubberBandLiveShifter may be created and used
* in separate threads concurrently. However, for any single instance
* of RubberBandLiveShifter, you may not call
* RubberBandLiveShifter::shift() more than once concurrently, and you
* may not change the pitch scaling ratio using
* RubberBandLiveShifter::setPitchScale() while a
* RubberBandLiveShifter::shift() call is being executed. Changing the
* ratio is real-time safe, so when the pitch ratio is time-varying,
* it is normal to update the ratio before each shift call.
*/
class RUBBERBAND_LIVE_DLLEXPORT
RubberBandLiveShifter
{
public:
enum Option {
OptionWindowShort = 0x00000000,
OptionWindowMedium = 0x00100000,
OptionFormantShifted = 0x00000000,
OptionFormantPreserved = 0x01000000,
OptionChannelsApart = 0x00000000,
OptionChannelsTogether = 0x10000000,
// n.b. Options is int, so we must stop before 0x80000000
};
/**
* A bitwise OR of values from the RubberBandLiveShifter::Option
* enum.
*/
typedef int Options;
enum PresetOption {
DefaultOptions = 0x00000000
};
/**
* Interface for log callbacks that may optionally be provided to
* the shifter on construction.
*
* If a Logger is provided, the shifter will call one of these
* functions instead of sending output to \c cerr when there is
* something to report. This allows debug output to be diverted to
* an application's logging facilities, and/or handled in an
* RT-safe way. See setDebugLevel() for details about how and when
* RubberBandLiveShifter reports something in this way.
*
* The message text passed to each of these log functions is a
* C-style string with no particular guaranteed lifespan. If you
* need to retain it, copy it before returning. Do not free it.
*
* @see setDebugLevel
* @see setDefaultDebugLevel
*/
struct Logger {
/// Receive a log message with no numeric values.
virtual void log(const char *) = 0;
/// Receive a log message and one accompanying numeric value.
virtual void log(const char *, double) = 0;
/// Receive a log message and two accompanying numeric values.
virtual void log(const char *, double, double) = 0;
virtual ~Logger() { }
};
/**
* Construct a pitch shifter object to run at the given sample
* rate, with the given number of channels.
*/
RubberBandLiveShifter(size_t sampleRate, size_t channels,
Options options);
/**
* Construct a pitch shifter object with a custom debug
* logger. This may be useful for debugging if the default logger
* output (which simply goes to \c cerr) is not visible in the
* runtime environment, or if the application has a standard or
* more realtime-appropriate logging mechanism.
*
* See the documentation for the other constructor above for
* details of the arguments other than the logger.
*
* Note that although the supplied logger gets to decide what to
* do with log messages, the separately-set debug level (see
* setDebugLevel() and setDefaultDebugLevel()) still determines
* whether any given debug message is sent to the logger in the
* first place.
*/
RubberBandLiveShifter(size_t sampleRate, size_t channels,
std::shared_ptr<Logger> logger,
Options options);
~RubberBandLiveShifter();
/**
* Reset the shifter's internal buffers. The shifter should
* subsequently behave as if it had just been constructed
* (although retaining the current pitch ratio).
*/
void reset();
/**
* Set the pitch scaling ratio for the shifter. This is the ratio
* of target frequency to source frequency. For example, a ratio
* of 2.0 would shift up by one octave; 0.5 down by one octave; or
* 1.0 leave the pitch unaffected.
*
* To put this in musical terms, a pitch scaling ratio
* corresponding to a shift of S equal-tempered semitones (where S
* is positive for an upwards shift and negative for downwards) is
* pow(2.0, S / 12.0).
*
* This function may be called at any time, so long as it is not
* called concurrently with shift(). You should either call this
* function from the same thread as shift(), or provide your own
* mutex or similar mechanism to ensure that setPitchScale and
* shift() cannot be run at once (there is no internal mutex for
* this purpose).
*/
void setPitchScale(double scale);
/**
* Set a pitch scale for the vocal formant envelope separately
* from the overall pitch scale. This is a ratio of target
* frequency to source frequency. For example, a ratio of 2.0
* would shift the formant envelope up by one octave; 0.5 down by
* one octave; or 1.0 leave the formant unaffected.
*
* By default this is set to the special value of 0.0, which
* causes the scale to be calculated automatically. It will be
* treated as 1.0 / the pitch scale if OptionFormantPreserved is
* specified, or 1.0 for OptionFormantShifted.
*
* Conversely, if this is set to a value other than the default
* 0.0, formant shifting will happen regardless of the state of
* the OptionFormantPreserved/OptionFormantShifted option.
*
* This function is provided for special effects only. You do not
* need to call it for ordinary pitch shifting, with or without
* formant preservation - just specify or omit the
* OptionFormantPreserved option as appropriate. Use this function
* only if you want to shift formants by a distance other than
* that of the overall pitch shift.
*/
void setFormantScale(double scale);
/**
* Return the last pitch scaling ratio value that was set (either
* on construction or with setPitchScale()).
*/
double getPitchScale() const;
/**
* Return the last formant scaling ratio that was set with
* setFormantScale, or 0.0 if the default automatic scaling is in
* effect.
*/
double getFormantScale() const;
/**
* Return the output delay of the shifter. This is the number of
* audio samples that one should discard at the start of the
* output, in order to ensure that the resulting audio has the
* expected time alignment with the input.
*
* Ensure you have set the pitch scale to its proper starting
* value before calling getStartDelay().
*/
size_t getStartDelay() const;
/**
* Return the number of channels this shifter was constructed
* with.
*/
size_t getChannelCount() const;
/**
* Change an OptionFormant configuration setting. This may be
* called at any time in any mode.
*
* Note that if running multi-threaded in Offline mode, the change
* may not take effect immediately if processing is already under
* way when this function is called.
*/
void setFormantOption(Options options);
/**
* Query the number of sample frames that must be passed to, and
* will be returned by, each shift() call. This value is fixed for
* the lifetime of the shifter.
*
* Note that the blocksize refers to the number of audio sample
* frames, which may be multi-channel, not the number of
* individual samples.
*/
size_t getBlockSize() const;
/**
* Pitch-shift a single block of sample frames. The number of
* sample frames (samples per channel) processed per call is
* constant.
*
* "input" should point to de-interleaved audio data with one
* float array per channel, with each array containing n samples
* where n is the value returned by getBlockSize().
*
* "output" should point to a float array per channel, with each
* array having enough room to store n samples where n is the value
* returned by getBlockSize().
*
* Sample values are conventionally expected to be in the range
* -1.0f to +1.0f.
*/
void shift(const float *const *input, float *const *output);
/**
* Set the level of debug output. The supported values are:
*
* 0. Report errors only.
*
* 1. Report some information on construction and ratio
* change. Nothing is reported during normal processing unless
* something changes.
*
* 2. Report a significant amount of information about ongoing
* calculations during normal processing.
*
* The default is whatever has been set using
* setDefaultDebugLevel(), or 0 if that function has not been
* called.
*
* All output goes to \c cerr unless a custom
* RubberBandLiveShifter::Logger has been provided on
* construction. Because writing to \c cerr is not RT-safe, only
* debug level 0 is RT-safe in normal use by default. Debug levels
* 0 and 1 use only C-string constants as debug messages, so they
* are RT-safe if your custom logger is RT-safe. Levels 2 and 3
* are not guaranteed to be RT-safe in any conditions as they may
* construct messages by allocation.
*
* @see Logger
* @see setDefaultDebugLevel
*/
void setDebugLevel(int level);
/**
* Set the default level of debug output for subsequently
* constructed shifters.
*
* @see setDebugLevel
*/
static void setDefaultDebugLevel(int level);
protected:
class Impl;
Impl *m_d;
RubberBandLiveShifter(const RubberBandLiveShifter &) =delete;
RubberBandLiveShifter &operator=(const RubberBandLiveShifter &) =delete;
};
}
#endif

View File

@@ -0,0 +1,273 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2024 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Live Pitch Shifter obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Live Pitch Shifter
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
#include "../rubberband/RubberBandLiveShifter.h"
#include "finer/R3LiveShifter.h"
#include <iostream>
namespace RubberBand {
class RubberBandLiveShifter::Impl
{
R3LiveShifter *m_s;
class CerrLogger : public RubberBandLiveShifter::Logger {
public:
void log(const char *message) override {
std::cerr << "RubberBandLive: " << message << "\n";
}
void log(const char *message, double arg0) override {
auto prec = std::cerr.precision();
std::cerr.precision(10);
std::cerr << "RubberBandLive: " << message << ": " << arg0 << "\n";
std::cerr.precision(prec);
}
void log(const char *message, double arg0, double arg1) override {
auto prec = std::cerr.precision();
std::cerr.precision(10);
std::cerr << "RubberBandLive: " << message
<< ": (" << arg0 << ", " << arg1 << ")" << "\n";
std::cerr.precision(prec);
}
};
Log makeRBLog(std::shared_ptr<RubberBandLiveShifter::Logger> logger) {
if (logger) {
return Log(
[=](const char *message) {
logger->log(message);
},
[=](const char *message, double arg0) {
logger->log(message, arg0);
},
[=](const char *message, double arg0, double arg1) {
logger->log(message, arg0, arg1);
}
);
} else {
return makeRBLog(std::shared_ptr<RubberBandLiveShifter::Logger>
(new CerrLogger()));
}
}
public:
Impl(size_t sampleRate, size_t channels,
std::shared_ptr<RubberBandLiveShifter::Logger> logger,
RubberBandLiveShifter::Options options) :
m_s (new R3LiveShifter
(R3LiveShifter::Parameters(double(sampleRate), channels,
options),
makeRBLog(logger)))
{
}
~Impl()
{
delete m_s;
}
void reset()
{
m_s->reset();
}
RTENTRY__
void
setPitchScale(double scale)
{
m_s->setPitchScale(scale);
}
RTENTRY__
void
setFormantScale(double scale)
{
m_s->setFormantScale(scale);
}
RTENTRY__
double
getPitchScale() const
{
return m_s->getPitchScale();
}
RTENTRY__
double
getFormantScale() const
{
return m_s->getFormantScale();
}
RTENTRY__
void
setFormantOption(RubberBandLiveShifter::Options options)
{
m_s->setFormantOption(options);
}
RTENTRY__
size_t
getStartDelay() const
{
return m_s->getStartDelay();
}
RTENTRY__
size_t
getBlockSize() const
{
return m_s->getBlockSize();
}
RTENTRY__
void
shift(const float *const *input, float *const *output)
{
m_s->shift(input, output);
}
RTENTRY__
size_t
getChannelCount() const
{
return m_s->getChannelCount();
}
void
setDebugLevel(int level)
{
m_s->setDebugLevel(level);
}
static void
setDefaultDebugLevel(int level)
{
Log::setDefaultDebugLevel(level);
}
};
RubberBandLiveShifter::RubberBandLiveShifter(size_t sampleRate,
size_t channels,
Options options) :
m_d(new Impl(sampleRate, channels, nullptr, options))
{
}
RubberBandLiveShifter::RubberBandLiveShifter(size_t sampleRate,
size_t channels,
std::shared_ptr<Logger> logger,
Options options) :
m_d(new Impl(sampleRate, channels, logger, options))
{
}
RubberBandLiveShifter::~RubberBandLiveShifter()
{
delete m_d;
}
void
RubberBandLiveShifter::reset()
{
m_d->reset();
}
RTENTRY__
void
RubberBandLiveShifter::setPitchScale(double scale)
{
m_d->setPitchScale(scale);
}
RTENTRY__
void
RubberBandLiveShifter::setFormantScale(double scale)
{
m_d->setFormantScale(scale);
}
RTENTRY__
double
RubberBandLiveShifter::getPitchScale() const
{
return m_d->getPitchScale();
}
RTENTRY__
double
RubberBandLiveShifter::getFormantScale() const
{
return m_d->getFormantScale();
}
RTENTRY__
size_t
RubberBandLiveShifter::getStartDelay() const
{
return m_d->getStartDelay();
}
RTENTRY__
void
RubberBandLiveShifter::setFormantOption(Options options)
{
m_d->setFormantOption(options);
}
RTENTRY__
size_t
RubberBandLiveShifter::getBlockSize() const
{
return m_d->getBlockSize();
}
RTENTRY__
void
RubberBandLiveShifter::shift(const float *const *input, float *const *output)
{
m_d->shift(input, output);
}
RTENTRY__
size_t
RubberBandLiveShifter::getChannelCount() const
{
return m_d->getChannelCount();
}
void
RubberBandLiveShifter::setDebugLevel(int level)
{
m_d->setDebugLevel(level);
}
void
RubberBandLiveShifter::setDefaultDebugLevel(int level)
{
Impl::setDefaultDebugLevel(level);
}
}

View File

@@ -457,7 +457,7 @@ public:
<< guidance.phaseReset.f0 << " to " << guidance.phaseReset.f1
<< "]" << std::endl;
m_log.log(1, str.str().c_str());
m_log.log(2, str.str().c_str());
*/
}
@@ -478,7 +478,9 @@ protected:
double m_maxHigher;
void updateForSilence(Guidance &guidance) const {
// std::cout << "phase reset on silence" << std::endl;
m_log.log(2, "Guide::updateForSilence");
double nyquist = m_parameters.sampleRate / 2.0;
if (!m_parameters.singleWindowMode) {
guidance.fftBands[0].f0 = 0.0;
@@ -498,7 +500,7 @@ protected:
const BinSegmenter::Segmentation &segmentation,
bool realtime) const {
// std::cout << "unity" << std::endl;
m_log.log(2, "Guide::updateForUnity: realtime and single-window mode", (int)realtime, m_parameters.singleWindowMode);
double nyquist = m_parameters.sampleRate / 2.0;
@@ -533,9 +535,9 @@ protected:
if (!hadPhaseReset) {
guidance.phaseReset.f0 = 16000.0;
guidance.phaseReset.f1 = nyquist;
// std::cout << "f0 = " << guidance.phaseReset.f0 << std::endl;
return;
} else {
m_log.log(2, "Guide::updateForUnity: had phase reset");
guidance.phaseReset.f0 *= 0.9;
guidance.phaseReset.f1 *= 1.1;
}
@@ -553,10 +555,7 @@ protected:
guidance.phaseReset.f0 = 0.0;
}
// if (guidance.phaseReset.f0 > 0.0) {
// std::cout << unityCount << ": f0 = " << guidance.phaseReset.f0
// << ", f1 = " << guidance.phaseReset.f1 << std::endl;
// }
m_log.log(2, "Guide::updateForUnity: f0 and f1", guidance.phaseReset.f0, guidance.phaseReset.f1);
}
bool checkPotentialKick(const process_t *const magnitudes,

1202
src/finer/R3LiveShifter.cpp Normal file

File diff suppressed because it is too large Load Diff

423
src/finer/R3LiveShifter.h Normal file
View File

@@ -0,0 +1,423 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2024 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
#ifndef RUBBERBAND_R3_LIVE_SHIFTERIMPL_H
#define RUBBERBAND_R3_LIVE_SHIFTERIMPL_H
#include "BinSegmenter.h"
#include "Guide.h"
#include "Peak.h"
#include "PhaseAdvance.h"
#include "../common/Resampler.h"
#include "../common/FFT.h"
#include "../common/FixedVector.h"
#include "../common/Allocators.h"
#include "../common/Window.h"
#include "../common/VectorOpsComplex.h"
#include "../common/Log.h"
#include "../../rubberband/RubberBandLiveShifter.h"
#include <map>
#include <memory>
#include <atomic>
namespace RubberBand
{
class R3LiveShifter
{
public:
struct Parameters {
double sampleRate;
int channels;
RubberBandLiveShifter::Options options;
Parameters(double _sampleRate, int _channels,
RubberBandLiveShifter::Options _options) :
sampleRate(_sampleRate), channels(_channels), options(_options) { }
};
R3LiveShifter(Parameters parameters, Log log);
~R3LiveShifter() { }
void reset();
void setPitchScale(double scale);
void setFormantScale(double scale);
double getPitchScale() const;
double getFormantScale() const;
void setFormantOption(RubberBandLiveShifter::Options);
size_t getBlockSize() const;
void shift(const float *const *input, float *const *output);
size_t getPreferredStartPad() const;
size_t getStartDelay() const;
size_t getChannelCount() const;
void setDebugLevel(int level) {
m_log.setDebugLevel(level);
for (auto &sd : m_scaleData) {
sd.second->guided.setDebugLevel(level);
}
m_guide.setDebugLevel(level);
}
protected:
struct Limits {
int minPreferredOuthop;
int maxPreferredOuthop;
int minInhop;
int maxInhopWithReadahead;
int maxInhop;
Limits(RubberBandLiveShifter::Options, double rate) :
// commented values are results when rate = 44100 or 48000
minInhop(1)
{
minPreferredOuthop = roundUpDiv(rate, 256); // 256
maxPreferredOuthop = (roundUpDiv(rate, 128) * 5) / 4; // 640
maxInhopWithReadahead = roundUpDiv(rate, 128); // 512
maxInhop = (roundUpDiv(rate, 64) * 3) / 2; // 1536
}
};
struct ClassificationReadaheadData {
FixedVector<process_t> timeDomain;
FixedVector<process_t> mag;
FixedVector<process_t> phase;
ClassificationReadaheadData(int _fftSize) :
timeDomain(_fftSize, 0.f),
mag(_fftSize/2 + 1, 0.f),
phase(_fftSize/2 + 1, 0.f)
{ }
private:
ClassificationReadaheadData(const ClassificationReadaheadData &) =delete;
ClassificationReadaheadData &operator=(const ClassificationReadaheadData &) =delete;
};
struct ChannelScaleData {
int fftSize;
int bufSize; // size of every freq-domain array here: fftSize/2 + 1
FixedVector<process_t> timeDomain;
FixedVector<process_t> real;
FixedVector<process_t> imag;
FixedVector<process_t> mag;
FixedVector<process_t> phase;
FixedVector<process_t> advancedPhase;
FixedVector<process_t> prevMag;
FixedVector<process_t> pendingKick;
FixedVector<process_t> accumulator;
int accumulatorFill;
ChannelScaleData(int _fftSize, int _longestFftSize) :
fftSize(_fftSize),
bufSize(fftSize/2 + 1),
timeDomain(fftSize, 0.f),
real(bufSize, 0.f),
imag(bufSize, 0.f),
mag(bufSize, 0.f),
phase(bufSize, 0.f),
advancedPhase(bufSize, 0.f),
prevMag(bufSize, 0.f),
pendingKick(bufSize, 0.f),
accumulator(_longestFftSize, 0.f),
accumulatorFill(0)
{ }
void reset() {
v_zero(prevMag.data(), prevMag.size());
v_zero(pendingKick.data(), pendingKick.size());
v_zero(accumulator.data(), accumulator.size());
accumulatorFill = 0;
}
private:
ChannelScaleData(const ChannelScaleData &) =delete;
ChannelScaleData &operator=(const ChannelScaleData &) =delete;
};
struct FormantData {
int fftSize;
FixedVector<process_t> cepstra;
FixedVector<process_t> envelope;
FixedVector<process_t> spare;
FormantData(int _fftSize) :
fftSize(_fftSize),
cepstra(_fftSize, 0.0),
envelope(_fftSize/2 + 1, 0.0),
spare(_fftSize/2 + 1, 0.0) { }
process_t envelopeAt(process_t bin) const {
int b0 = int(floor(bin)), b1 = int(ceil(bin));
if (b0 < 0 || b0 > fftSize/2) {
return 0.0;
} else if (b1 == b0 || b1 > fftSize/2) {
return envelope.at(b0);
} else {
process_t diff = bin - process_t(b0);
return envelope.at(b0) * (1.0 - diff) + envelope.at(b1) * diff;
}
}
};
struct ChannelData {
std::map<int, std::shared_ptr<ChannelScaleData>> scales;
FixedVector<process_t> windowSource;
ClassificationReadaheadData readahead;
bool haveReadahead;
std::unique_ptr<BinClassifier> classifier;
FixedVector<BinClassifier::Classification> classification;
FixedVector<BinClassifier::Classification> nextClassification;
std::unique_ptr<BinSegmenter> segmenter;
BinSegmenter::Segmentation segmentation;
BinSegmenter::Segmentation prevSegmentation;
BinSegmenter::Segmentation nextSegmentation;
Guide::Guidance guidance;
FixedVector<float> mixdown;
FixedVector<float> resampled;
std::unique_ptr<RingBuffer<float>> inbuf;
std::unique_ptr<RingBuffer<float>> outbuf;
std::unique_ptr<FormantData> formant;
ChannelData(BinSegmenter::Parameters segmenterParameters,
BinClassifier::Parameters classifierParameters,
int longestFftSize,
int windowSourceSize,
int inRingBufferSize,
int outRingBufferSize) :
scales(),
windowSource(windowSourceSize, 0.0),
readahead(segmenterParameters.fftSize),
haveReadahead(false),
classifier(new BinClassifier(classifierParameters)),
classification(classifierParameters.binCount,
BinClassifier::Classification::Residual),
nextClassification(classifierParameters.binCount,
BinClassifier::Classification::Residual),
segmenter(new BinSegmenter(segmenterParameters)),
segmentation(), prevSegmentation(), nextSegmentation(),
mixdown(longestFftSize, 0.f),
resampled(outRingBufferSize, 0.f),
inbuf(new RingBuffer<float>(inRingBufferSize)),
outbuf(new RingBuffer<float>(outRingBufferSize)),
formant(new FormantData(segmenterParameters.fftSize)) { }
void reset() {
haveReadahead = false;
classifier->reset();
segmentation = BinSegmenter::Segmentation();
prevSegmentation = BinSegmenter::Segmentation();
nextSegmentation = BinSegmenter::Segmentation();
for (size_t i = 0; i < nextClassification.size(); ++i) {
nextClassification[i] = BinClassifier::Classification::Residual;
}
inbuf->reset();
outbuf->reset();
for (auto &s : scales) {
s.second->reset();
}
}
};
struct ChannelAssembly {
// Vectors of bare pointers, used to package container data
// from different channels into arguments for PhaseAdvance
FixedVector<const float *> input;
FixedVector<process_t *> mag;
FixedVector<process_t *> phase;
FixedVector<process_t *> prevMag;
FixedVector<Guide::Guidance *> guidance;
FixedVector<process_t *> outPhase;
FixedVector<float *> mixdown;
FixedVector<float *> resampled;
ChannelAssembly(int channels) :
input(channels, nullptr),
mag(channels, nullptr), phase(channels, nullptr),
prevMag(channels, nullptr), guidance(channels, nullptr),
outPhase(channels, nullptr), mixdown(channels, nullptr),
resampled(channels, nullptr) { }
};
struct ScaleData {
int fftSize;
bool singleWindowMode;
FFT fft;
Window<process_t> analysisWindow;
Window<process_t> synthesisWindow;
process_t windowScaleFactor;
GuidedPhaseAdvance guided;
ScaleData(GuidedPhaseAdvance::Parameters guidedParameters,
Log log) :
fftSize(guidedParameters.fftSize),
singleWindowMode(guidedParameters.singleWindowMode),
fft(fftSize),
analysisWindow(analysisWindowShape(),
analysisWindowLength()),
synthesisWindow(synthesisWindowShape(),
synthesisWindowLength()),
windowScaleFactor(0.0),
guided(guidedParameters, log)
{
int asz = analysisWindow.getSize(), ssz = synthesisWindow.getSize();
int off = (asz - ssz) / 2;
for (int i = 0; i < ssz; ++i) {
windowScaleFactor += analysisWindow.getValue(i + off) *
synthesisWindow.getValue(i);
}
}
WindowType analysisWindowShape();
int analysisWindowLength();
WindowType synthesisWindowShape();
int synthesisWindowLength();
};
Log m_log;
Parameters m_parameters;
const Limits m_limits;
std::atomic<double> m_pitchScale;
std::atomic<double> m_formantScale;
std::vector<std::shared_ptr<ChannelData>> m_channelData;
std::map<int, std::shared_ptr<ScaleData>> m_scaleData;
Guide m_guide;
Guide::Configuration m_guideConfiguration;
ChannelAssembly m_channelAssembly;
std::unique_ptr<Resampler> m_inResampler;
std::unique_ptr<Resampler> m_outResampler;
std::pair<int, int> m_initialResamplerDelays;
bool m_useReadahead;
int m_prevInhop;
int m_prevOuthop;
bool m_firstProcess;
uint32_t m_unityCount;
void initialise();
void readIn(const float *const *input);
void generate(int required);
int readOut(float *const *output, int outcount);
void createResamplers();
void measureResamplerDelay();
void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop);
void analyseFormant(int channel);
void adjustFormant(int channel);
void adjustPreKick(int channel);
void synthesiseChannel(int channel, int outhop, bool draining);
struct ToPolarSpec {
int magFromBin;
int magBinCount;
int polarFromBin;
int polarBinCount;
};
Parameters validateSampleRate(const Parameters &params) {
Parameters validated { params };
double minRate = 8000.0, maxRate = 192000.0;
if (params.sampleRate < minRate) {
m_log.log(0, "R3LiveShifter: WARNING: Unsupported sample rate", params.sampleRate);
m_log.log(0, "R3LiveShifter: Minimum rate is", minRate);
validated.sampleRate = minRate;
} else if (params.sampleRate > maxRate) {
m_log.log(0, "R3LiveShifter: WARNING: Unsupported sample rate", params.sampleRate);
m_log.log(0, "R3LiveShifter: Maximum rate is", maxRate);
validated.sampleRate = maxRate;
}
return validated;
}
void convertToPolar(process_t *mag, process_t *phase,
const process_t *real, const process_t *imag,
const ToPolarSpec &s) const {
v_cartesian_to_polar(mag + s.polarFromBin,
phase + s.polarFromBin,
real + s.polarFromBin,
imag + s.polarFromBin,
s.polarBinCount);
if (s.magFromBin < s.polarFromBin) {
v_cartesian_to_magnitudes(mag + s.magFromBin,
real + s.magFromBin,
imag + s.magFromBin,
s.polarFromBin - s.magFromBin);
}
if (s.magFromBin + s.magBinCount > s.polarFromBin + s.polarBinCount) {
v_cartesian_to_magnitudes(mag + s.polarFromBin + s.polarBinCount,
real + s.polarFromBin + s.polarBinCount,
imag + s.polarFromBin + s.polarBinCount,
s.magFromBin + s.magBinCount -
s.polarFromBin - s.polarBinCount);
}
}
bool useMidSide() const {
return m_parameters.channels == 2 &&
(m_parameters.options &
RubberBandLiveShifter::OptionChannelsTogether);
}
bool isSingleWindowed() const {
return true;
}
double getInRatio() const {
if (m_pitchScale > 1.0) {
return 1.0 / m_pitchScale;
} else {
return 1.0;
}
}
double getOutRatio() const {
if (m_pitchScale < 1.0) {
return 1.0 / m_pitchScale;
} else {
return 1.0;
}
}
int getWindowSourceSize() const {
if (m_useReadahead) {
int sz = m_guideConfiguration.classificationFftSize +
m_limits.maxInhopWithReadahead;
if (m_guideConfiguration.longestFftSize > sz) {
return m_guideConfiguration.longestFftSize;
} else {
return sz;
}
} else {
return m_guideConfiguration.longestFftSize;
}
}
};
}
#endif

View File

@@ -299,6 +299,10 @@ R3Stretcher::createResampler()
resamplerParameters.dynamism = Resampler::RatioMostlyFixed;
resamplerParameters.ratioChange = Resampler::SuddenRatioChange;
}
int debug = m_log.getDebugLevel();
if (debug > 0) --debug;
resamplerParameters.debugLevel = debug;
m_resampler = std::unique_ptr<Resampler>
(new Resampler(resamplerParameters, m_parameters.channels));

View File

@@ -0,0 +1,298 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
/*
Rubber Band Library
An audio time-stretching and pitch-shifting library.
Copyright 2007-2024 Particular Programs Ltd.
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.
Alternatively, if you have a valid commercial licence for the
Rubber Band Library obtained by agreement with the copyright
holders, you may redistribute and/or modify it under the terms
described in that licence.
If you wish to distribute code using the Rubber Band Library
under terms other than those of the GNU General Public License,
you must obtain a valid commercial licence before doing so.
*/
#ifndef BOOST_TEST_DYN_LINK
#define BOOST_TEST_DYN_LINK
#endif
#include <boost/test/unit_test.hpp>
#include "../../rubberband/RubberBandLiveShifter.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
using namespace RubberBand;
using std::vector;
using std::cerr;
using std::endl;
using std::string;
using std::ofstream;
namespace tt = boost::test_tools;
BOOST_AUTO_TEST_SUITE(TestLiveShifter)
static void dumpTo(string basename,
const vector<float> &data)
{
string dir = "/tmp";
string filename = dir + "/" + basename + ".csv";
ofstream file(filename, std::ios::out | std::ios::binary);
if (!file) {
cerr << "dumpTo: failed to open file \"" << filename << "\" for writing" << endl;
return;
}
file << "sample,V" << endl;
for (int i = 0; i < int(data.size()); ++i) {
file << i << "," << data[i] << endl;
}
}
static void dump(string prefix,
const vector<float> &in,
const vector<float> &out,
const vector<float> &expected,
int delay)
{
cerr << "dump: delay reported as " << delay << endl;
if (prefix != "") {
prefix += "-";
}
dumpTo(prefix + "in", in);
dumpTo(prefix + "out", out);
dumpTo(prefix + "expected", expected);
vector<float> shifted;
vector<float> diff;
for (int i = 0; i + delay < int(out.size()); ++i) {
shifted.push_back(out[i + delay]);
diff.push_back(out[i + delay] - expected[i]);
}
dumpTo(prefix + "shifted", shifted);
dumpTo(prefix + "diff", diff);
}
static void check_sinusoid_unchanged(int n, int rate, float freq,
RubberBandLiveShifter::Options options,
string debugPrefix = {})
{
bool printDebug = (debugPrefix != "");
if (printDebug) {
RubberBandLiveShifter::setDefaultDebugLevel(2);
}
RubberBandLiveShifter shifter(rate, 1, options);
int blocksize = shifter.getBlockSize();
BOOST_TEST(blocksize == 512);
n = (n / blocksize + 1) * blocksize;
vector<float> in(n), out(n);
for (int i = 0; i < n; ++i) {
in[i] = 0.5f * sinf(float(i) * freq * M_PI * 2.f / float(rate));
}
for (int i = 0; i < n; i += blocksize) {
float *inp = in.data() + i;
float *outp = out.data() + i;
shifter.shift(&inp, &outp);
}
int delay = shifter.getStartDelay();
// We now have n samples of a simple sinusoid with stretch factor
// 1.0; obviously we expect the output to be essentially the same
// thing. It will have lower precision for a while at the start,
// so we check that with a threshold of 0.1; after that we expect
// better precision.
int slackpart = 2048;
float slackeps = 1.0e-1f;
float eps = 1.0e-3f;
#ifdef USE_BQRESAMPLER
eps = 1.0e-2f;
#endif
for (int i = 0; i < slackpart; ++i) {
float fin = in[i];
float fout = out[delay + i];
float err = fabsf(fin - fout);
if (err > slackeps) {
cerr << "Error at index " << i << " exceeds slack eps "
<< slackeps << ": output " << fout << " - input "
<< fin << " = " << fout - fin << endl;
BOOST_TEST(err < eps);
break;
}
}
for (int i = slackpart; i < n - delay; ++i) {
float fin = in[i];
float fout = out[delay + i];
float err = fabsf(fin - fout);
if (err > eps) {
cerr << "Error at index " << i << " exceeds tight eps "
<< eps << ": output " << fout << " - input "
<< fin << " = " << fout - fin << endl;
BOOST_TEST(err < eps);
break;
}
}
if (printDebug) {
RubberBandLiveShifter::setDefaultDebugLevel(0);
dump(debugPrefix, in, out, in, delay);
}
}
static void check_sinusoid_shifted(int n, int rate, float freq, float shift,
RubberBandLiveShifter::Options options,
string debugPrefix = {})
{
bool printDebug = (debugPrefix != "");
if (printDebug) {
RubberBandLiveShifter::setDefaultDebugLevel(2);
}
RubberBandLiveShifter shifter(rate, 1, options);
shifter.setPitchScale(shift);
int blocksize = shifter.getBlockSize();
BOOST_TEST(blocksize == 512);
n = (n / blocksize + 1) * blocksize;
vector<float> in(n), out(n), expected(n);
int endpoint = n;
if (endpoint > 20000) endpoint -= 10000;
for (int i = 0; i < n; ++i) {
float value = 0.5f * sinf(float(i) * freq * M_PI * 2.f / float(rate));
if (i > endpoint && value > 0.f && in[i-1] <= 0.f) break;
in[i] = value;
expected[i] = 0.5f * sinf(float(i) * freq * shift * M_PI * 2.f / float(rate));
}
for (int i = 0; i < n; i += blocksize) {
float *inp = in.data() + i;
float *outp = out.data() + i;
shifter.shift(&inp, &outp);
}
int reportedDelay = shifter.getStartDelay();
int slackpart = 2048;
double lastCrossing = -1;
double eps = 2.0;
int i0 = reportedDelay + slackpart;
int i1 = endpoint;
for (int i = i0; i < i1; ++i) {
if (out[i-1] < 0.f && out[i] >= 0.f) {
double crossing = (i-1) + (out[i-1] / (out[i-1] - out[i]));
if (lastCrossing >= 0) {
double f = rate / (crossing - lastCrossing);
double diff = freq * shift - f;
double ratio = f / (freq * shift);
double cents = 1200.0 * (log(ratio)/log(2.0));
if (fabs(cents) >= eps) {
cerr << "i = " << i << " (from " << i0 << " to " << i1 << ", out[i-1] = " << out[i-1] << ", out[i] = " << out[i] << "), f = " << f << ", in freq = " << freq << ", out freq = " << freq * shift << ", ratio = " << ratio << ", cents = " << cents << ", diff = " << diff << ", eps = " << eps << ", shift = " << shift << ", factor = " << fabs(cents)/eps << endl;
BOOST_TEST(fabs(cents) < eps);
}
}
lastCrossing = crossing;
}
}
if (printDebug) {
RubberBandLiveShifter::setDefaultDebugLevel(0);
dump(debugPrefix, in, out, expected, reportedDelay);
}
}
BOOST_AUTO_TEST_CASE(sinusoid_unchanged)
{
int n = 20000;
// delay = 2112, correct
check_sinusoid_unchanged(n, 44100, 440.f, 0);
check_sinusoid_unchanged(n, 48000, 260.f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_down_octave_440)
{
// Checked: delay = 3648, seems ok
int n = 30000;
check_sinusoid_shifted(n, 44100, 440.f, 0.5f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_down_octave_260)
{
// Checked: delay = 3648, correct
int n = 30000;
check_sinusoid_shifted(n, 48000, 260.f, 0.5f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_down_2octave)
{
// Checked: delay = 6784, sound
int n = 30000;
check_sinusoid_shifted(n, 44100, 440.f, 0.25f, 0);
check_sinusoid_shifted(n, 48000, 260.f, 0.25f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_up_octave_440)
{
// Checked: delay = 2879, correct
int n = 30000;
check_sinusoid_shifted(n, 44100, 440.f, 2.0f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_up_octave_260)
{
// Checked: delay = 2879, seems ok
int n = 30000;
check_sinusoid_shifted(n, 44100, 260.f, 2.0f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_down_0_99)
{
int n = 30000;
check_sinusoid_shifted(n, 44100, 440.f, 0.99f, 0);
}
BOOST_AUTO_TEST_CASE(sinusoid_up_1_01)
{
int n = 30000;
check_sinusoid_shifted(n, 44100, 440.f, 1.01f, 0);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@@ -46,10 +46,10 @@ BOOST_AUTO_TEST_CASE(engine_version)
{
RubberBandStretcher s2(44100, 1, RubberBandStretcher::OptionEngineFaster);
BOOST_TEST(s2.getEngineVersion() == 2);
BOOST_TEST(s2.getProcessSizeLimit() == 524288);
BOOST_TEST(s2.getProcessSizeLimit() == 524288u);
RubberBandStretcher s3(44100, 1, RubberBandStretcher::OptionEngineFiner);
BOOST_TEST(s3.getEngineVersion() == 3);
BOOST_TEST(s3.getProcessSizeLimit() == 524288);
BOOST_TEST(s3.getProcessSizeLimit() == 524288u);
}
BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_faster)
@@ -76,10 +76,10 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_faster)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n);
BOOST_TEST(got == n);
BOOST_TEST(got == size_t(n));
BOOST_TEST(stretcher.available() == -1);
// We now have n samples of a simple sinusoid with stretch factor
@@ -133,10 +133,10 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_finer)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n);
BOOST_TEST(got == n);
BOOST_TEST(got == size_t(n));
BOOST_TEST(stretcher.available() == -1);
// The R3 engine is actually less precise than R2 here because of
@@ -186,10 +186,10 @@ BOOST_AUTO_TEST_CASE(sinusoid_2x_offline_finer)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n*2);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n*2);
BOOST_TEST(got == n*2);
BOOST_TEST(got == size_t(n)*2);
BOOST_TEST(stretcher.available() == -1);
int period = -1;
@@ -774,10 +774,10 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n * 2);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n * 2);
BOOST_TEST(got == n * 2);
BOOST_TEST(got == size_t(n) * 2);
BOOST_TEST(stretcher.available() == -1);
int peak0 = -1, peak1 = -1, peak2 = -1;
@@ -843,10 +843,10 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n * 2);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n * 2);
BOOST_TEST(got == n * 2);
BOOST_TEST(got == size_t(n) * 2);
BOOST_TEST(stretcher.available() == -1);
int peak0 = -1, peak1 = -1, peak2 = -1;
@@ -913,10 +913,10 @@ BOOST_AUTO_TEST_CASE(impulses_2x_5up_offline_finer)
stretcher.process(&inp, n, true);
BOOST_TEST(stretcher.available() == n * 2);
BOOST_TEST(stretcher.getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher.getStartDelay() == 0u); // offline mode
size_t got = stretcher.retrieve(&outp, n * 2);
BOOST_TEST(got == n * 2);
BOOST_TEST(got == size_t(n) * 2);
BOOST_TEST(stretcher.available() == -1);
int peak0 = -1, peak1 = -1, peak2 = -1;
@@ -1424,7 +1424,7 @@ static void with_resets(RubberBandStretcher::Options options,
stretcher->process(&inp, n, true);
BOOST_TEST(stretcher->available() == nOut);
BOOST_TEST(stretcher->getStartDelay() == 0); // offline mode
BOOST_TEST(stretcher->getStartDelay() == 0u); // offline mode
nActual = (int)stretcher->retrieve(&outp, nOut);
BOOST_TEST(nActual == nOut);