Add process mode; start on key-frame map

This commit is contained in:
Chris Cannam
2022-06-13 16:06:21 +01:00
parent 90ad1274d8
commit ac4072937e
3 changed files with 136 additions and 27 deletions

View File

@@ -140,7 +140,6 @@ void
RubberBandStretcher::setPitchOption(Options options) RubberBandStretcher::setPitchOption(Options options)
{ {
if (m_d) m_d->setPitchOption(options); if (m_d) m_d->setPitchOption(options);
else if (m_r3d) m_r3d->setPitchOption(options);
} }
void void
@@ -159,7 +158,7 @@ void
RubberBandStretcher::setKeyFrameMap(const std::map<size_t, size_t> &mapping) RubberBandStretcher::setKeyFrameMap(const std::map<size_t, size_t> &mapping)
{ {
if (m_d) m_d->setKeyFrameMap(mapping); if (m_d) m_d->setKeyFrameMap(mapping);
//!!! else m_r3d->setKeyFrameMap(mapping);
} }
size_t size_t
@@ -174,7 +173,7 @@ RubberBandStretcher::study(const float *const *input, size_t samples,
bool final) bool final)
{ {
if (m_d) m_d->study(input, samples, final); if (m_d) m_d->study(input, samples, final);
//!!! else m_r3d->study(input, samples, final);
} }
void void

View File

