/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rubber Band An audio time-stretching and pitch-shifting library. Copyright 2007 Chris Cannam. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "RubberBandPitchShifter.h" #include "RubberBandStretcher.h" #include #include const char *const RubberBandPitchShifter::portNamesMono[PortCountMono] = { "_latency", "Cents", "Semitones", "Octaves", "Input", "Output" }; const char *const RubberBandPitchShifter::portNamesStereo[PortCountStereo] = { "_latency", "Cents", "Semitones", "Octaves", "Input L", "Output L", "Input R", "Output R" }; const LADSPA_PortDescriptor RubberBandPitchShifter::portsMono[PortCountMono] = { LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO }; const LADSPA_PortDescriptor RubberBandPitchShifter::portsStereo[PortCountStereo] = { LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO }; const LADSPA_PortRangeHint RubberBandPitchShifter::hintsMono[PortCountMono] = { { 0, 0, 0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, -100.0, 100.0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, -12.0, 12.0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, -4.0, 4.0 }, { 0, 0, 0 }, { 0, 0, 0 } }; const LADSPA_PortRangeHint RubberBandPitchShifter::hintsStereo[PortCountStereo] = { { 0, 0, 0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE, -100.0, 100.0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, -12.0, 12.0 }, { LADSPA_HINT_DEFAULT_0 | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, -4.0, 4.0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } }; const LADSPA_Properties RubberBandPitchShifter::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; const LADSPA_Descriptor RubberBandPitchShifter::ladspaDescriptorMono = { 2979, // "Unique" ID "rubberband-pitchshifter-mono", // Label properties, "Rubber Band Mono Pitch Shifter", // Name "Chris Cannam", //!!! Maker "GPL", //!!! Copyright PortCountMono, portsMono, portNamesMono, hintsMono, 0, // Implementation data instantiate, connectPort, activate, run, 0, // Run adding 0, // Set run adding gain deactivate, cleanup }; const LADSPA_Descriptor RubberBandPitchShifter::ladspaDescriptorStereo = { 9792, // "Unique" ID "rubberband-pitchshifter-stereo", // Label properties, "Rubber Band Stereo Pitch Shifter", // Name "Chris Cannam", //!!! Maker "GPL", //!!! Copyright PortCountStereo, portsStereo, portNamesStereo, hintsStereo, 0, // Implementation data instantiate, connectPort, activate, run, 0, // Run adding 0, // Set run adding gain deactivate, cleanup }; const LADSPA_Descriptor * RubberBandPitchShifter::getDescriptor(unsigned long index) { // std::cerr << "RubberBandPitchShifter::getDescriptor(" << index << ")" << std::endl; if (index == 0) return &ladspaDescriptorMono; if (index == 1) return &ladspaDescriptorStereo; else return 0; } RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) : m_latency(0), m_cents(0), m_semitones(0), m_octaves(0), m_ratio(1.0), m_prevRatio(1.0), m_extraLatency(8192), //!!! this should be at least the maximum possible displacement from linear at input rates, divided by the pitch scale factor. It could be very large m_stretcher(new RubberBand::RubberBandStretcher (sampleRate, channels, RubberBand::RubberBandStretcher::OptionProcessRealTime)),// | // RubberBand::RubberBandStretcher::OptionStretchPrecise | // RubberBand::RubberBandStretcher::OptionTransientsSmooth | // RubberBand::RubberBandStretcher::OptionTransientsCrisp | // RubberBand::RubberBandStretcher::OptionPhasePeakLocked | // RubberBand::RubberBandStretcher::OptionThreadingNone)), m_sampleRate(sampleRate), m_channels(channels) { // m_stretcher->setMaxProcessBlockSize(4096); for (size_t c = 0; c < m_channels; ++c) { m_input[c] = 0; m_output[c] = 0; //!!! size must be at least max process size plus m_extraLatency: m_outputBuffer[c] = new RubberBand::RingBuffer(8092); //!!! m_outputBuffer[c]->zero(m_extraLatency); //!!! size must be at least max process size: m_scratch[c] = new float[16384];//!!! } } RubberBandPitchShifter::~RubberBandPitchShifter() { delete m_stretcher; for (size_t c = 0; c < m_channels; ++c) { delete m_outputBuffer[c]; delete[] m_scratch[c]; } } LADSPA_Handle RubberBandPitchShifter::instantiate(const LADSPA_Descriptor *desc, unsigned long rate) { if (desc->PortCount == ladspaDescriptorMono.PortCount) { return new RubberBandPitchShifter(rate, 1); } else if (desc->PortCount == ladspaDescriptorStereo.PortCount) { return new RubberBandPitchShifter(rate, 2); } return 0; } void RubberBandPitchShifter::connectPort(LADSPA_Handle handle, unsigned long port, LADSPA_Data *location) { RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; float **ports[PortCountStereo] = { &shifter->m_latency, &shifter->m_cents, &shifter->m_semitones, &shifter->m_octaves, &shifter->m_input[0], &shifter->m_output[0], &shifter->m_input[1], &shifter->m_output[1] }; *ports[port] = (float *)location; } void RubberBandPitchShifter::activate(LADSPA_Handle handle) { RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; //!!! QMutexLocker locker(&shifter->m_mutex); shifter->updateRatio(); shifter->m_prevRatio = shifter->m_ratio; shifter->m_stretcher->reset(); shifter->m_stretcher->setPitchScale(shifter->m_ratio); } void RubberBandPitchShifter::run(LADSPA_Handle handle, unsigned long samples) { RubberBandPitchShifter *shifter = (RubberBandPitchShifter *)handle; shifter->runImpl(samples); } void RubberBandPitchShifter::updateRatio() { double oct = *m_octaves; oct += *m_semitones / 12; oct += *m_cents / 1200; m_ratio = pow(2.0, oct); } void RubberBandPitchShifter::runImpl(unsigned long insamples) { // std::cerr << "RubberBandPitchShifter::runImpl(" << insamples << ")" << std::endl; updateRatio(); if (m_ratio != m_prevRatio) { m_stretcher->setPitchScale(m_ratio); m_prevRatio = m_ratio; } if (m_latency) { *m_latency = m_stretcher->getLatency() + m_extraLatency; // std::cerr << "latency = " << *m_latency << std::endl; } int samples = insamples; int processed = 0; size_t outTotal = 0; float *ptrs[2]; //!!! We have to break up the input into chunks like this because // insamples could be arbitrarily large while (processed < samples) { //!!! size_t: int toCauseProcessing = m_stretcher->getSamplesRequired(); // std::cout << "to-cause: " << toCauseProcessing << ", remain = " << samples - processed; int inchunk = std::min(samples - processed, toCauseProcessing); for (size_t c = 0; c < m_channels; ++c) { ptrs[c] = &(m_input[c][processed]); } m_stretcher->process(ptrs, inchunk, false); processed += inchunk; int avail = m_stretcher->available(); int writable = m_outputBuffer[0]->getWriteSpace(); int outchunk = std::min(avail, writable); size_t actual = m_stretcher->retrieve(m_scratch, outchunk); outTotal += actual; // std::cout << ", avail: " << avail << ", outchunk = " << outchunk; // if (actual != outchunk) std::cout << " (" << actual << ")"; // std::cout << std::endl; outchunk = actual; for (size_t c = 0; c < m_channels; ++c) { if (int(m_outputBuffer[c]->getWriteSpace()) < outchunk) { std::cerr << "RubberBandPitchShifter::runImpl: buffer overrun: chunk = " << outchunk << ", space = " << m_outputBuffer[c]->getWriteSpace() << std::endl; } m_outputBuffer[c]->write(m_scratch[c], outchunk); } } // std::cout << "processed = " << processed << " in, " << outTotal << " out" << ", fill = " << m_outputBuffer[0]->getReadSpace() << " of " << m_outputBuffer[0]->getSize() << std::endl; for (size_t c = 0; c < m_channels; ++c) { int avail = m_outputBuffer[c]->getReadSpace(); // std::cout << "avail: " << avail << std::endl; if (avail < samples && c == 0) { std::cerr << "RubberBandPitchShifter::runImpl: buffer underrun: required = " << samples << ", available = " << avail << std::endl; } int chunk = std::min(avail, samples); // std::cout << "out chunk: " << chunk << std::endl; m_outputBuffer[c]->read(m_output[c], chunk); } } void RubberBandPitchShifter::deactivate(LADSPA_Handle handle) { activate(handle); // both functions just reset the plugin } void RubberBandPitchShifter::cleanup(LADSPA_Handle handle) { delete (RubberBandPitchShifter *)handle; }