#include "formantshifter.h" #include "common.h" #include "effect.h" #include "interpolation.h" #include "loudness.h" #include "mengumath.h" #include #include #include #include #include using namespace Mengu; using namespace dsp; LPCFormantShifter::LPCFormantShifter() { _transformed_buffer.resize(OverlapSize); } Effect::InputDomain LPCFormantShifter::get_input_domain() { return InputDomain::Time; }; // push new value of signal void LPCFormantShifter::push_signal(const Complex *input, const uint32_t &size) { _raw_buffer.extend_back(input, size); } // Last value of transformed signal uint32_t LPCFormantShifter::pop_transformed_signal(Complex *output, const uint32_t &size) { std::array freq_shifted {0}; std::array samples {0}; std::array shifted_samples {0}; while (_raw_buffer.size() >= ProcSize && _transformed_buffer.size() < size + OverlapSize) { // Load sample segment. 0 unused frames _raw_buffer.to_array(samples.data(), ProcSize); // do the shifty _lpc.load_sample(samples.data()); _shift_by_env( _lpc.get_freq_spectrum().data(), freq_shifted.data(), _lpc.get_envelope().data(), _shift_factor ); _lpc.get_fft().inverse_transform(freq_shifted.data(), shifted_samples.data()); // Make downward shifts not quieter and upward shifts not louder // Automatically adjusts for the fact that only half of the frequency spectrum is used _loudness_norm.normalize(shifted_samples.data(), samples.data(), shifted_samples.data()); // copy to output mix_and_extend(_transformed_buffer, shifted_samples, OverlapSize, hamming_window); _raw_buffer.pop_front_many(nullptr, HopSize); } if (_transformed_buffer.size() < size + OverlapSize) { // Not enough samples to output anything return 0; } else { return _transformed_buffer.pop_front_many(output, size); } } // number of samples that can be output given the current pushed signals of the Effect uint32_t LPCFormantShifter::n_transformed_ready() const { return _raw_buffer.size(); }; // resets state of effect to make it reading to take in a new sample void LPCFormantShifter::reset() { _raw_sample_filter.reset(); _shifted_sample_filter.reset(); } // The properties that this Effect exposes to be changed by GUI. // The index that they are put in is considered the props id std::vector LPCFormantShifter::get_property_descs() const { return { EffectPropDesc { .type = EffectPropType::Slider, .name = "Formant Shift", .desc = "Scales the formant of pushed signals by this amount", .slider_data = { .min_value = 0.5, .max_value = 2, .scale = Exp, } } }; } // Sets a property with the specified id the value declared in the payload void LPCFormantShifter::set_property(uint32_t id, EffectPropPayload data) { switch (id) { case 0: if (data.type == Slider) { _shift_factor = data.value; } break; default: break; } } // Gets the value of a property with the specified id EffectPropPayload LPCFormantShifter::get_property(uint32_t id) const { return EffectPropPayload { .type = Slider, .value = _shift_factor, }; }; // rescale an array in the freqency domain by the shape of an envelope if it were to be shifted up or down void LPCFormantShifter::_shift_by_env(const Complex *input, Complex *output, const float *envelope, const float shift_factor) { for (uint32_t i = 0; i < ProcSize / 2; i++) { uint32_t shifted_ind = i / shift_factor; if (shifted_ind < ProcSize / 2) { float correction = envelope[shifted_ind] / envelope[i]; if (!std::isfinite(correction)) { output[i] = Complex(0.0f); } else { output[i] = correction * input[i]; } } else { // output[i] = input[size - 1]; output[i] = Complex(0.0f); } } }