@@ -42,7 +42,11 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters,
m_inhop(1), m_inhop(1),
m_prevOuthop(1), m_prevOuthop(1),
m_startSkip(0), m_startSkip(0),
m_draining(false) m_studyInputDuration(0),
m_totalTargetDuration(0),
m_processInputDuration(0),
m_totalOutputDuration(0),
m_mode(ProcessMode::JustCreated)
{ {
double maxClassifierFrequency = 16000.0; double maxClassifierFrequency = 16000.0;
if (maxClassifierFrequency > m_parameters.sampleRate/2) { if (maxClassifierFrequency > m_parameters.sampleRate/2) {
@@ -92,7 +96,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters,
Resampler::Parameters resamplerParameters; Resampler::Parameters resamplerParameters;
resamplerParameters.quality = Resampler::FastestTolerable; resamplerParameters.quality = Resampler::FastestTolerable;
if (m_parameters.options & RubberBandStretcher::OptionProcessRealTime) { if (isRealTime()) {
resamplerParameters.dynamism = Resampler::RatioOftenChanging; resamplerParameters.dynamism = Resampler::RatioOftenChanging;
resamplerParameters.ratioChange = Resampler::SmoothRatioChange; resamplerParameters.ratioChange = Resampler::SmoothRatioChange;
} else { } else {
@@ -123,7 +127,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters,
// introduce more latency, and we don't want gaps when the ratio // introduce more latency, and we don't want gaps when the ratio
// changes. // changes.
if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { if (!isRealTime()) {
int pad = m_guideConfiguration.longestFftSize / 2; int pad = m_guideConfiguration.longestFftSize / 2;
for (int c = 0; c < m_parameters.channels; ++c) { for (int c = 0; c < m_parameters.channels; ++c) {
m_channelData[c]->inbuf->zero(pad); m_channelData[c]->inbuf->zero(pad);
@@ -163,6 +167,15 @@ R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize)
void void
R3StretcherImpl::setTimeRatio(double ratio) R3StretcherImpl::setTimeRatio(double ratio)
{ {
if (!isRealTime()) {
if (m_mode == ProcessMode::Studying ||
m_mode == ProcessMode::Processing) {
m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode");
return;
}
}
if (ratio == m_timeRatio) return;
m_timeRatio = ratio; m_timeRatio = ratio;
calculateHop(); calculateHop();
} }
@@ -170,6 +183,15 @@ R3StretcherImpl::setTimeRatio(double ratio)
void void
R3StretcherImpl::setPitchScale(double scale) R3StretcherImpl::setPitchScale(double scale)
{ {
if (!isRealTime()) {
if (m_mode == ProcessMode::Studying ||
m_mode == ProcessMode::Processing) {
m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode");
return;
}
}
if (scale == m_pitchScale) return;
m_pitchScale = scale; m_pitchScale = scale;
calculateHop(); calculateHop();
} }
@@ -177,6 +199,14 @@ R3StretcherImpl::setPitchScale(double scale)
void void
R3StretcherImpl::setFormantScale(double scale) R3StretcherImpl::setFormantScale(double scale)
{ {
if (!isRealTime()) {
if (m_mode == ProcessMode::Studying ||
m_mode == ProcessMode::Processing) {
m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode");
return;
}
}
m_formantScale = scale; m_formantScale = scale;
} }
@@ -190,16 +220,19 @@ R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options)
m_parameters.options |= options; m_parameters.options |= options;
} }
//!!! unused?
void void
R3StretcherImpl::setPitchOption(RubberBandStretcher::Options options) R3StretcherImpl::setKeyFrameMap(const std::map<size_t, size_t> &mapping)
{ {
int mask = (RubberBandStretcher::OptionPitchHighQuality | if (isRealTime()) {
RubberBandStretcher::OptionPitchHighSpeed | m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map in RT mode");
RubberBandStretcher::OptionPitchHighConsistency); return;
m_parameters.options &= ~mask; }
options &= mask; if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) {
m_parameters.options |= options; m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map after process() has begun");
return;
}
m_keyFrameMap = mapping;
} }
void void
@@ -229,6 +262,14 @@ R3StretcherImpl::calculateHop()
// std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; // std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl;
} }
void
R3StretcherImpl::updateRatioFromMap()
{
if (m_keyFrameMap.empty()) return;
auto itr = m_keyFrameMap.upper_bound(m_processInputDuration);
}
double double
R3StretcherImpl::getTimeRatio() const R3StretcherImpl::getTimeRatio() const
{ {
@@ -250,7 +291,7 @@ R3StretcherImpl::getFormantScale() const
size_t size_t
R3StretcherImpl::getLatency() const R3StretcherImpl::getLatency() const
{ {
if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { if (!isRealTime()) {
return 0; return 0;
} else { } else {
double factor = m_pitchScale * 0.5; double factor = m_pitchScale * 0.5;
@@ -280,6 +321,35 @@ R3StretcherImpl::reset()
m_prevInhop = m_inhop; m_prevInhop = m_inhop;
m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); m_prevOuthop = int(round(m_inhop * getEffectiveRatio()));
m_studyInputDuration = 0;
m_totalTargetDuration = 0;
m_processInputDuration = 0;
m_totalOutputDuration = 0;
m_keyFrameMap.clear();
m_mode = ProcessMode::JustCreated;
}
void
R3StretcherImpl::study(const float *const *, size_t samples, bool)
{
if (isRealTime()) {
m_parameters.logger("R3StretcherImpl::study: Not meaningful in realtime mode");
return;
}
if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) {
m_parameters.logger("R3StretcherImpl::study: Cannot study after processing");
return;
}
if (m_mode == ProcessMode::JustCreated) {
m_studyInputDuration = 0;
}
m_mode = ProcessMode::Studying;
m_studyInputDuration += samples;
} }
size_t size_t
@@ -301,10 +371,26 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final)
{ {
//!!! todo: final //!!! todo: final
//!!! m_parameters.logger("process called"); if (m_mode == ProcessMode::Finished) {
m_parameters.logger("R3StretcherImpl::process: Cannot process again after final chunk");
return;
}
if (!isRealTime() && !m_keyFrameMap.empty()) {
if (m_mode == ProcessMode::Studying) {
m_totalTargetDuration =
round(m_studyInputDuration * getEffectiveRatio());
}
}
if (final) { if (final) {
// m_parameters.logger("final = true"); // We don't distinguish between Finished and "draining, but
m_draining = true; // haven't yet delivered all the samples" because the
// distinction is meaningless internally - it only affects
// whether available() finds any samples in the buffer
m_mode = ProcessMode::Finished;
} else {
m_mode = ProcessMode::Processing;
} }
bool allConsumed = false; bool allConsumed = false;
@@ -324,6 +410,8 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final)
m_channelData[c]->inbuf->write(input[c], samples); m_channelData[c]->inbuf->write(input[c], samples);
} }
m_processInputDuration += samples;
consume(); consume();
} }
@@ -331,17 +419,18 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final)
int int
R3StretcherImpl::available() const R3StretcherImpl::available() const
{ {
//!!! m_parameters.logger("available called");
int av = int(m_channelData[0]->outbuf->getReadSpace()); int av = int(m_channelData[0]->outbuf->getReadSpace());
if (av == 0 && m_draining) return -1; if (av == 0 && m_mode == ProcessMode::Finished) {
else return av; return -1;
} else {
return av;
}
} }
//!!! __attribute__((annotate("realtime"))) //!!! __attribute__((annotate("realtime")))
size_t size_t
R3StretcherImpl::retrieve(float *const *output, size_t samples) const R3StretcherImpl::retrieve(float *const *output, size_t samples) const
{ {
//!!! m_parameters.logger("retrieve called");
size_t got = samples; size_t got = samples;
for (size_t c = 0; c < m_parameters.channels; ++c) { for (size_t c = 0; c < m_parameters.channels; ++c) {
@@ -411,7 +500,7 @@ R3StretcherImpl::consume()
int readSpace = m_channelData.at(0)->inbuf->getReadSpace(); int readSpace = m_channelData.at(0)->inbuf->getReadSpace();
if (readSpace < longest) { if (readSpace < longest) {
if (m_draining) { if (m_mode == ProcessMode::Finished) {
if (readSpace == 0) { if (readSpace == 0) {
break; break;
} }
@@ -475,7 +564,7 @@ R3StretcherImpl::consume()
m_channelAssembly.mixdown.data(), m_channelAssembly.mixdown.data(),
outhop, outhop,
1.0 / m_pitchScale, 1.0 / m_pitchScale,
m_draining && readSpace < longest); m_mode == ProcessMode::Finished && readSpace < longest);
} }
// Emit // Emit
@@ -490,7 +579,7 @@ R3StretcherImpl::consume()
int readSpace = cd->inbuf->getReadSpace(); int readSpace = cd->inbuf->getReadSpace();
if (readSpace < inhop) { if (readSpace < inhop) {
// This should happen only when draining // This should happen only when draining (Finished)
cd->inbuf->skip(readSpace); cd->inbuf->skip(readSpace);
} else { } else {
cd->inbuf->skip(inhop); cd->inbuf->skip(inhop);

View File

@@ -75,9 +75,11 @@ public:
double getPitchScale() const; double getPitchScale() const;
double getFormantScale() const; double getFormantScale() const;
void setFormantOption(RubberBandStretcher::Options); void setKeyFrameMap(const std::map<size_t, size_t> &);
void setPitchOption(RubberBandStretcher::Options);
void setFormantOption(RubberBandStretcher::Options);
void study(const float *const *input, size_t samples, bool final);
size_t getSamplesRequired() const; size_t getSamplesRequired() const;
void process(const float *const *input, size_t samples, bool final); void process(const float *const *input, size_t samples, bool final);
int available() const; int available() const;
@@ -280,10 +282,24 @@ protected:
int m_prevInhop; int m_prevInhop;
int m_prevOuthop; int m_prevOuthop;
int m_startSkip; int m_startSkip;
bool m_draining;
size_t m_studyInputDuration;
size_t m_totalTargetDuration;
size_t m_processInputDuration;
size_t m_totalOutputDuration;
std::map<size_t, size_t> m_keyFrameMap;
enum class ProcessMode {
JustCreated,
Studying,
Processing,
Finished
};
ProcessMode m_mode;
void consume(); void consume();
void calculateHop(); void calculateHop();
void updateRatioFromMap();
void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop);
void analyseFormant(int channel); void analyseFormant(int channel);
void adjustFormant(int channel); void adjustFormant(int channel);
@@ -294,6 +310,11 @@ protected:
return m_timeRatio * m_pitchScale; return m_timeRatio * m_pitchScale;
} }
bool isRealTime() const {
return m_parameters.options &
RubberBandStretcher::OptionProcessRealTime;
}
static void logCout(const std::string &message) { static void logCout(const std::string &message) {
std::cout << "RubberBandStretcher: " << message << std::endl; std::cout << "RubberBandStretcher: " << message << std::endl;
} }