Merge from branch rblive
This commit is contained in:
@@ -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,
|
FFTW3 -Dfft=fftw -DHAVE_FFTW3 A bit faster than built-in,
|
||||||
a bit slower than vDSP.
|
a bit slower than vDSP.
|
||||||
GPL licence.
|
GPL with commercial option.
|
||||||
|
|
||||||
SLEEF -Dfft=sleef -DHAVE_SLEEF Usually very fast. Not as widely
|
SLEEF -Dfft=sleef -DHAVE_SLEEF Usually very fast. Not as widely
|
||||||
distributed as FFTW3. Requires
|
distributed as FFTW3. Requires
|
||||||
|
|||||||
44
CONTRIBUTING.md
Normal file
44
CONTRIBUTING.md
Normal 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.
|
||||||
|
|
||||||
@@ -12,7 +12,6 @@ tempo and pitch of an audio recording independently of one another.
|
|||||||
|
|
||||||
* About Rubber Band: https://breakfastquay.com/rubberband/
|
* About Rubber Band: https://breakfastquay.com/rubberband/
|
||||||
* Code repository: https://hg.sr.ht/~breakfastquay/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
|
* Github mirror: https://github.com/breakfastquay/rubberband
|
||||||
|
|
||||||
CI builds:
|
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
|
our knowledge. See also the file [COMPILING.md](COMPILING.md) for more
|
||||||
details.
|
details.
|
||||||
|
|
||||||
* FFTW3 - GPL; proprietary licence needed for redistribution
|
* FFTW3 - GPL with commercial proprietary option
|
||||||
* Intel IPP - Proprietary; licence needed for redistribution
|
* Intel IPP - Proprietary of some nature
|
||||||
* SLEEF - BSD-like
|
* SLEEF - BSD-like
|
||||||
* KissFFT - BSD-like
|
* KissFFT - BSD-like
|
||||||
* libsamplerate - BSD-like from version 0.1.9 onwards
|
* libsamplerate - BSD-like from version 0.1.9 onwards
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ cpu = 'x86_64'
|
|||||||
system = 'darwin'
|
system = 'darwin'
|
||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
|
[properties]
|
||||||
|
needs_exe_wrapper = true
|
||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
c = 'cc'
|
c = 'cc'
|
||||||
cpp = 'c++'
|
cpp = 'c++'
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ cpu = 'aarch64'
|
|||||||
system = 'darwin'
|
system = 'darwin'
|
||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
|
[properties]
|
||||||
|
needs_exe_wrapper = true
|
||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
c = 'cc'
|
c = 'cc'
|
||||||
cpp = 'c++'
|
cpp = 'c++'
|
||||||
|
|||||||
564
ladspa-lv2/RubberBandLivePitchShifter.cpp
Normal file
564
ladspa-lv2/RubberBandLivePitchShifter.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
148
ladspa-lv2/RubberBandLivePitchShifter.h
Normal file
148
ladspa-lv2/RubberBandLivePitchShifter.h
Normal 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
|
||||||
@@ -34,6 +34,7 @@ using std::cout;
|
|||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using std::min;
|
using std::min;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
#ifdef RB_PLUGIN_LADSPA
|
#ifdef RB_PLUGIN_LADSPA
|
||||||
|
|
||||||
@@ -356,18 +357,18 @@ RubberBandPitchShifter::instantiate(const LV2_Descriptor *desc, double rate,
|
|||||||
const char *, const LV2_Feature *const *)
|
const char *, const LV2_Feature *const *)
|
||||||
{
|
{
|
||||||
if (rate < 1.0) {
|
if (rate < 1.0) {
|
||||||
std::cerr << "RubberBandPitchShifter::instantiate: invalid sample rate "
|
cerr << "RubberBandPitchShifter::instantiate: invalid sample rate "
|
||||||
<< rate << " provided" << std::endl;
|
<< rate << " provided" << endl;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
size_t srate = size_t(round(rate));
|
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);
|
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);
|
return new RubberBandPitchShifter(srate, 2);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "RubberBandPitchShifter::instantiate: unrecognised URI "
|
cerr << "RubberBandPitchShifter::instantiate: unrecognised URI "
|
||||||
<< desc->URI << " requested" << std::endl;
|
<< desc->URI << " requested" << endl;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,7 +643,6 @@ RubberBandPitchShifter::runImpl(uint32_t insamples, uint32_t offset)
|
|||||||
|
|
||||||
const int samples = insamples;
|
const int samples = insamples;
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
size_t outTotal = 0;
|
|
||||||
|
|
||||||
while (processed < samples) {
|
while (processed < samples) {
|
||||||
|
|
||||||
@@ -671,7 +671,6 @@ RubberBandPitchShifter::runImpl(uint32_t insamples, uint32_t offset)
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
|
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
|
||||||
outTotal += actual;
|
|
||||||
|
|
||||||
for (size_t c = 0; c < m_channels; ++c) {
|
for (size_t c = 0; c < m_channels; ++c) {
|
||||||
m_outputBuffer[c]->write(m_scratch[c], actual);
|
m_outputBuffer[c]->write(m_scratch[c], actual);
|
||||||
|
|||||||
@@ -30,10 +30,9 @@
|
|||||||
|
|
||||||
using namespace RubberBand;
|
using namespace RubberBand;
|
||||||
|
|
||||||
using std::cout;
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using std::min;
|
using std::string;
|
||||||
|
|
||||||
#ifdef RB_PLUGIN_LADSPA
|
#ifdef RB_PLUGIN_LADSPA
|
||||||
|
|
||||||
@@ -341,18 +340,18 @@ RubberBandR3PitchShifter::instantiate(const LV2_Descriptor *desc, double rate,
|
|||||||
const char *, const LV2_Feature *const *)
|
const char *, const LV2_Feature *const *)
|
||||||
{
|
{
|
||||||
if (rate < 1.0) {
|
if (rate < 1.0) {
|
||||||
std::cerr << "RubberBandR3PitchShifter::instantiate: invalid sample rate "
|
cerr << "RubberBandR3PitchShifter::instantiate: invalid sample rate "
|
||||||
<< rate << " provided" << std::endl;
|
<< rate << " provided" << endl;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
size_t srate = size_t(round(rate));
|
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);
|
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);
|
return new RubberBandR3PitchShifter(srate, 2);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "RubberBandR3PitchShifter::instantiate: unrecognised URI "
|
cerr << "RubberBandR3PitchShifter::instantiate: unrecognised URI "
|
||||||
<< desc->URI << " requested" << std::endl;
|
<< desc->URI << " requested" << endl;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,7 +592,6 @@ RubberBandR3PitchShifter::runImpl(uint32_t insamples, uint32_t offset)
|
|||||||
|
|
||||||
const int samples = insamples;
|
const int samples = insamples;
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
size_t outTotal = 0;
|
|
||||||
|
|
||||||
while (processed < samples) {
|
while (processed < samples) {
|
||||||
|
|
||||||
@@ -622,7 +620,6 @@ RubberBandR3PitchShifter::runImpl(uint32_t insamples, uint32_t offset)
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
|
size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
|
||||||
outTotal += actual;
|
|
||||||
|
|
||||||
for (size_t c = 0; c < m_channels; ++c) {
|
for (size_t c = 0; c < m_channels; ++c) {
|
||||||
m_outputBuffer[c]->write(m_scratch[c], actual);
|
m_outputBuffer[c]->write(m_scratch[c], actual);
|
||||||
|
|||||||
@@ -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-pitchshifter-stereo::Frequency > Pitch shifters
|
||||||
ladspa:ladspa-rubberband:rubberband-r3-pitchshifter-mono::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-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
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
|
|
||||||
<ladspa:PitchPlugin rdf:about="&ladspa;2979"/>
|
<ladspa:PitchPlugin rdf:about="&ladspa;2979"/>
|
||||||
<ladspa:PitchPlugin rdf:about="&ladspa;9792"/>
|
<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>
|
</rdf:RDF>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#undef RB_PLUGIN_LV2
|
#undef RB_PLUGIN_LV2
|
||||||
#include "RubberBandPitchShifter.cpp"
|
#include "RubberBandPitchShifter.cpp"
|
||||||
#include "RubberBandR3PitchShifter.cpp"
|
#include "RubberBandR3PitchShifter.cpp"
|
||||||
|
#include "RubberBandLivePitchShifter.cpp"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -34,8 +35,10 @@ const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
|
|||||||
{
|
{
|
||||||
if (index < 2) {
|
if (index < 2) {
|
||||||
return RubberBandPitchShifter::getDescriptor(index);
|
return RubberBandPitchShifter::getDescriptor(index);
|
||||||
} else {
|
} else if (index < 4) {
|
||||||
return RubberBandR3PitchShifter::getDescriptor(index - 2);
|
return RubberBandR3PitchShifter::getDescriptor(index - 2);
|
||||||
|
} else {
|
||||||
|
return RubberBandLivePitchShifter::getDescriptor(index - 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#undef RB_PLUGIN_LADSPA
|
#undef RB_PLUGIN_LADSPA
|
||||||
#include "RubberBandPitchShifter.cpp"
|
#include "RubberBandPitchShifter.cpp"
|
||||||
#include "RubberBandR3PitchShifter.cpp"
|
#include "RubberBandR3PitchShifter.cpp"
|
||||||
|
#include "RubberBandLivePitchShifter.cpp"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -35,8 +36,10 @@ const LV2_Descriptor *lv2_descriptor(uint32_t index)
|
|||||||
{
|
{
|
||||||
if (index < 2) {
|
if (index < 2) {
|
||||||
return RubberBandPitchShifter::getDescriptor(index);
|
return RubberBandPitchShifter::getDescriptor(index);
|
||||||
} else {
|
} else if (index < 4) {
|
||||||
return RubberBandR3PitchShifter::getDescriptor(index - 2);
|
return RubberBandR3PitchShifter::getDescriptor(index - 2);
|
||||||
|
} else {
|
||||||
|
return RubberBandLivePitchShifter::getDescriptor(index - 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,43 @@ rubberband:r3mono
|
|||||||
lv2:designation pg:center ;
|
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
|
rubberband:stereo
|
||||||
a doap:Project, lv2:Plugin, lv2:PitchPlugin ;
|
a doap:Project, lv2:Plugin, lv2:PitchPlugin ;
|
||||||
doap:name "Rubber Band Stereo Pitch Shifter" ;
|
doap:name "Rubber Band Stereo Pitch Shifter" ;
|
||||||
@@ -306,3 +343,55 @@ rubberband:r3stereo
|
|||||||
lv2:designation pg:right ;
|
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 ;
|
||||||
|
] .
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ rubberband:r3mono
|
|||||||
lv2:binary <lv2-rubberband.so> ;
|
lv2:binary <lv2-rubberband.so> ;
|
||||||
rdfs:seeAlso <lv2-rubberband.ttl> .
|
rdfs:seeAlso <lv2-rubberband.ttl> .
|
||||||
|
|
||||||
|
rubberband:livemono
|
||||||
|
a lv2:Plugin ;
|
||||||
|
lv2:binary <lv2-rubberband.so> ;
|
||||||
|
rdfs:seeAlso <lv2-rubberband.ttl> .
|
||||||
|
|
||||||
rubberband:stereo
|
rubberband:stereo
|
||||||
a lv2:Plugin ;
|
a lv2:Plugin ;
|
||||||
lv2:binary <lv2-rubberband.so> ;
|
lv2:binary <lv2-rubberband.so> ;
|
||||||
@@ -22,3 +27,8 @@ rubberband:r3stereo
|
|||||||
lv2:binary <lv2-rubberband.so> ;
|
lv2:binary <lv2-rubberband.so> ;
|
||||||
rdfs:seeAlso <lv2-rubberband.ttl> .
|
rdfs:seeAlso <lv2-rubberband.ttl> .
|
||||||
|
|
||||||
|
rubberband:livestereo
|
||||||
|
a lv2:Plugin ;
|
||||||
|
lv2:binary <lv2-rubberband.so> ;
|
||||||
|
rdfs:seeAlso <lv2-rubberband.ttl> .
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ pkg = import('pkgconfig')
|
|||||||
public_headers = [
|
public_headers = [
|
||||||
'rubberband/rubberband-c.h',
|
'rubberband/rubberband-c.h',
|
||||||
'rubberband/RubberBandStretcher.h',
|
'rubberband/RubberBandStretcher.h',
|
||||||
|
'rubberband/RubberBandLiveShifter.h',
|
||||||
]
|
]
|
||||||
|
|
||||||
library_sources = [
|
library_sources = [
|
||||||
'src/rubberband-c.cpp',
|
'src/rubberband-c.cpp',
|
||||||
'src/RubberBandStretcher.cpp',
|
'src/RubberBandStretcher.cpp',
|
||||||
|
'src/RubberBandLiveShifter.cpp',
|
||||||
'src/faster/AudioCurveCalculator.cpp',
|
'src/faster/AudioCurveCalculator.cpp',
|
||||||
'src/faster/CompoundAudioCurve.cpp',
|
'src/faster/CompoundAudioCurve.cpp',
|
||||||
'src/faster/HighFrequencyAudioCurve.cpp',
|
'src/faster/HighFrequencyAudioCurve.cpp',
|
||||||
@@ -52,6 +54,7 @@ library_sources = [
|
|||||||
'src/common/mathmisc.cpp',
|
'src/common/mathmisc.cpp',
|
||||||
'src/common/Thread.cpp',
|
'src/common/Thread.cpp',
|
||||||
'src/finer/R3Stretcher.cpp',
|
'src/finer/R3Stretcher.cpp',
|
||||||
|
'src/finer/R3LiveShifter.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
jni_sources = [
|
jni_sources = [
|
||||||
@@ -93,6 +96,7 @@ lv2_sources = [
|
|||||||
unit_test_sources = [
|
unit_test_sources = [
|
||||||
'src/test/TestAllocators.cpp',
|
'src/test/TestAllocators.cpp',
|
||||||
'src/test/TestFFT.cpp',
|
'src/test/TestFFT.cpp',
|
||||||
|
'src/test/TestLiveShifter.cpp',
|
||||||
'src/test/TestResampler.cpp',
|
'src/test/TestResampler.cpp',
|
||||||
'src/test/TestVectorOpsComplex.cpp',
|
'src/test/TestVectorOpsComplex.cpp',
|
||||||
'src/test/TestVectorOps.cpp',
|
'src/test/TestVectorOps.cpp',
|
||||||
|
|||||||
348
rubberband/RubberBandLiveShifter.h
Normal file
348
rubberband/RubberBandLiveShifter.h
Normal 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
|
||||||
273
src/RubberBandLiveShifter.cpp
Normal file
273
src/RubberBandLiveShifter.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ public:
|
|||||||
<< guidance.phaseReset.f0 << " to " << guidance.phaseReset.f1
|
<< guidance.phaseReset.f0 << " to " << guidance.phaseReset.f1
|
||||||
<< "]" << std::endl;
|
<< "]" << 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;
|
double m_maxHigher;
|
||||||
|
|
||||||
void updateForSilence(Guidance &guidance) const {
|
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;
|
double nyquist = m_parameters.sampleRate / 2.0;
|
||||||
if (!m_parameters.singleWindowMode) {
|
if (!m_parameters.singleWindowMode) {
|
||||||
guidance.fftBands[0].f0 = 0.0;
|
guidance.fftBands[0].f0 = 0.0;
|
||||||
@@ -498,7 +500,7 @@ protected:
|
|||||||
const BinSegmenter::Segmentation &segmentation,
|
const BinSegmenter::Segmentation &segmentation,
|
||||||
bool realtime) const {
|
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;
|
double nyquist = m_parameters.sampleRate / 2.0;
|
||||||
|
|
||||||
@@ -533,9 +535,9 @@ protected:
|
|||||||
if (!hadPhaseReset) {
|
if (!hadPhaseReset) {
|
||||||
guidance.phaseReset.f0 = 16000.0;
|
guidance.phaseReset.f0 = 16000.0;
|
||||||
guidance.phaseReset.f1 = nyquist;
|
guidance.phaseReset.f1 = nyquist;
|
||||||
// std::cout << "f0 = " << guidance.phaseReset.f0 << std::endl;
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
m_log.log(2, "Guide::updateForUnity: had phase reset");
|
||||||
guidance.phaseReset.f0 *= 0.9;
|
guidance.phaseReset.f0 *= 0.9;
|
||||||
guidance.phaseReset.f1 *= 1.1;
|
guidance.phaseReset.f1 *= 1.1;
|
||||||
}
|
}
|
||||||
@@ -553,10 +555,7 @@ protected:
|
|||||||
guidance.phaseReset.f0 = 0.0;
|
guidance.phaseReset.f0 = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (guidance.phaseReset.f0 > 0.0) {
|
m_log.log(2, "Guide::updateForUnity: f0 and f1", guidance.phaseReset.f0, guidance.phaseReset.f1);
|
||||||
// std::cout << unityCount << ": f0 = " << guidance.phaseReset.f0
|
|
||||||
// << ", f1 = " << guidance.phaseReset.f1 << std::endl;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkPotentialKick(const process_t *const magnitudes,
|
bool checkPotentialKick(const process_t *const magnitudes,
|
||||||
|
|||||||
1202
src/finer/R3LiveShifter.cpp
Normal file
1202
src/finer/R3LiveShifter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
423
src/finer/R3LiveShifter.h
Normal file
423
src/finer/R3LiveShifter.h
Normal 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 ¶ms) {
|
||||||
|
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
|
||||||
@@ -300,6 +300,10 @@ R3Stretcher::createResampler()
|
|||||||
resamplerParameters.ratioChange = Resampler::SuddenRatioChange;
|
resamplerParameters.ratioChange = Resampler::SuddenRatioChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int debug = m_log.getDebugLevel();
|
||||||
|
if (debug > 0) --debug;
|
||||||
|
resamplerParameters.debugLevel = debug;
|
||||||
|
|
||||||
m_resampler = std::unique_ptr<Resampler>
|
m_resampler = std::unique_ptr<Resampler>
|
||||||
(new Resampler(resamplerParameters, m_parameters.channels));
|
(new Resampler(resamplerParameters, m_parameters.channels));
|
||||||
|
|
||||||
|
|||||||
298
src/test/TestLiveShifter.cpp
Normal file
298
src/test/TestLiveShifter.cpp
Normal 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()
|
||||||
@@ -46,10 +46,10 @@ BOOST_AUTO_TEST_CASE(engine_version)
|
|||||||
{
|
{
|
||||||
RubberBandStretcher s2(44100, 1, RubberBandStretcher::OptionEngineFaster);
|
RubberBandStretcher s2(44100, 1, RubberBandStretcher::OptionEngineFaster);
|
||||||
BOOST_TEST(s2.getEngineVersion() == 2);
|
BOOST_TEST(s2.getEngineVersion() == 2);
|
||||||
BOOST_TEST(s2.getProcessSizeLimit() == 524288);
|
BOOST_TEST(s2.getProcessSizeLimit() == 524288u);
|
||||||
RubberBandStretcher s3(44100, 1, RubberBandStretcher::OptionEngineFiner);
|
RubberBandStretcher s3(44100, 1, RubberBandStretcher::OptionEngineFiner);
|
||||||
BOOST_TEST(s3.getEngineVersion() == 3);
|
BOOST_TEST(s3.getEngineVersion() == 3);
|
||||||
BOOST_TEST(s3.getProcessSizeLimit() == 524288);
|
BOOST_TEST(s3.getProcessSizeLimit() == 524288u);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_faster)
|
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);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n);
|
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);
|
size_t got = stretcher.retrieve(&outp, n);
|
||||||
BOOST_TEST(got == n);
|
BOOST_TEST(got == size_t(n));
|
||||||
BOOST_TEST(stretcher.available() == -1);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
// We now have n samples of a simple sinusoid with stretch factor
|
// 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);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n);
|
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);
|
size_t got = stretcher.retrieve(&outp, n);
|
||||||
BOOST_TEST(got == n);
|
BOOST_TEST(got == size_t(n));
|
||||||
BOOST_TEST(stretcher.available() == -1);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
// The R3 engine is actually less precise than R2 here because of
|
// 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);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n*2);
|
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);
|
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);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
int period = -1;
|
int period = -1;
|
||||||
@@ -774,10 +774,10 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster)
|
|||||||
stretcher.process(&inp, n, true);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n * 2);
|
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);
|
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);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
int peak0 = -1, peak1 = -1, peak2 = -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);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n * 2);
|
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);
|
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);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
int peak0 = -1, peak1 = -1, peak2 = -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);
|
stretcher.process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher.available() == n * 2);
|
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);
|
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);
|
BOOST_TEST(stretcher.available() == -1);
|
||||||
|
|
||||||
int peak0 = -1, peak1 = -1, peak2 = -1;
|
int peak0 = -1, peak1 = -1, peak2 = -1;
|
||||||
@@ -1424,7 +1424,7 @@ static void with_resets(RubberBandStretcher::Options options,
|
|||||||
stretcher->process(&inp, n, true);
|
stretcher->process(&inp, n, true);
|
||||||
BOOST_TEST(stretcher->available() == nOut);
|
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);
|
nActual = (int)stretcher->retrieve(&outp, nOut);
|
||||||
BOOST_TEST(nActual == nOut);
|
BOOST_TEST(nActual == nOut);
|
||||||
|
|||||||
Reference in New Issue
Block a user