diff --git a/Assets/web/src/App.js b/Assets/web/src/App.js index d0ae111..33b9b8e 100644 --- a/Assets/web/src/App.js +++ b/Assets/web/src/App.js @@ -55,6 +55,8 @@ function App() { + + diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 4f3de5e..b58a534 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -68,6 +68,8 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer& buffer, shifter.SetPortamentoTime(state.getParameterAsValue("portTime").getValue()); shifter.SetHarmonyMix(state.getParameterAsValue("harmonyMix").getValue()); shifter.SetAutoTuneEnable(state.getParameterAsValue("autoTuneEnabled").getValue()); + shifter.SetFreeze(state.getParameterAsValue("freezeEnabled").getValue()); + juce::AudioBuffer const_buff; const_buff.makeCopyOf(buffer); shifter.Process(const_buff.getArrayOfReadPointers(), (float**)buffer.getArrayOfWritePointers(), buffer.getNumSamples()); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 6031e52..0bf3bb7 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -93,6 +93,12 @@ public: ParameterID("autoTuneEnabled"), "AutoTune Enabled", false); + + toggleIds.push_back("freezeEnabled"); + addToLayout(layout, + ParameterID("freezeEnabled"), + "Freeze Enabled", + false); } diff --git a/Source/Shifter.cpp b/Source/Shifter.cpp index d59c6c0..3faf871 100644 --- a/Source/Shifter.cpp +++ b/Source/Shifter.cpp @@ -35,6 +35,7 @@ void Shifter::Init(float samplerate, int samplesPerBlock) for (int i = 0; i < MAX_VOICES; ++i) { voices[i].Init(samplerate); + freeze_voices[i].Init(samplerate); } for (int i = 0; i < BUFFER_SIZE; ++i) { @@ -127,6 +128,11 @@ float Shifter::GetOutputEnvelopePeriod(int out_voice) { return in_period * formant_preserve + voices[out_voice].CurrentPeriod() * (1.0 - formant_preserve); } +float Shifter::GetOutputEnvelopePeriodFreeze(int freeze_voice) { + //TODO add something so that low pitch ratios end up reducing formant_preservation + return freeze_period * formant_preserve + freeze_voices[freeze_voice].CurrentPeriod() * (1.0 - formant_preserve); +} + int Shifter::GetPeakIndex() { int index = in_playhead - in_period * 2; if (index < 0) @@ -152,39 +158,6 @@ int Shifter::GetPeakIndex() { return max_index; } -//void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) { -// float period_ratio = resampling_period / out_periods[voice]; -// float f_index; -// f_index = max_index - resampling_period; -// if (f_index < 0) -// { -// f_index += BUFFER_SIZE; -// } -// float mult = 0; -// int out_index = out_playhead; -// for (int j = 0; j < resampling_period * 2; ++j) -// { -// // mult = .5 -// // * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1))); -// float interp = f_index - (int)f_index; -// mult = .5 -// * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]); -// float value = ((1 - interp) * in_buffer[(int)f_index] + (interp)*in_buffer[(int)(f_index + 1) % 8192]) * mult; -// out_buffer[0][out_index] += value * (1 - out_panning[voice]); -// out_buffer[1][out_index] += value * out_panning[voice]; -// -// -// f_index += period_ratio; -// if (f_index >= BUFFER_SIZE) -// { -// f_index -= BUFFER_SIZE; -// } -// if (++out_index >= BUFFER_SIZE) -// { -// out_index -= BUFFER_SIZE; -// } -// } -//} void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) { float period_ratio = in_period / resampling_period; @@ -230,6 +203,22 @@ void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_pe void Shifter::GetSamples(float** output, const float* input, size_t size) { + if (freeze_needs_copy) { + freeze_needs_copy = false; + //copy current buffer to freeze buffer + int index = GetPeakIndex(); + memset(freeze_buffer, 0, sizeof(freeze_buffer)); + CopyInputToFreezeBuffer(index); + //init freeze voices + for (int i = 0; i < MAX_VOICES; ++i) { + //freeze_voices[i].Init(sample_rate_); + if (voices[i].IsActive()) { + freeze_voices[i].Trigger(voices[i].GetMidiNote()); + freeze_voices[i].panning = voices[i].panning; + freeze_voices[i].SetPortamentoTime(0.0001f); + } + } + } for (int i = 0; i < size; ++i) { @@ -251,6 +240,20 @@ void Shifter::GetSamples(float** output, const float* input, size_t size) } } + //add freeze samples if necessary + for (int out_p = 0; out_p < MAX_VOICES; ++out_p) + { + freeze_voices[out_p].Process(); + if (!freeze_voices[out_p].IsActive()) continue; + if (freeze_voices[out_p].PeriodOverflow()) + { + float resampling_period = GetOutputEnvelopePeriodFreeze(out_p); + + //add samples centered on that max + AddFreezeToOutput(out_p, resampling_period); + } + } + if (out_period_counter > out_period) { out_period_counter -= out_period; @@ -328,4 +331,75 @@ void Shifter::SetHarmonyMix(float mix) { harmony_mix = 1.0f; melody_mix = (1.0f - mix) * 2.0f; } +} + +void Shifter::CopyInputToFreezeBuffer(int max_index) { + freeze_period = in_period; + float period_ratio = 1; + float f_index; + f_index = max_index - in_period; + if (f_index < 0) + { + f_index += BUFFER_SIZE; + } + float mult = 0; + for (int j = 0; j < in_period * 2; ++j) + { + mult = .5 * (1 - cos_lookup[(int)((float)j / (in_period * 2.0) * 8191.0)]); + float value = in_buffer[(int)f_index] * mult; + freeze_buffer[j] = value; + f_index += 1; + if (f_index >= BUFFER_SIZE) + { + f_index -= BUFFER_SIZE; + } + } +} + +void Shifter::AddFreezeToOutput(int voice, float resampling_period) { + float period_ratio = freeze_period / resampling_period; + float f_index; + f_index = 0; + if (f_index < 0) + { + f_index += BUFFER_SIZE; + } + float mult = 0; + int out_index = out_playhead; + for (int j = 0; j < resampling_period * 2; ++j) + { + // mult = .5 + // * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1))); + float interp = f_index - (int)f_index; + //mult = .5 * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]); + float value = ((1 - interp) * freeze_buffer[(int)f_index] + (interp)*freeze_buffer[(int)(f_index + 1) % 8192]); + value *= freeze_voices[voice].CurrentAmplitude(); + out_buffer[0][out_index] += value * freeze_voices[voice].GetPanning(0) * freeze_volume; + out_buffer[1][out_index] += value * freeze_voices[voice].GetPanning(1) * freeze_volume; + + + f_index += period_ratio; + if (f_index >= BUFFER_SIZE) + { + f_index -= BUFFER_SIZE; + } + if (++out_index >= BUFFER_SIZE) + { + out_index -= BUFFER_SIZE; + } + } +} + +void Shifter::SetFreeze(bool freeze) { + if (!freeze_mode && freeze) { + freeze_needs_copy = true; + + } + else if (freeze_mode && !freeze) { + //release freeze voices + for (int i = 0; i < MAX_VOICES; ++i) { + freeze_voices[i].Release(); + } + } + freeze_mode = freeze; } \ No newline at end of file diff --git a/Source/Shifter.h b/Source/Shifter.h index 54fdee1..e3e65ea 100644 --- a/Source/Shifter.h +++ b/Source/Shifter.h @@ -158,17 +158,22 @@ public: float getOutputPitch() const { return sample_rate_ / out_period; } void SetHarmonyMix(float mix); void SetAutoTuneEnable(bool enable) { enable_autotune = enable; } + void SetFreeze(bool); float out_midi = 40; ShifterVoice voices[MAX_VOICES]; + ShifterVoice freeze_voices[MAX_VOICES]; private: void DetectPitch(const float* const* in, float** out, size_t size); void SetRates(); void GetSamples(float** output, const float* input, size_t size); float GetOutputEnvelopePeriod(int out_voice); + float GetOutputEnvelopePeriodFreeze(int freeze_voice); int GetPeakIndex(); void AddInterpolatedFrame(int voice, int max_index, float period_to_use); + void AddFreezeToOutput(int voice, float resampling_period); + void CopyInputToFreezeBuffer(int); Helmholtz helm; // GranularSustain player; @@ -189,6 +194,7 @@ private: bool pitch_trigger = false; float harmony_mix = 0.0f; float melody_mix = 0.0f; + bool freeze_needs_copy; int trigger_bank; @@ -206,13 +212,14 @@ private: float last_freqs[3]; float in_buffer[BUFFER_SIZE]; float out_buffer[2][BUFFER_SIZE]; + float freeze_buffer[BUFFER_SIZE]; int out_playhead = 0; int in_playhead = 0; int last_autotune_midi = -1; float out_period_filter_amount = 0.7f; // You can expose this as a parameter - + bool freeze_mode = false; float out_period = 0; //C3 float in_period = 366.936; @@ -221,6 +228,8 @@ private: float sample_rate_; int blocksize; bool enable_autotune = false; + float freeze_period; + float freeze_volume = 1; MidiPitchSmoother out_midi_smoother; }; #endif \ No newline at end of file diff --git a/Source/shifter_voice.h b/Source/shifter_voice.h index 373fa46..6e07baf 100644 --- a/Source/shifter_voice.h +++ b/Source/shifter_voice.h @@ -34,6 +34,8 @@ public: int GetMidiNote() const { return current_midi; } bool onoff_; + float panning; + private: Port portamento_; Adsr amplitude_envelope_; @@ -44,5 +46,5 @@ private: float current_period_; float current_amplitude; float period_counter; - float panning; + }; \ No newline at end of file