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 * as Juce from "juce-framework-frontend";
|
||||
import JuceSlider from "./Components/JuceSlider.js";
|
||||
import JuceCheckbox from "./Components/JuceCheckbox.js";
|
||||
import MidiNoteInfo from "./Components/MidiNoteInfo.js";
|
||||
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
|
||||
|
||||
@ -50,6 +51,7 @@ function App() {
|
||||
<Container>
|
||||
<JuceSlider identifier="harmonyMix" title="Mix" />
|
||||
<JuceSlider identifier="formantPreserve" title="Formant" />
|
||||
<JuceCheckbox identifier="autoTuneEnabled" />
|
||||
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
||||
<JuceSlider identifier="autoTuneDepth" title="Auto Tune Depth" />
|
||||
<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());
|
||||
}
|
||||
|
||||
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);
|
||||
addAndMakeVisible(*webComponent);
|
||||
|
||||
|
||||
@ -38,6 +38,13 @@ public:
|
||||
for (auto& relays : slider_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);
|
||||
@ -83,6 +90,9 @@ private:
|
||||
std::vector<WebSliderRelay*> slider_relays;
|
||||
std::vector< WebSliderParameterAttachment*> slider_attatchments;
|
||||
|
||||
std::vector<WebToggleButtonRelay*> toggle_relays;
|
||||
std::vector< WebToggleButtonParameterAttachment*> toggle_attatchments;
|
||||
|
||||
WebControlParameterIndexReceiver controlParameterIndexReceiver;
|
||||
|
||||
SinglePageBrowser* webComponent = nullptr;
|
||||
|
||||
@ -67,6 +67,7 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
||||
shifter.SetAutoTuneDepth(state.getParameterAsValue("autoTuneDepth").getValue());
|
||||
shifter.SetPortamentoTime(state.getParameterAsValue("portTime").getValue());
|
||||
shifter.SetHarmonyMix(state.getParameterAsValue("harmonyMix").getValue());
|
||||
shifter.SetAutoTuneEnable(state.getParameterAsValue("autoTuneEnabled").getValue());
|
||||
juce::AudioBuffer<float> const_buff;
|
||||
const_buff.makeCopyOf(buffer);
|
||||
shifter.Process(const_buff.getArrayOfReadPointers(), (float**)buffer.getArrayOfWritePointers(), buffer.getNumSamples());
|
||||
|
||||
@ -87,13 +87,21 @@ public:
|
||||
NormalisableRange<float> {0.001f, 0.2f, .001f},
|
||||
.01f);
|
||||
|
||||
|
||||
toggleIds.push_back("autoTuneEnabled");
|
||||
addToLayout<AudioParameterBool>(layout,
|
||||
ParameterID("autoTuneEnabled"),
|
||||
"AutoTune Enabled",
|
||||
false);
|
||||
}
|
||||
|
||||
|
||||
/*AudioParameterFloat& formantPreserve;
|
||||
AudioParameterFloat& autoTuneSpeed;
|
||||
AudioParameterFloat& autoTuneDepth;
|
||||
AudioParameterFloat& portTime;*/
|
||||
std::vector<juce::String> sliderIds;
|
||||
std::vector<juce::String> toggleIds;
|
||||
/*AudioParameterBool& mute;
|
||||
AudioParameterChoice& filterType;*/
|
||||
|
||||
|
||||
@ -113,7 +113,7 @@ void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ void Shifter::SetRates() {}
|
||||
|
||||
float Shifter::GetOutputEnvelopePeriod(int out_voice) {
|
||||
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
|
||||
return in_period * formant_preserve + voices[out_voice].CurrentPeriod() * (1.0 - formant_preserve);
|
||||
@ -203,11 +203,12 @@ void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_pe
|
||||
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;
|
||||
if(voice >= MAX_VOICES) {
|
||||
if (voice >= MAX_VOICES) {
|
||||
//value *= volume;
|
||||
out_buffer[0][out_index] += value * melody_mix;
|
||||
out_buffer[1][out_index] += value * melody_mix;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
value *= voices[voice].CurrentAmplitude() * volume;
|
||||
out_buffer[0][out_index] += value * voices[voice].GetPanning(0) * harmony_mix;
|
||||
out_buffer[1][out_index] += value * voices[voice].GetPanning(1) * harmony_mix;
|
||||
@ -249,9 +250,11 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||
AddInterpolatedFrame(out_p, max_index, resampling_period);
|
||||
}
|
||||
}
|
||||
|
||||
if (out_period_counter > out_period)
|
||||
{
|
||||
out_period_counter -= out_period;
|
||||
if (enable_autotune) {
|
||||
float resampling_period = GetOutputEnvelopePeriod(MAX_VOICES);
|
||||
|
||||
|
||||
@ -262,12 +265,19 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
||||
//add samples centered on that max
|
||||
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];
|
||||
if (!enable_autotune) {
|
||||
output[ch][i] += input[i] * melody_mix;
|
||||
}
|
||||
out_buffer[ch][out_playhead] = 0;
|
||||
}
|
||||
|
||||
|
||||
@ -157,6 +157,7 @@ public:
|
||||
float getInputPitch() const { return sample_rate_ / in_period; }
|
||||
float getOutputPitch() const { return sample_rate_ / out_period; }
|
||||
void SetHarmonyMix(float mix);
|
||||
void SetAutoTuneEnable(bool enable) { enable_autotune = enable; }
|
||||
|
||||
float out_midi = 40;
|
||||
ShifterVoice voices[MAX_VOICES];
|
||||
@ -219,6 +220,7 @@ private:
|
||||
float cos_lookup[8192];
|
||||
float sample_rate_;
|
||||
int blocksize;
|
||||
bool enable_autotune = false;
|
||||
MidiPitchSmoother out_midi_smoother;
|
||||
};
|
||||
#endif
|
||||
Reference in New Issue
Block a user