closes #2
Add dynamic toggle options. Add toggle for turning on and off the autotune
This commit is contained in:
@ -29,6 +29,7 @@ import "@fontsource/roboto/700.css";
|
|||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import * as Juce from "juce-framework-frontend";
|
import * as Juce from "juce-framework-frontend";
|
||||||
import JuceSlider from "./Components/JuceSlider.js";
|
import JuceSlider from "./Components/JuceSlider.js";
|
||||||
|
import JuceCheckbox from "./Components/JuceCheckbox.js";
|
||||||
import MidiNoteInfo from "./Components/MidiNoteInfo.js";
|
import MidiNoteInfo from "./Components/MidiNoteInfo.js";
|
||||||
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
|
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ function App() {
|
|||||||
<Container>
|
<Container>
|
||||||
<JuceSlider identifier="harmonyMix" title="Mix" />
|
<JuceSlider identifier="harmonyMix" title="Mix" />
|
||||||
<JuceSlider identifier="formantPreserve" title="Formant" />
|
<JuceSlider identifier="formantPreserve" title="Formant" />
|
||||||
|
<JuceCheckbox identifier="autoTuneEnabled" />
|
||||||
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
||||||
<JuceSlider identifier="autoTuneDepth" title="Auto Tune Depth" />
|
<JuceSlider identifier="autoTuneDepth" title="Auto Tune Depth" />
|
||||||
<JuceSlider identifier="portTime" title="Portamento Speed" />
|
<JuceSlider identifier="portTime" title="Portamento Speed" />
|
||||||
|
|||||||
54
Assets/web/src/Components/JuceCheckbox.js
Normal file
54
Assets/web/src/Components/JuceCheckbox.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import * as Juce from "juce-framework-frontend";
|
||||||
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
|
import FormGroup from "@mui/material/FormGroup";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
||||||
|
|
||||||
|
export default function JuceCheckbox({ identifier }) {
|
||||||
|
JuceCheckbox.propTypes = {
|
||||||
|
identifier: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkboxState = Juce.getToggleState(identifier);
|
||||||
|
|
||||||
|
const [value, setValue] = useState(checkboxState.getValue());
|
||||||
|
const [properties, setProperties] = useState(checkboxState.properties);
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
checkboxState.setValue(event.target.checked);
|
||||||
|
setValue(event.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const valueListenerId = checkboxState.valueChangedEvent.addListener(() => {
|
||||||
|
setValue(checkboxState.getValue());
|
||||||
|
});
|
||||||
|
const propertiesListenerId =
|
||||||
|
checkboxState.propertiesChangedEvent.addListener(() =>
|
||||||
|
setProperties(checkboxState.properties)
|
||||||
|
);
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
checkboxState.valueChangedEvent.removeListener(valueListenerId);
|
||||||
|
checkboxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const cb = <Checkbox checked={value} onChange={handleChange} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...{
|
||||||
|
[controlParameterIndexAnnotation]:
|
||||||
|
checkboxState.properties.parameterIndex,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel control={cb} label={properties.name} />
|
||||||
|
</FormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
64
Assets/web/src/Components/JuceComboBox.js
Normal file
64
Assets/web/src/Components/JuceComboBox.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import * as Juce from "juce-framework-frontend";
|
||||||
|
import Select from "@mui/material/Select";
|
||||||
|
import InputLabel from "@mui/material/InputLabel";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import FormControl from "@mui/material/FormControl";
|
||||||
|
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
||||||
|
export default function JuceComboBox({ identifier }) {
|
||||||
|
JuceComboBox.propTypes = {
|
||||||
|
identifier: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const comboBoxState = Juce.getComboBoxState(identifier);
|
||||||
|
|
||||||
|
const [value, setValue] = useState(comboBoxState.getChoiceIndex());
|
||||||
|
const [properties, setProperties] = useState(comboBoxState.properties);
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
comboBoxState.setChoiceIndex(event.target.value);
|
||||||
|
setValue(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const valueListenerId = comboBoxState.valueChangedEvent.addListener(() => {
|
||||||
|
setValue(comboBoxState.getChoiceIndex());
|
||||||
|
});
|
||||||
|
const propertiesListenerId =
|
||||||
|
comboBoxState.propertiesChangedEvent.addListener(() => {
|
||||||
|
setProperties(comboBoxState.properties);
|
||||||
|
});
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
comboBoxState.valueChangedEvent.removeListener(valueListenerId);
|
||||||
|
comboBoxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...{
|
||||||
|
[controlParameterIndexAnnotation]:
|
||||||
|
comboBoxState.properties.parameterIndex,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel id={identifier}>{properties.name}</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId={identifier}
|
||||||
|
value={value}
|
||||||
|
label={properties.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{properties.choices.map((choice, i) => (
|
||||||
|
<MenuItem value={i} key={i}>
|
||||||
|
{choice}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -161,6 +161,16 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
|
|||||||
options = options.withOptionsFrom(*slider_relays.back());
|
options = options.withOptionsFrom(*slider_relays.back());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& toggleId : p.parameters.toggleIds) {
|
||||||
|
toggle_relays.push_back(new WebToggleButtonRelay{ toggleId });
|
||||||
|
toggle_attatchments.push_back(new
|
||||||
|
WebToggleButtonParameterAttachment(
|
||||||
|
*processorRef.state.getParameter(toggleId),
|
||||||
|
*toggle_relays.back(),
|
||||||
|
processorRef.state.undoManager));
|
||||||
|
options = options.withOptionsFrom(*toggle_relays.back());
|
||||||
|
}
|
||||||
|
|
||||||
webComponent = new SinglePageBrowser(options);
|
webComponent = new SinglePageBrowser(options);
|
||||||
addAndMakeVisible(*webComponent);
|
addAndMakeVisible(*webComponent);
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,13 @@ public:
|
|||||||
for (auto& relays : slider_relays) {
|
for (auto& relays : slider_relays) {
|
||||||
delete relays;
|
delete relays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& attatchments : toggle_attatchments) {
|
||||||
|
delete attatchments;
|
||||||
|
}
|
||||||
|
for (auto& relays : toggle_relays) {
|
||||||
|
delete relays;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WebBrowserComponent::Resource> getResource(const String& url);
|
std::optional<WebBrowserComponent::Resource> getResource(const String& url);
|
||||||
@ -83,6 +90,9 @@ private:
|
|||||||
std::vector<WebSliderRelay*> slider_relays;
|
std::vector<WebSliderRelay*> slider_relays;
|
||||||
std::vector< WebSliderParameterAttachment*> slider_attatchments;
|
std::vector< WebSliderParameterAttachment*> slider_attatchments;
|
||||||
|
|
||||||
|
std::vector<WebToggleButtonRelay*> toggle_relays;
|
||||||
|
std::vector< WebToggleButtonParameterAttachment*> toggle_attatchments;
|
||||||
|
|
||||||
WebControlParameterIndexReceiver controlParameterIndexReceiver;
|
WebControlParameterIndexReceiver controlParameterIndexReceiver;
|
||||||
|
|
||||||
SinglePageBrowser* webComponent = nullptr;
|
SinglePageBrowser* webComponent = nullptr;
|
||||||
|
|||||||
@ -67,6 +67,7 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
|||||||
shifter.SetAutoTuneDepth(state.getParameterAsValue("autoTuneDepth").getValue());
|
shifter.SetAutoTuneDepth(state.getParameterAsValue("autoTuneDepth").getValue());
|
||||||
shifter.SetPortamentoTime(state.getParameterAsValue("portTime").getValue());
|
shifter.SetPortamentoTime(state.getParameterAsValue("portTime").getValue());
|
||||||
shifter.SetHarmonyMix(state.getParameterAsValue("harmonyMix").getValue());
|
shifter.SetHarmonyMix(state.getParameterAsValue("harmonyMix").getValue());
|
||||||
|
shifter.SetAutoTuneEnable(state.getParameterAsValue("autoTuneEnabled").getValue());
|
||||||
juce::AudioBuffer<float> const_buff;
|
juce::AudioBuffer<float> const_buff;
|
||||||
const_buff.makeCopyOf(buffer);
|
const_buff.makeCopyOf(buffer);
|
||||||
shifter.Process(const_buff.getArrayOfReadPointers(), (float**)buffer.getArrayOfWritePointers(), buffer.getNumSamples());
|
shifter.Process(const_buff.getArrayOfReadPointers(), (float**)buffer.getArrayOfWritePointers(), buffer.getNumSamples());
|
||||||
|
|||||||
@ -87,13 +87,21 @@ public:
|
|||||||
NormalisableRange<float> {0.001f, 0.2f, .001f},
|
NormalisableRange<float> {0.001f, 0.2f, .001f},
|
||||||
.01f);
|
.01f);
|
||||||
|
|
||||||
|
|
||||||
|
toggleIds.push_back("autoTuneEnabled");
|
||||||
|
addToLayout<AudioParameterBool>(layout,
|
||||||
|
ParameterID("autoTuneEnabled"),
|
||||||
|
"AutoTune Enabled",
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*AudioParameterFloat& formantPreserve;
|
/*AudioParameterFloat& formantPreserve;
|
||||||
AudioParameterFloat& autoTuneSpeed;
|
AudioParameterFloat& autoTuneSpeed;
|
||||||
AudioParameterFloat& autoTuneDepth;
|
AudioParameterFloat& autoTuneDepth;
|
||||||
AudioParameterFloat& portTime;*/
|
AudioParameterFloat& portTime;*/
|
||||||
std::vector<juce::String> sliderIds;
|
std::vector<juce::String> sliderIds;
|
||||||
|
std::vector<juce::String> toggleIds;
|
||||||
/*AudioParameterBool& mute;
|
/*AudioParameterBool& mute;
|
||||||
AudioParameterChoice& filterType;*/
|
AudioParameterChoice& filterType;*/
|
||||||
|
|
||||||
|
|||||||
@ -12,16 +12,16 @@ void Shifter::SetAutoTuneSpeed(float val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::SetAutoTuneDepth(float val) {
|
void Shifter::SetAutoTuneDepth(float val) {
|
||||||
out_midi_smoother.SetDepth(val);
|
out_midi_smoother.SetDepth(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline float mtof(float m)
|
static inline float mtof(float m)
|
||||||
{
|
{
|
||||||
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
|
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool float_equal(float one, float two) {
|
static inline bool float_equal(float one, float two) {
|
||||||
return abs(one - two) < 1e-5f;
|
return abs(one - two) < 1e-5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::Init(float samplerate, int samplesPerBlock)
|
void Shifter::Init(float samplerate, int samplesPerBlock)
|
||||||
@ -29,127 +29,127 @@ void Shifter::Init(float samplerate, int samplesPerBlock)
|
|||||||
sample_rate_ = samplerate;
|
sample_rate_ = samplerate;
|
||||||
blocksize = samplesPerBlock;
|
blocksize = samplesPerBlock;
|
||||||
out_midi_smoother.SetFrameTime((float)samplesPerBlock / samplerate);
|
out_midi_smoother.SetFrameTime((float)samplesPerBlock / samplerate);
|
||||||
volume = 1;
|
volume = 1;
|
||||||
helm.setframesize(1024);
|
helm.setframesize(1024);
|
||||||
helm.setoverlap(1);
|
helm.setoverlap(1);
|
||||||
for (int i = 0; i < MAX_VOICES; ++i)
|
for (int i = 0; i < MAX_VOICES; ++i)
|
||||||
{
|
{
|
||||||
voices[i].Init(samplerate);
|
voices[i].Init(samplerate);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < BUFFER_SIZE; ++i)
|
for (int i = 0; i < BUFFER_SIZE; ++i)
|
||||||
{
|
{
|
||||||
in_buffer[i] = 0;
|
in_buffer[i] = 0;
|
||||||
out_buffer[0][i] = 0;
|
out_buffer[0][i] = 0;
|
||||||
out_buffer[1][i] = 0;
|
out_buffer[1][i] = 0;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 8192; ++i) {
|
for (int i = 0; i < 8192; ++i) {
|
||||||
cos_lookup[i] = cos(2 * PI_F * i / 8192.0);
|
cos_lookup[i] = cos(2 * PI_F * i / 8192.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::Process(const float* const* in,
|
void Shifter::Process(const float* const* in,
|
||||||
float** out,
|
float** out,
|
||||||
size_t size)
|
size_t size)
|
||||||
{
|
{
|
||||||
DetectPitch(in, out, size);
|
DetectPitch(in, out, size);
|
||||||
SetRates();
|
SetRates();
|
||||||
// for (size_t i = 0; i < size; ++i) {
|
// for (size_t i = 0; i < size; ++i) {
|
||||||
// out[0][i] = 0;
|
// out[0][i] = 0;
|
||||||
// }
|
// }
|
||||||
GetSamples(out, in[0], size);
|
GetSamples(out, in[0], size);
|
||||||
//for (size_t i = 0; i < size; ++i)
|
//for (size_t i = 0; i < size; ++i)
|
||||||
//{
|
//{
|
||||||
// // out[0][i] = osc.Process();
|
// // out[0][i] = osc.Process();
|
||||||
// // out[0][i] = in[0][i];
|
// // out[0][i] = in[0][i];
|
||||||
// //out[0][i] = out[0][i] + in[0][i];
|
// //out[0][i] = out[0][i] + in[0][i];
|
||||||
// out[1][i] = out[0][i];
|
// out[1][i] = out[0][i];
|
||||||
//}
|
//}
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
float findMedian(float a, float b, float c) {
|
float findMedian(float a, float b, float c) {
|
||||||
if ((a >= b && a <= c) || (a <= b && a >= c))
|
if ((a >= b && a <= c) || (a <= b && a >= c))
|
||||||
return a;
|
return a;
|
||||||
else if ((b >= a && b <= c) || (b <= a && b >= c))
|
else if ((b >= a && b <= c) || (b <= a && b >= c))
|
||||||
return b;
|
return b;
|
||||||
else
|
else
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
||||||
{
|
{
|
||||||
// detect current pitch
|
// detect current pitch
|
||||||
// pitch_detect.update(in[0], size);
|
// pitch_detect.update(in[0], size);
|
||||||
// if(pitch_detect.available())
|
// if(pitch_detect.available())
|
||||||
// {
|
// {
|
||||||
// float read = pitch_detect.read();
|
// float read = pitch_detect.read();
|
||||||
// if(read >= 35 && read <= 2000)
|
// if(read >= 35 && read <= 2000)
|
||||||
// {
|
// {
|
||||||
// for(int i = 2; i > 0; --i){
|
// for(int i = 2; i > 0; --i){
|
||||||
// last_freqs[i] = last_freqs[i-1];
|
// last_freqs[i] = last_freqs[i-1];
|
||||||
// }
|
// }
|
||||||
// last_freqs[0] = read;
|
// last_freqs[0] = read;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// current_pitch = findMedian(last_freqs[0], last_freqs[1], last_freqs[2]);
|
// current_pitch = findMedian(last_freqs[0], last_freqs[1], last_freqs[2]);
|
||||||
// in_period = 1.0 / current_pitch * 48000;
|
// in_period = 1.0 / current_pitch * 48000;
|
||||||
|
|
||||||
helm.iosamples(in[0], out[0], size);
|
helm.iosamples(in[0], out[0], size);
|
||||||
float period = helm.getperiod();
|
float period = helm.getperiod();
|
||||||
float fidel = helm.getfidelity();
|
float fidel = helm.getfidelity();
|
||||||
//DBG("frequency: " << 48000 / period << " fidel: " << fidel);
|
//DBG("frequency: " << 48000 / period << " fidel: " << fidel);
|
||||||
|
|
||||||
// Adjustable filter amount (0.0f = no filtering, 1.0f = max filtering)
|
// Adjustable filter amount (0.0f = no filtering, 1.0f = max filtering)
|
||||||
static float in_period_filter_amount = 0.5f; // You can expose this as a parameter
|
static float in_period_filter_amount = 0.5f; // You can expose this as a parameter
|
||||||
|
|
||||||
if (fidel > 0.95) {
|
if (fidel > 0.95) {
|
||||||
// Smoothly filter in_period changes
|
// Smoothly filter in_period changes
|
||||||
in_period = in_period * in_period_filter_amount + period * (1.0f - in_period_filter_amount);
|
in_period = in_period * in_period_filter_amount + period * (1.0f - in_period_filter_amount);
|
||||||
}
|
}
|
||||||
float in_freq = sample_rate_ / in_period;
|
float in_freq = sample_rate_ / in_period;
|
||||||
|
|
||||||
float midi = (12 * log2f(in_freq / 440) + 69.0f);
|
float midi = (12 * log2f(in_freq / 440) + 69.0f);
|
||||||
|
|
||||||
//target_out_period = in_period * out_period_filter_amount + target_out_period * (1 - out_period_filter_amount);
|
//target_out_period = in_period * out_period_filter_amount + target_out_period * (1 - out_period_filter_amount);
|
||||||
out_midi = out_midi_smoother.update(midi, (int)(midi+.5));
|
out_midi = out_midi_smoother.update(midi, (int)(midi + .5));
|
||||||
out_period = sample_rate_ / mtof(out_midi);
|
out_period = sample_rate_ / mtof(out_midi);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::SetRates() {}
|
void Shifter::SetRates() {}
|
||||||
|
|
||||||
float Shifter::GetOutputEnvelopePeriod(int out_voice) {
|
float Shifter::GetOutputEnvelopePeriod(int out_voice) {
|
||||||
if (out_voice >= MAX_VOICES) {
|
if (out_voice >= MAX_VOICES) {
|
||||||
return in_period * formant_preserve + out_period *(1.0 - formant_preserve);
|
return in_period * formant_preserve + out_period * (1.0 - formant_preserve);
|
||||||
}
|
}
|
||||||
//TODO add something so that low pitch ratios end up reducing formant_preservation
|
//TODO add something so that low pitch ratios end up reducing formant_preservation
|
||||||
return in_period * formant_preserve + voices[out_voice].CurrentPeriod() * (1.0 - formant_preserve);
|
return in_period * formant_preserve + voices[out_voice].CurrentPeriod() * (1.0 - formant_preserve);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Shifter::GetPeakIndex() {
|
int Shifter::GetPeakIndex() {
|
||||||
int index = in_playhead - in_period * 2;
|
int index = in_playhead - in_period * 2;
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
index += BUFFER_SIZE;
|
index += BUFFER_SIZE;
|
||||||
|
|
||||||
//search for max absolute value
|
//search for max absolute value
|
||||||
int max_index = -1;
|
int max_index = -1;
|
||||||
float max_value = -2;
|
float max_value = -2;
|
||||||
for (int j = 0; j < in_period; ++j)
|
for (int j = 0; j < in_period; ++j)
|
||||||
{
|
{
|
||||||
//float val = fabs(in_buffer[index]);
|
//float val = fabs(in_buffer[index]);
|
||||||
float val = in_buffer[index];
|
float val = in_buffer[index];
|
||||||
if (val > max_value)
|
if (val > max_value)
|
||||||
{
|
{
|
||||||
max_index = index;
|
max_index = index;
|
||||||
max_value = val;
|
max_value = val;
|
||||||
}
|
}
|
||||||
if (++index >= BUFFER_SIZE)
|
if (++index >= BUFFER_SIZE)
|
||||||
{
|
{
|
||||||
index -= BUFFER_SIZE;
|
index -= BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return max_index;
|
return max_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
//void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
|
//void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
|
||||||
@ -187,135 +187,145 @@ int Shifter::GetPeakIndex() {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
|
void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
|
||||||
float period_ratio = in_period / resampling_period;
|
float period_ratio = in_period / resampling_period;
|
||||||
float f_index;
|
float f_index;
|
||||||
f_index = max_index - in_period;
|
f_index = max_index - in_period;
|
||||||
if (f_index < 0)
|
if (f_index < 0)
|
||||||
{
|
{
|
||||||
f_index += BUFFER_SIZE;
|
f_index += BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
float mult = 0;
|
float mult = 0;
|
||||||
int out_index = out_playhead;
|
int out_index = out_playhead;
|
||||||
for (int j = 0; j < resampling_period * 2; ++j)
|
for (int j = 0; j < resampling_period * 2; ++j)
|
||||||
{
|
{
|
||||||
// mult = .5
|
// mult = .5
|
||||||
// * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1)));
|
// * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1)));
|
||||||
float interp = f_index - (int)f_index;
|
float interp = f_index - (int)f_index;
|
||||||
mult = .5 * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]);
|
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;
|
float value = ((1 - interp) * in_buffer[(int)f_index] + (interp)*in_buffer[(int)(f_index + 1) % 8192]) * mult;
|
||||||
if(voice >= MAX_VOICES) {
|
if (voice >= MAX_VOICES) {
|
||||||
//value *= volume;
|
//value *= volume;
|
||||||
out_buffer[0][out_index] += value * melody_mix;
|
out_buffer[0][out_index] += value * melody_mix;
|
||||||
out_buffer[1][out_index] += value * melody_mix;
|
out_buffer[1][out_index] += value * melody_mix;
|
||||||
} else {
|
}
|
||||||
value *= voices[voice].CurrentAmplitude() * volume;
|
else {
|
||||||
out_buffer[0][out_index] += value * voices[voice].GetPanning(0) * harmony_mix;
|
value *= voices[voice].CurrentAmplitude() * volume;
|
||||||
out_buffer[1][out_index] += value * voices[voice].GetPanning(1) * harmony_mix;
|
out_buffer[0][out_index] += value * voices[voice].GetPanning(0) * harmony_mix;
|
||||||
|
out_buffer[1][out_index] += value * voices[voice].GetPanning(1) * harmony_mix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
f_index += period_ratio;
|
f_index += period_ratio;
|
||||||
if (f_index >= BUFFER_SIZE)
|
if (f_index >= BUFFER_SIZE)
|
||||||
{
|
{
|
||||||
f_index -= BUFFER_SIZE;
|
f_index -= BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
if (++out_index >= BUFFER_SIZE)
|
if (++out_index >= BUFFER_SIZE)
|
||||||
{
|
{
|
||||||
out_index -= BUFFER_SIZE;
|
out_index -= BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::GetSamples(float** output, const float* input, size_t size)
|
void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < size; ++i)
|
for (int i = 0; i < size; ++i)
|
||||||
{
|
{
|
||||||
|
|
||||||
//add new samples if necessary
|
//add new samples if necessary
|
||||||
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
|
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
|
||||||
{
|
{
|
||||||
voices[out_p].Process();
|
voices[out_p].Process();
|
||||||
if (!voices[out_p].IsActive()) continue;
|
if (!voices[out_p].IsActive()) continue;
|
||||||
if (voices[out_p].PeriodOverflow())
|
if (voices[out_p].PeriodOverflow())
|
||||||
{
|
{
|
||||||
float resampling_period = GetOutputEnvelopePeriod(out_p);
|
float resampling_period = GetOutputEnvelopePeriod(out_p);
|
||||||
|
|
||||||
|
|
||||||
//find the start index
|
//find the start index
|
||||||
int max_index = GetPeakIndex();
|
int max_index = GetPeakIndex();
|
||||||
|
|
||||||
//add samples centered on that max
|
//add samples centered on that max
|
||||||
AddInterpolatedFrame(out_p, max_index, resampling_period);
|
AddInterpolatedFrame(out_p, max_index, resampling_period);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out_period_counter > out_period)
|
|
||||||
{
|
if (out_period_counter > out_period)
|
||||||
out_period_counter -= out_period;
|
{
|
||||||
float resampling_period = GetOutputEnvelopePeriod(MAX_VOICES);
|
out_period_counter -= out_period;
|
||||||
|
if (enable_autotune) {
|
||||||
|
float resampling_period = GetOutputEnvelopePeriod(MAX_VOICES);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//find the start index
|
//find the start index
|
||||||
int max_index = GetPeakIndex();
|
int max_index = GetPeakIndex();
|
||||||
|
|
||||||
//add samples centered on that max
|
//add samples centered on that max
|
||||||
AddInterpolatedFrame(MAX_VOICES, max_index, resampling_period);
|
AddInterpolatedFrame(MAX_VOICES, max_index, resampling_period);
|
||||||
}
|
}
|
||||||
//add input samples
|
}
|
||||||
in_buffer[in_playhead] = input[i];
|
|
||||||
|
|
||||||
//output samples, set to 0
|
|
||||||
for (int ch = 0; ch < 2; ++ch) {
|
|
||||||
output[ch][i] = out_buffer[ch][out_playhead];
|
|
||||||
out_buffer[ch][out_playhead] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//increment playheads
|
|
||||||
if (++in_playhead >= BUFFER_SIZE)
|
//add input samples
|
||||||
{
|
in_buffer[in_playhead] = input[i];
|
||||||
in_playhead -= BUFFER_SIZE;
|
|
||||||
}
|
//output samples, set to 0
|
||||||
if (++out_playhead >= BUFFER_SIZE)
|
for (int ch = 0; ch < 2; ++ch) {
|
||||||
{
|
output[ch][i] = out_buffer[ch][out_playhead];
|
||||||
out_playhead -= BUFFER_SIZE;
|
if (!enable_autotune) {
|
||||||
}
|
output[ch][i] += input[i] * melody_mix;
|
||||||
out_period_counter++;
|
}
|
||||||
}
|
out_buffer[ch][out_playhead] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//increment playheads
|
||||||
|
if (++in_playhead >= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
in_playhead -= BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
if (++out_playhead >= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
out_playhead -= BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
out_period_counter++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Shifter::AddMidiNote(int note) {
|
void Shifter::AddMidiNote(int note) {
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (voices[i].onoff_ && voices[i].GetMidiNote() == note) {
|
if (voices[i].onoff_ && voices[i].GetMidiNote() == note) {
|
||||||
voices[i].Release();
|
voices[i].Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (!voices[i].onoff_) {
|
if (!voices[i].onoff_) {
|
||||||
voices[i].Trigger(note);
|
voices[i].Trigger(note);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Shifter::RemoveMidiNote(int note) {
|
void Shifter::RemoveMidiNote(int note) {
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (voices[i].GetMidiNote() == note) {
|
if (voices[i].GetMidiNote() == note) {
|
||||||
voices[i].Release();
|
voices[i].Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::SetHarmonyMix(float mix) {
|
void Shifter::SetHarmonyMix(float mix) {
|
||||||
if (mix < .5) {
|
if (mix < .5) {
|
||||||
melody_mix = 1.0f;
|
melody_mix = 1.0f;
|
||||||
harmony_mix = mix * 2.0f;
|
harmony_mix = mix * 2.0f;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
harmony_mix = 1.0f;
|
harmony_mix = 1.0f;
|
||||||
melody_mix = (1.0f - mix) * 2.0f;
|
melody_mix = (1.0f - mix) * 2.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,6 +157,7 @@ public:
|
|||||||
float getInputPitch() const { return sample_rate_ / in_period; }
|
float getInputPitch() const { return sample_rate_ / in_period; }
|
||||||
float getOutputPitch() const { return sample_rate_ / out_period; }
|
float getOutputPitch() const { return sample_rate_ / out_period; }
|
||||||
void SetHarmonyMix(float mix);
|
void SetHarmonyMix(float mix);
|
||||||
|
void SetAutoTuneEnable(bool enable) { enable_autotune = enable; }
|
||||||
|
|
||||||
float out_midi = 40;
|
float out_midi = 40;
|
||||||
ShifterVoice voices[MAX_VOICES];
|
ShifterVoice voices[MAX_VOICES];
|
||||||
@ -219,6 +220,7 @@ private:
|
|||||||
float cos_lookup[8192];
|
float cos_lookup[8192];
|
||||||
float sample_rate_;
|
float sample_rate_;
|
||||||
int blocksize;
|
int blocksize;
|
||||||
|
bool enable_autotune = false;
|
||||||
MidiPitchSmoother out_midi_smoother;
|
MidiPitchSmoother out_midi_smoother;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
Reference in New Issue
Block a user