cannam@95: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@95: cannam@95: /* cannam@95: Rubber Band Library cannam@95: An audio time-stretching and pitch-shifting library. cannam@95: Copyright 2007-2012 Particular Programs Ltd. cannam@95: cannam@95: This program is free software; you can redistribute it and/or cannam@95: modify it under the terms of the GNU General Public License as cannam@95: published by the Free Software Foundation; either version 2 of the cannam@95: License, or (at your option) any later version. See the file cannam@95: COPYING included with this distribution for more information. cannam@95: cannam@95: Alternatively, if you have a valid commercial licence for the cannam@95: Rubber Band Library obtained by agreement with the copyright cannam@95: holders, you may redistribute and/or modify it under the terms cannam@95: described in that licence. cannam@95: cannam@95: If you wish to distribute code using the Rubber Band Library cannam@95: under terms other than those of the GNU General Public License, cannam@95: you must obtain a valid commercial licence before doing so. cannam@95: */ cannam@95: cannam@95: #include "RubberBandPitchShifter.h" cannam@95: cannam@95: #include "RubberBandStretcher.h" cannam@95: cannam@95: #include cannam@95: #include cannam@95: cannam@95: using namespace RubberBand; cannam@95: cannam@95: using std::cout; cannam@95: using std::cerr; cannam@95: using std::endl; cannam@95: using std::min; cannam@95: cannam@95: const char *const cannam@95: RubberBandPitchShifter::portNamesMono[PortCountMono] = cannam@95: { cannam@95: "latency", cannam@95: "Cents", cannam@95: "Semitones", cannam@95: "Octaves", cannam@95: "Crispness", cannam@95: "Formant Preserving", cannam@95: "Faster", cannam@95: "Input", cannam@95: "Output" cannam@95: }; cannam@95: cannam@95: const char *const cannam@95: RubberBandPitchShifter::portNamesStereo[PortCountStereo] = cannam@95: { cannam@95: "latency", cannam@95: "Cents", cannam@95: "Semitones", cannam@95: "Octaves", cannam@95: "Crispness", cannam@95: "Formant Preserving", cannam@95: "Faster", cannam@95: "Input L", cannam@95: "Output L", cannam@95: "Input R", cannam@95: "Output R" cannam@95: }; cannam@95: cannam@95: const LADSPA_PortDescriptor cannam@95: RubberBandPitchShifter::portsMono[PortCountMono] = cannam@95: { cannam@95: LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, cannam@95: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO cannam@95: }; cannam@95: cannam@95: const LADSPA_PortDescriptor cannam@95: RubberBandPitchShifter::portsStereo[PortCountStereo] = cannam@95: { cannam@95: LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, cannam@95: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, cannam@95: LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, cannam@95: LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO cannam@95: }; cannam@95: cannam@95: const LADSPA_PortRangeHint cannam@95: RubberBandPitchShifter::hintsMono[PortCountMono] = cannam@95: { cannam@95: { 0, 0, 0 }, // latency cannam@95: { LADSPA_HINT_DEFAULT_0 | // cents cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE, cannam@95: -100.0, 100.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // semitones cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: -12.0, 12.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // octaves cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: -3.0, 3.0 }, cannam@95: { LADSPA_HINT_DEFAULT_MAXIMUM | // crispness cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: 0.0, 3.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // formant preserving cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_TOGGLED, cannam@95: 0.0, 1.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // fast cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_TOGGLED, cannam@95: 0.0, 1.0 }, cannam@95: { 0, 0, 0 }, cannam@95: { 0, 0, 0 } cannam@95: }; cannam@95: cannam@95: const LADSPA_PortRangeHint cannam@95: RubberBandPitchShifter::hintsStereo[PortCountStereo] = cannam@95: { cannam@95: { 0, 0, 0 }, // latency cannam@95: { LADSPA_HINT_DEFAULT_0 | // cents cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE, cannam@95: -100.0, 100.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // semitones cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: -12.0, 12.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // octaves cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: -3.0, 3.0 }, cannam@95: { LADSPA_HINT_DEFAULT_MAXIMUM | // crispness cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_INTEGER, cannam@95: 0.0, 3.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // formant preserving cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_TOGGLED, cannam@95: 0.0, 1.0 }, cannam@95: { LADSPA_HINT_DEFAULT_0 | // fast cannam@95: LADSPA_HINT_BOUNDED_BELOW | cannam@95: LADSPA_HINT_BOUNDED_ABOVE | cannam@95: LADSPA_HINT_TOGGLED, cannam@95: 0.0, 1.0 }, cannam@95: { 0, 0, 0 }, cannam@95: { 0, 0, 0 }, cannam@95: { 0, 0, 0 }, cannam@95: { 0, 0, 0 } cannam@95: }; cannam@95: cannam@95: const LADSPA_Properties cannam@95: RubberBandPitchShifter::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; cannam@95: cannam@95: const LADSPA_Descriptor cannam@95: RubberBandPitchShifter::ladspaDescriptorMono = cannam@95: { cannam@95: 2979, // "Unique" ID cannam@95: "rubberband-pitchshifter-mono", // Label cannam@95: properties, cannam@95: "Rubber Band Mono Pitch Shifter", // Name cannam@95: "Breakfast Quay", cannam@95: "GPL", cannam@95: PortCountMono, cannam@95: portsMono, cannam@95: portNamesMono, cannam@95: hintsMono, cannam@95: 0, // Implementation data cannam@95: instantiate, cannam@95: connectPort, cannam@95: activate, cannam@95: run, cannam@95: 0, // Run adding cannam@95: 0, // Set run adding gain cannam@95: deactivate, cannam@95: cleanup cannam@95: }; cannam@95: cannam@95: const LADSPA_Descriptor cannam@95: RubberBandPitchShifter::ladspaDescriptorStereo = cannam@95: { cannam@95: 9792, // "Unique" ID cannam@95: "rubberband-pitchshifter-stereo", // Label cannam@95: properties, cannam@95: "Rubber Band Stereo Pitch Shifter", // Name cannam@95: "Breakfast Quay", cannam@95: "GPL", cannam@95: PortCountStereo, cannam@95: portsStereo, cannam@95: portNamesStereo, cannam@95: hintsStereo, cannam@95: 0, // Implementation data cannam@95: instantiate, cannam@95: connectPort, cannam@95: activate, cannam@95: run, cannam@95: 0, // Run adding cannam@95: 0, // Set run adding gain cannam@95: deactivate, cannam@95: cleanup cannam@95: }; cannam@95: cannam@95: const LADSPA_Descriptor * cannam@95: RubberBandPitchShifter::getDescriptor(unsigned long index) cannam@95: { cannam@95: if (index == 0) return &ladspaDescriptorMono; cannam@95: if (index == 1) return &ladspaDescriptorStereo; cannam@95: else return 0; cannam@95: } cannam@95: cannam@95: RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) : cannam@95: m_latency(0), cannam@95: m_cents(0), cannam@95: m_semitones(0), cannam@95: m_octaves(0), cannam@95: m_crispness(0), cannam@95: m_formant(0), cannam@95: m_fast(0), cannam@95: m_ratio(1.0), cannam@95: m_prevRatio(1.0), cannam@95: m_currentCrispness(-1), cannam@95: m_currentFormant(false), cannam@95: m_currentFast(false), cannam@95: m_blockSize(1024), cannam@95: m_reserve(1024), cannam@95: m_minfill(0), cannam@95: m_stretcher(new RubberBandStretcher cannam@95: (sampleRate, channels, cannam@95: RubberBandStretcher::OptionProcessRealTime | cannam@95: RubberBandStretcher::OptionPitchHighConsistency)), cannam@95: m_sampleRate(sampleRate), cannam@95: m_channels(channels) cannam@95: { cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: cannam@95: m_input[c] = 0; cannam@95: m_output[c] = 0; cannam@95: cannam@95: int bufsize = m_blockSize + m_reserve + 8192; cannam@95: cannam@95: m_outputBuffer[c] = new RingBuffer(bufsize); cannam@95: cannam@95: m_scratch[c] = new float[bufsize]; cannam@95: for (int i = 0; i < bufsize; ++i) m_scratch[c][i] = 0.f; cannam@95: } cannam@95: cannam@95: activateImpl(); cannam@95: } cannam@95: cannam@95: RubberBandPitchShifter::~RubberBandPitchShifter() cannam@95: { cannam@95: delete m_stretcher; cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: delete m_outputBuffer[c]; cannam@95: delete[] m_scratch[c]; cannam@95: } cannam@95: } cannam@95: cannam@95: LADSPA_Handle cannam@95: RubberBandPitchShifter::instantiate(const LADSPA_Descriptor *desc, unsigned long rate) cannam@95: { cannam@95: if (desc->PortCount == ladspaDescriptorMono.PortCount) { cannam@95: return new RubberBandPitchShifter(rate, 1); cannam@95: } else if (desc->PortCount == ladspaDescriptorStereo.PortCount) { cannam@95: return new RubberBandPitchShifter(rate, 2); cannam@95: } cannam@95: return 0; cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::connectPort(LADSPA_Handle handle, cannam@95: unsigned long port, LADSPA_Data *location) cannam@95: { cannam@95: RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; cannam@95: cannam@95: float **ports[PortCountStereo] = { cannam@95: &shifter->m_latency, cannam@95: &shifter->m_cents, cannam@95: &shifter->m_semitones, cannam@95: &shifter->m_octaves, cannam@95: &shifter->m_crispness, cannam@95: &shifter->m_formant, cannam@95: &shifter->m_fast, cannam@95: &shifter->m_input[0], cannam@95: &shifter->m_output[0], cannam@95: &shifter->m_input[1], cannam@95: &shifter->m_output[1] cannam@95: }; cannam@95: cannam@95: if (shifter->m_channels == 1) { cannam@95: if (port >= PortCountMono) return; cannam@95: } else { cannam@95: if (port >= PortCountStereo) return; cannam@95: } cannam@95: cannam@95: *ports[port] = (float *)location; cannam@95: cannam@95: if (shifter->m_latency) { cannam@95: *(shifter->m_latency) = cannam@95: float(shifter->m_stretcher->getLatency() + shifter->m_reserve); cannam@95: } cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::activate(LADSPA_Handle handle) cannam@95: { cannam@95: RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; cannam@95: shifter->activateImpl(); cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::activateImpl() cannam@95: { cannam@95: updateRatio(); cannam@95: m_prevRatio = m_ratio; cannam@95: m_stretcher->reset(); cannam@95: m_stretcher->setPitchScale(m_ratio); cannam@95: cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: m_outputBuffer[c]->reset(); cannam@95: m_outputBuffer[c]->zero(m_reserve); cannam@95: } cannam@95: cannam@95: m_minfill = 0; cannam@95: cannam@95: // prime stretcher cannam@95: // for (int i = 0; i < 8; ++i) { cannam@95: // int reqd = m_stretcher->getSamplesRequired(); cannam@95: // m_stretcher->process(m_scratch, reqd, false); cannam@95: // int avail = m_stretcher->available(); cannam@95: // if (avail > 0) { cannam@95: // m_stretcher->retrieve(m_scratch, avail); cannam@95: // } cannam@95: // } cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::run(LADSPA_Handle handle, unsigned long samples) cannam@95: { cannam@95: RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; cannam@95: shifter->runImpl(samples); cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::updateRatio() cannam@95: { cannam@95: double oct = (m_octaves ? *m_octaves : 0.0); cannam@95: oct += (m_semitones ? *m_semitones : 0.0) / 12; cannam@95: oct += (m_cents ? *m_cents : 0.0) / 1200; cannam@95: m_ratio = pow(2.0, oct); cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::updateCrispness() cannam@95: { cannam@95: if (!m_crispness) return; cannam@95: cannam@95: int c = lrintf(*m_crispness); cannam@95: if (c == m_currentCrispness) return; cannam@95: if (c < 0 || c > 3) return; cannam@95: RubberBandStretcher *s = m_stretcher; cannam@95: cannam@95: switch (c) { cannam@95: case 0: cannam@95: s->setPhaseOption(RubberBandStretcher::OptionPhaseIndependent); cannam@95: s->setTransientsOption(RubberBandStretcher::OptionTransientsSmooth); cannam@95: break; cannam@95: case 1: cannam@95: s->setPhaseOption(RubberBandStretcher::OptionPhaseLaminar); cannam@95: s->setTransientsOption(RubberBandStretcher::OptionTransientsSmooth); cannam@95: break; cannam@95: case 2: cannam@95: s->setPhaseOption(RubberBandStretcher::OptionPhaseLaminar); cannam@95: s->setTransientsOption(RubberBandStretcher::OptionTransientsMixed); cannam@95: break; cannam@95: case 3: cannam@95: s->setPhaseOption(RubberBandStretcher::OptionPhaseLaminar); cannam@95: s->setTransientsOption(RubberBandStretcher::OptionTransientsCrisp); cannam@95: break; cannam@95: } cannam@95: cannam@95: m_currentCrispness = c; cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::updateFormant() cannam@95: { cannam@95: if (!m_formant) return; cannam@95: cannam@95: bool f = (*m_formant > 0.5f); cannam@95: if (f == m_currentFormant) return; cannam@95: cannam@95: RubberBandStretcher *s = m_stretcher; cannam@95: cannam@95: s->setFormantOption(f ? cannam@95: RubberBandStretcher::OptionFormantPreserved : cannam@95: RubberBandStretcher::OptionFormantShifted); cannam@95: cannam@95: m_currentFormant = f; cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::updateFast() cannam@95: { cannam@95: if (!m_fast) return; cannam@95: cannam@95: bool f = (*m_fast > 0.5f); cannam@95: if (f == m_currentFast) return; cannam@95: cannam@95: RubberBandStretcher *s = m_stretcher; cannam@95: cannam@95: s->setPitchOption(f ? cannam@95: RubberBandStretcher::OptionPitchHighSpeed : cannam@95: RubberBandStretcher::OptionPitchHighConsistency); cannam@95: cannam@95: m_currentFast = f; cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::runImpl(unsigned long insamples) cannam@95: { cannam@95: unsigned long offset = 0; cannam@95: cannam@95: // We have to break up the input into chunks like this because cannam@95: // insamples could be arbitrarily large and our output buffer is cannam@95: // of limited size cannam@95: cannam@95: while (offset < insamples) { cannam@95: cannam@95: unsigned long block = (unsigned long)m_blockSize; cannam@95: if (block + offset > insamples) block = insamples - offset; cannam@95: cannam@95: runImpl(block, offset); cannam@95: cannam@95: offset += block; cannam@95: } cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) cannam@95: { cannam@95: // cerr << "RubberBandPitchShifter::runImpl(" << insamples << ")" << endl; cannam@95: cannam@95: // static int incount = 0, outcount = 0; cannam@95: cannam@95: updateRatio(); cannam@95: if (m_ratio != m_prevRatio) { cannam@95: m_stretcher->setPitchScale(m_ratio); cannam@95: m_prevRatio = m_ratio; cannam@95: } cannam@95: cannam@95: if (m_latency) { cannam@95: *m_latency = float(m_stretcher->getLatency() + m_reserve); cannam@95: // cerr << "latency = " << *m_latency << endl; cannam@95: } cannam@95: cannam@95: updateCrispness(); cannam@95: updateFormant(); cannam@95: updateFast(); cannam@95: cannam@95: const int samples = insamples; cannam@95: int processed = 0; cannam@95: size_t outTotal = 0; cannam@95: cannam@95: float *ptrs[2]; cannam@95: cannam@95: int rs = m_outputBuffer[0]->getReadSpace(); cannam@95: if (rs < int(m_minfill)) { cannam@95: // cerr << "temporary expansion (have " << rs << ", want " << m_reserve << ")" << endl; cannam@95: m_stretcher->setTimeRatio(1.1); // fill up temporarily cannam@95: } else if (rs > 8192) { cannam@95: // cerr << "temporary reduction (have " << rs << ", want " << m_reserve << ")" << endl; cannam@95: m_stretcher->setTimeRatio(0.9); // reduce temporarily cannam@95: } else { cannam@95: m_stretcher->setTimeRatio(1.0); cannam@95: } cannam@95: cannam@95: while (processed < samples) { cannam@95: cannam@95: // never feed more than the minimum necessary number of cannam@95: // samples at a time; ensures nothing will overflow internally cannam@95: // and we don't need to call setMaxProcessSize cannam@95: cannam@95: int toCauseProcessing = m_stretcher->getSamplesRequired(); cannam@95: int inchunk = min(samples - processed, toCauseProcessing); cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: ptrs[c] = &(m_input[c][offset + processed]); cannam@95: } cannam@95: m_stretcher->process(ptrs, inchunk, false); cannam@95: processed += inchunk; cannam@95: cannam@95: int avail = m_stretcher->available(); cannam@95: int writable = m_outputBuffer[0]->getWriteSpace(); cannam@95: int outchunk = min(avail, writable); cannam@95: size_t actual = m_stretcher->retrieve(m_scratch, outchunk); cannam@95: outTotal += actual; cannam@95: cannam@95: // incount += inchunk; cannam@95: // outcount += actual; cannam@95: cannam@95: // cout << "avail: " << avail << ", outchunk = " << outchunk; cannam@95: // if (actual != outchunk) cout << " (" << actual << ")"; cannam@95: // cout << endl; cannam@95: cannam@95: outchunk = actual; cannam@95: cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: if (int(m_outputBuffer[c]->getWriteSpace()) < outchunk) { cannam@95: cerr << "RubberBandPitchShifter::runImpl: buffer overrun: chunk = " << outchunk << ", space = " << m_outputBuffer[c]->getWriteSpace() << endl; cannam@95: } cannam@95: m_outputBuffer[c]->write(m_scratch[c], outchunk); cannam@95: } cannam@95: } cannam@95: cannam@95: for (size_t c = 0; c < m_channels; ++c) { cannam@95: int toRead = m_outputBuffer[c]->getReadSpace(); cannam@95: if (toRead < samples && c == 0) { cannam@95: cerr << "RubberBandPitchShifter::runImpl: buffer underrun: required = " << samples << ", available = " << toRead << endl; cannam@95: } cannam@95: int chunk = min(toRead, samples); cannam@95: m_outputBuffer[c]->read(&(m_output[c][offset]), chunk); cannam@95: } cannam@95: cannam@95: if (m_minfill == 0) { cannam@95: m_minfill = m_outputBuffer[0]->getReadSpace(); cannam@95: // cerr << "minfill = " << m_minfill << endl; cannam@95: } cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::deactivate(LADSPA_Handle handle) cannam@95: { cannam@95: activate(handle); // both functions just reset the plugin cannam@95: } cannam@95: cannam@95: void cannam@95: RubberBandPitchShifter::cleanup(LADSPA_Handle handle) cannam@95: { cannam@95: delete (RubberBandPitchShifter *)handle; cannam@95: } cannam@95: