kopia lustrzana https://github.com/jameshball/osci-render
Slightly improve Lua variable performance, and add control for MIDI voices
rodzic
b85318a4fd
commit
fd8935a589
|
@ -5,14 +5,33 @@ MidiComponent::MidiComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
|
|||
setText("MIDI Settings");
|
||||
|
||||
addAndMakeVisible(midiToggle);
|
||||
addAndMakeVisible(voicesSlider);
|
||||
addAndMakeVisible(voicesLabel);
|
||||
addAndMakeVisible(keyboard);
|
||||
|
||||
midiToggle.setToggleState(audioProcessor.midiEnabled->getBoolValue(), juce::dontSendNotification);
|
||||
midiToggle.setTooltip("Enable MIDI input for the synth. If disabled, the synth will play a constant tone, as controlled by the frequency slider.");
|
||||
|
||||
midiToggle.onClick = [this]() {
|
||||
audioProcessor.midiEnabled->setBoolValueNotifyingHost(midiToggle.getToggleState());
|
||||
};
|
||||
|
||||
audioProcessor.midiEnabled->addListener(this);
|
||||
|
||||
voicesSlider.setRange(1, 16, 1);
|
||||
voicesSlider.setValue(audioProcessor.voices->getValueUnnormalised(), juce::dontSendNotification);
|
||||
voicesSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 50, 20);
|
||||
|
||||
voicesLabel.setText("Voices", juce::dontSendNotification);
|
||||
voicesLabel.attachToComponent(&voicesSlider, true);
|
||||
voicesLabel.setTooltip("Number of voices for the synth to use. Larger numbers will use more CPU, and may cause audio glitches.");
|
||||
|
||||
voicesSlider.onValueChange = [this]() {
|
||||
audioProcessor.voices->setUnnormalisedValueNotifyingHost(voicesSlider.getValue());
|
||||
};
|
||||
|
||||
audioProcessor.voices->addListener(this);
|
||||
|
||||
addAndMakeVisible(envelope);
|
||||
envelope.setAdsrMode(true);
|
||||
envelope.setEnv(audioProcessor.adsrEnv);
|
||||
|
@ -39,6 +58,9 @@ MidiComponent::~MidiComponent() {
|
|||
audioProcessor.sustainLevel->removeListener(this);
|
||||
audioProcessor.releaseTime->removeListener(this);
|
||||
audioProcessor.releaseShape->removeListener(this);
|
||||
|
||||
audioProcessor.midiEnabled->removeListener(this);
|
||||
audioProcessor.voices->removeListener(this);
|
||||
}
|
||||
|
||||
void MidiComponent::parameterValueChanged(int parameterIndex, float newValue) {
|
||||
|
@ -48,6 +70,9 @@ void MidiComponent::parameterValueChanged(int parameterIndex, float newValue) {
|
|||
void MidiComponent::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {}
|
||||
|
||||
void MidiComponent::handleAsyncUpdate() {
|
||||
midiToggle.setToggleState(audioProcessor.midiEnabled->getBoolValue(), juce::dontSendNotification);
|
||||
voicesSlider.setValue(audioProcessor.voices->getValueUnnormalised(), juce::dontSendNotification);
|
||||
|
||||
Env newEnv = Env(
|
||||
{
|
||||
0.0,
|
||||
|
@ -74,7 +99,10 @@ void MidiComponent::handleAsyncUpdate() {
|
|||
|
||||
void MidiComponent::resized() {
|
||||
auto area = getLocalBounds().withTrimmedTop(20).reduced(20);
|
||||
midiToggle.setBounds(area.removeFromTop(30));
|
||||
auto topRow = area.removeFromTop(30);
|
||||
midiToggle.setBounds(topRow.removeFromLeft(120));
|
||||
topRow.removeFromLeft(80);
|
||||
voicesSlider.setBounds(topRow.removeFromLeft(250));
|
||||
area.removeFromTop(5);
|
||||
keyboard.setBounds(area.removeFromBottom(50));
|
||||
envelope.setBounds(area);
|
||||
|
|
|
@ -20,6 +20,8 @@ private:
|
|||
OscirenderAudioProcessorEditor& pluginEditor;
|
||||
|
||||
juce::ToggleButton midiToggle{"Enable MIDI"};
|
||||
juce::Slider voicesSlider;
|
||||
juce::Label voicesLabel;
|
||||
juce::MidiKeyboardComponent keyboard{audioProcessor.keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard};
|
||||
|
||||
EnvelopeContainerComponent envelope;
|
||||
|
|
|
@ -157,9 +157,12 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
addParameter(releaseTime);
|
||||
addParameter(releaseShape);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0; i < voices->getValueUnnormalised(); i++) {
|
||||
synth.addVoice(new ShapeVoice(*this));
|
||||
}
|
||||
|
||||
addParameter(voices);
|
||||
voices->addListener(this);
|
||||
|
||||
synth.addSound(defaultSound);
|
||||
}
|
||||
|
@ -168,6 +171,7 @@ OscirenderAudioProcessor::~OscirenderAudioProcessor() {
|
|||
for (auto& effect : luaEffects) {
|
||||
effect->removeListener(0, this);
|
||||
}
|
||||
voices->removeListener(this);
|
||||
}
|
||||
|
||||
const juce::String OscirenderAudioProcessor::getName() const {
|
||||
|
@ -766,6 +770,22 @@ void OscirenderAudioProcessor::parameterValueChanged(int parameterIndex, float n
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (parameterIndex == voices->getParameterIndex()) {
|
||||
int numVoices = voices->getValueUnnormalised();
|
||||
// if the number of voices has changed, update the synth without clearing all the voices
|
||||
if (numVoices != synth.getNumVoices()) {
|
||||
if (numVoices > synth.getNumVoices()) {
|
||||
for (int i = synth.getNumVoices(); i < numVoices; i++) {
|
||||
synth.addVoice(new ShapeVoice(*this));
|
||||
}
|
||||
} else {
|
||||
for (int i = synth.getNumVoices() - 1; i >= numVoices; i--) {
|
||||
synth.removeVoice(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {}
|
||||
|
|
|
@ -270,6 +270,8 @@ public:
|
|||
|
||||
juce::MidiKeyboardState keyboardState;
|
||||
|
||||
IntParameter* voices = new IntParameter("Voices", "voices", VERSION_HINT, 4, 1, 16);
|
||||
|
||||
private:
|
||||
juce::SpinLock consumerLock;
|
||||
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
||||
|
|
|
@ -27,7 +27,7 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect) :
|
|||
void EffectComponent::setupComponent() {
|
||||
EffectParameter* parameter = effect.parameters[index];
|
||||
|
||||
label.setTooltip(parameter->description);
|
||||
setTooltip(parameter->description);
|
||||
label.setText(parameter->name, juce::dontSendNotification);
|
||||
label.setInterceptsMouseClicks(false, false);
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
#include "../PluginEditor.h"
|
||||
|
||||
juce::StringArray MainMenuBarModel::getMenuBarNames() {
|
||||
return juce::StringArray("File", "Options");
|
||||
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
|
||||
return juce::StringArray("File", "Options");
|
||||
} else {
|
||||
return juce::StringArray("File");
|
||||
}
|
||||
}
|
||||
|
||||
juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
|
||||
|
|
|
@ -59,10 +59,14 @@ void LuaParser::parse(lua_State*& L) {
|
|||
|
||||
// only the audio thread runs this fuction
|
||||
std::vector<float> LuaParser::run(lua_State*& L, const LuaVariables vars, long& step, double& phase) {
|
||||
juce::SpinLock::ScopedLockType lock(variableLock);
|
||||
|
||||
// if we haven't seen this state before, reset it
|
||||
if (std::find(seenStates.begin(), seenStates.end(), L) == seenStates.end()) {
|
||||
int stateIndex = std::find(seenStates.begin(), seenStates.end(), L) - seenStates.begin();
|
||||
if (stateIndex == seenStates.size()) {
|
||||
reset(L, script);
|
||||
seenStates.push_back(L);
|
||||
staleStates.push_back(true);
|
||||
}
|
||||
|
||||
std::vector<float> values;
|
||||
|
@ -79,14 +83,14 @@ std::vector<float> LuaParser::run(lua_State*& L, const LuaVariables vars, long&
|
|||
lua_pushnumber(L, phase);
|
||||
lua_setglobal(L, "phase");
|
||||
|
||||
// this CANNOT run at the same time as setVariable
|
||||
juce::SpinLock::ScopedTryLockType lock(variableLock);
|
||||
if (lock.isLocked()) {
|
||||
if (staleStates[stateIndex]) {
|
||||
// update variables
|
||||
for (int i = 0; i < variableNames.size(); i++) {
|
||||
lua_pushnumber(L, variables[i]);
|
||||
lua_setglobal(L, variableNames[i].toUTF8());
|
||||
}
|
||||
}
|
||||
staleStates[stateIndex] = false;
|
||||
}
|
||||
|
||||
lua_geti(L, LUA_REGISTRYINDEX, functionRef);
|
||||
|
||||
|
@ -147,15 +151,26 @@ void LuaParser::setVariable(juce::String variableName, double value) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (index == -1) {
|
||||
// add new variable
|
||||
variableNames.push_back(variableName);
|
||||
variables.push_back(value);
|
||||
changed = true;
|
||||
} else {
|
||||
// update existing variable
|
||||
changed = variables[index] != value;
|
||||
variables[index] = value;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
// mark all states as stale
|
||||
for (int i = 0; i < staleStates.size(); i++) {
|
||||
staleStates[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LuaParser::isFunctionValid() {
|
||||
|
|
|
@ -43,4 +43,5 @@ private:
|
|||
std::function<void(int, juce::String, juce::String)> errorCallback;
|
||||
juce::String fileName;
|
||||
std::vector<lua_State*> seenStates;
|
||||
std::vector<bool> staleStates;
|
||||
};
|
|
@ -58,10 +58,12 @@ double CircleArc::length() {
|
|||
len = 0;
|
||||
// TODO: Replace this, it's stupid. Do a real approximation.
|
||||
int segments = 5;
|
||||
Vector2 start;
|
||||
Vector2 end = nextVector(0);
|
||||
for (int i = 0; i < segments; i++) {
|
||||
Vector2 v1 = nextVector(i / (double) segments);
|
||||
Vector2 v2 = nextVector((i + 1) / (double) segments);
|
||||
len += Line(v1.x, v1.y, v2.x, v2.y).length();
|
||||
start = end;
|
||||
end = nextVector((i + 1) / (double) segments);
|
||||
len += Line::length(start.x, start.y, end.x, end.y);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
|
|
|
@ -38,6 +38,14 @@ void Line::translate(double x, double y) {
|
|||
y2 += y;
|
||||
}
|
||||
|
||||
double Line::length(double x1, double y1, double x2, double y2) {
|
||||
// Euclidean distance approximation based on octagonal boundary
|
||||
double dx = std::abs(x2 - x1);
|
||||
double dy = std::abs(y2 - y1);
|
||||
|
||||
return 0.41 * std::min(dx, dy) + 0.941246 * std::max(dx, dy);
|
||||
}
|
||||
|
||||
double inline Line::length() {
|
||||
if (len < 0) {
|
||||
// Euclidean distance approximation based on octagonal boundary
|
||||
|
|
|
@ -11,6 +11,7 @@ public:
|
|||
void rotate(double theta) override;
|
||||
void scale(double x, double y) override;
|
||||
void translate(double x, double y) override;
|
||||
static double length(double x1, double y1, double x2, double y2);
|
||||
double length() override;
|
||||
std::unique_ptr<Shape> clone() override;
|
||||
std::string type() override;
|
||||
|
|
Ładowanie…
Reference in New Issue