Allow Lua sliders to be animated, and smoothened

pull/222/head
James Ball 2024-02-26 22:11:37 +00:00 zatwierdzone przez James H Ball
rodzic 292d8fe96d
commit f3bc547588
14 zmienionych plików z 112 dodań i 159 usunięć

Wyświetl plik

@ -380,7 +380,6 @@ void OscirenderAudioProcessorEditor::updateCodeDocument() {
if (editingCustomFunction) {
juce::String file = codeDocuments[0]->getAllContent();
audioProcessor.customEffect->updateCode(file);
audioProcessor.updateLuaValues();
} else {
int originalIndex = audioProcessor.getCurrentFileIndex();
int index = audioProcessor.getCurrentFileIndex();

Wyświetl plik

@ -15,7 +15,6 @@
#include "audio/SmoothEffect.h"
#include "audio/BitCrushEffect.h"
#include "audio/BulgeEffect.h"
#include "audio/LuaEffect.h"
#include "audio/EffectParameter.h"
//==============================================================================
@ -132,10 +131,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
new EffectParameter("Dash Length", "Controls the length of the dashed line.", "dashLength", VERSION_HINT, 0.2, 0.0, 1.0),
}
));
toggleableEffects.push_back(std::make_shared<Effect>(
customEffect,
new EffectParameter("Lua Effect", "Controls the strength of the custom Lua effect applied. You can write your own custom effect using Lua by pressing the edit button on the right.", "customEffectStrength", VERSION_HINT, 1.0, 0.0, 1.0)
));
toggleableEffects.push_back(custom);
toggleableEffects.push_back(traceMax);
toggleableEffects.push_back(traceMin);
@ -156,10 +152,6 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
addLuaSlider();
}
for (auto& effect : luaEffects) {
effect->addListener(0, this);
}
allEffects = toggleableEffects;
allEffects.insert(allEffects.end(), permanentEffects.begin(), permanentEffects.end());
allEffects.insert(allEffects.end(), luaEffects.begin(), luaEffects.end());
@ -209,9 +201,6 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
}
OscirenderAudioProcessor::~OscirenderAudioProcessor() {
for (auto& effect : luaEffects) {
effect->removeListener(0, this);
}
voices->removeListener(this);
}
@ -317,7 +306,8 @@ bool OscirenderAudioProcessor::isBusesLayoutSupported (const BusesLayout& layout
void OscirenderAudioProcessor::addLuaSlider() {
juce::String sliderName = "";
int sliderNum = luaEffects.size() + 1;
int sliderIndex = luaEffects.size();
int sliderNum = sliderIndex + 1;
while (sliderNum > 0) {
int mod = (sliderNum - 1) % 26;
sliderName = (char)(mod + 'A') + sliderName;
@ -325,25 +315,16 @@ void OscirenderAudioProcessor::addLuaSlider() {
}
luaEffects.push_back(std::make_shared<Effect>(
std::make_shared<LuaEffect>(sliderName, *this),
new EffectParameter(
[this, sliderIndex](int index, Point input, const std::vector<double>& values, double sampleRate) {
luaValues[sliderIndex] = values[0];
return input;
}, new EffectParameter(
"Lua Slider " + sliderName,
"Controls the value of the Lua variable called slider_" + sliderName.toLowerCase() + ".",
"lua" + sliderName,
VERSION_HINT, 0.0, 0.0, 1.0, 0.001, false
)
));
auto& effect = luaEffects.back();
effect->parameters[0]->disableLfo();
effect->parameters[0]->disableSidechain();
}
// effectsLock should be held when calling this
void OscirenderAudioProcessor::updateLuaValues() {
for (auto& effect : luaEffects) {
effect->apply();
}
}
void OscirenderAudioProcessor::addErrorListener(ErrorListener* listener) {
@ -493,7 +474,6 @@ void OscirenderAudioProcessor::changeCurrentFile(int index) {
return;
}
currentFile = index;
updateLuaValues();
changeSound(sounds[index]);
}
@ -666,6 +646,12 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
for (auto& effect : permanentEffects) {
channels = effect->apply(sample, channels, currentVolume);
}
auto lua = currentFile >= 0 ? sounds[currentFile]->parser->getLua() : nullptr;
if (lua != nullptr || custom->enabled->getBoolValue()) {
for (auto& effect : luaEffects) {
effect->apply(sample, channels, currentVolume);
}
}
}
double x = channels.x;
@ -889,14 +875,6 @@ void OscirenderAudioProcessor::consumerStop(std::shared_ptr<BufferConsumer> cons
}
void OscirenderAudioProcessor::parameterValueChanged(int parameterIndex, float newValue) {
// call apply on lua effects
for (auto& effect : luaEffects) {
if (parameterIndex == effect->parameters[0]->getParameterIndex()) {
effect->apply();
return;
}
}
if (parameterIndex == voices->getParameterIndex()) {
int numVoices = voices->getValueUnnormalised();
// if the number of voices has changed, update the synth without clearing all the voices

Wyświetl plik

@ -80,6 +80,7 @@ public:
juce::SpinLock effectsLock;
std::vector<std::shared_ptr<Effect>> toggleableEffects;
std::vector<std::shared_ptr<Effect>> luaEffects;
double luaValues[26] = { 0.0 };
std::shared_ptr<Effect> frequencyEffect = std::make_shared<Effect>(
[this](int index, Point input, const std::vector<double>& values, double sampleRate) {
@ -143,8 +144,12 @@ public:
std::shared_ptr<DashedLineEffect> dashedLineEffect = std::make_shared<DashedLineEffect>();
std::function<void(int, juce::String, juce::String)> errorCallback = [this](int lineNum, juce::String fileName, juce::String error) { notifyErrorListeners(lineNum, fileName, error); };
std::shared_ptr<CustomEffect> customEffect = std::make_shared<CustomEffect>(errorCallback);
std::shared_ptr<CustomEffect> customEffect = std::make_shared<CustomEffect>(errorCallback, luaValues);
std::shared_ptr<Effect> custom = std::make_shared<Effect>(
customEffect,
new EffectParameter("Lua Effect", "Controls the strength of the custom Lua effect applied. You can write your own custom effect using Lua by pressing the edit button on the right.", "customEffectStrength", VERSION_HINT, 1.0, 0.0, 1.0)
);
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>();
std::shared_ptr<Effect> perspective = std::make_shared<Effect>(
perspectiveEffect,
@ -227,7 +232,6 @@ public:
juce::String getFileId(int index);
std::shared_ptr<juce::MemoryBlock> getFileBlock(int index);
void setObjectServerRendering(bool enabled);
void updateLuaValues();
void addErrorListener(ErrorListener* listener);
void removeErrorListener(ErrorListener* listener);
void notifyErrorListeners(int lineNumber, juce::String fileName, juce::String error);

Wyświetl plik

@ -4,7 +4,9 @@
const juce::String CustomEffect::FILE_NAME = "6a3580b0-c5fc-4b28-a33e-e26a487f052f";
CustomEffect::CustomEffect(std::function<void(int, juce::String, juce::String)> errorCallback) : errorCallback(errorCallback) {}
CustomEffect::CustomEffect(std::function<void(int, juce::String, juce::String)> errorCallback, double (&luaValues)[26]) : errorCallback(errorCallback), luaValues(luaValues) {
vars.isEffect = true;
}
CustomEffect::~CustomEffect() {
parser->close(L);
@ -20,11 +22,16 @@ Point CustomEffect::apply(int index, Point input, const std::vector<double>& val
{
juce::SpinLock::ScopedLockType lock(codeLock);
if (!defaultScript) {
parser->setVariable("x", x);
parser->setVariable("y", y);
parser->setVariable("z", z);
vars.sampleRate = sampleRate;
vars.frequency = frequency;
auto result = parser->run(L, LuaVariables{sampleRate, frequency}, step, phase);
vars.x = x;
vars.y = y;
vars.z = z;
std::copy(std::begin(luaValues), std::end(luaValues), std::begin(vars.sliders));
auto result = parser->run(L, vars);
if (result.size() >= 2) {
x = result[0];
y = result[1];
@ -37,10 +44,6 @@ Point CustomEffect::apply(int index, Point input, const std::vector<double>& val
}
}
step++;
phase += 2 * std::numbers::pi * frequency / sampleRate;
phase = MathUtil::wrapAngle(phase);
return Point(
(1 - effectScale) * input.x + effectScale * x,
(1 - effectScale) * input.y + effectScale * y,
@ -55,13 +58,6 @@ void CustomEffect::updateCode(const juce::String& newCode) {
parser = std::make_unique<LuaParser>(FILE_NAME, code, errorCallback);
}
void CustomEffect::setVariable(juce::String variableName, double value) {
juce::SpinLock::ScopedLockType lock(codeLock);
if (!defaultScript) {
parser->setVariable(variableName, value);
}
}
juce::String CustomEffect::getCode() {
juce::SpinLock::ScopedLockType lock(codeLock);
return code;

Wyświetl plik

@ -6,7 +6,7 @@
class CustomEffect : public EffectApplication {
public:
CustomEffect(std::function<void(int, juce::String, juce::String)> errorCallback);
CustomEffect(std::function<void(int, juce::String, juce::String)> errorCallback, double (&luaValues)[26]);
~CustomEffect();
// arbitrary UUID
@ -14,7 +14,6 @@ public:
Point apply(int index, Point input, const std::vector<double>& values, double sampleRate) override;
void updateCode(const juce::String& newCode);
void setVariable(juce::String variableName, double value);
juce::String getCode();
@ -31,6 +30,6 @@ private:
lua_State *L = nullptr;
long step = 1;
double phase = 0;
LuaVariables vars;
double(&luaValues)[26];
};

Wyświetl plik

@ -1,17 +0,0 @@
#include "LuaEffect.h"
#include "../lua/LuaParser.h"
Point LuaEffect::apply(int index, Point input, const std::vector<double>& values, double sampleRate) {
int fileIndex = audioProcessor.getCurrentFileIndex();
if (fileIndex == -1) {
return input;
}
std::shared_ptr<LuaParser> parser = audioProcessor.getCurrentFileParser()->getLua();
if (parser != nullptr) {
parser->setVariable("slider_" + name.toLowerCase(), values[0]);
}
audioProcessor.customEffect->setVariable("slider_" + name.toLowerCase(), values[0]);
return input;
}

Wyświetl plik

@ -1,15 +0,0 @@
#pragma once
#include "EffectApplication.h"
#include "../shape/Point.h"
#include "../audio/Effect.h"
#include "../PluginProcessor.h"
class LuaEffect : public EffectApplication {
public:
LuaEffect(juce::String name, OscirenderAudioProcessor& p) : audioProcessor(p), name(name) {};
Point apply(int index, Point input, const std::vector<double>& values, double sampleRate) override;
private:
OscirenderAudioProcessor& audioProcessor;
juce::String name;
};

Wyświetl plik

@ -108,7 +108,11 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
renderingSample = parser != nullptr && parser->isSample();
if (renderingSample) {
channels = parser->nextSample(L, LuaVariables{ audioProcessor.currentSampleRate, actualFrequency }, step, phase);
vars.sampleRate = audioProcessor.currentSampleRate;
vars.frequency = actualFrequency;
std::copy(std::begin(audioProcessor.luaValues), std::end(audioProcessor.luaValues), std::begin(vars.sliders));
channels = parser->nextSample(L, vars);
} else if (currentShape < frame.size()) {
auto& shape = frame[currentShape];
double length = shape->length();

Wyświetl plik

@ -43,8 +43,7 @@ private:
double pitchWheelAdjustment = 1.0;
lua_State* L = nullptr;
long step = 1;
double phase = 0;
LuaVariables vars;
Env adsr;
double time = 0.0;

Wyświetl plik

@ -58,20 +58,17 @@ 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);
std::vector<float> LuaParser::run(lua_State*& L, LuaVariables& vars) {
// if we haven't seen this state before, reset it
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;
lua_pushnumber(L, step);
lua_pushnumber(L, vars.step);
lua_setglobal(L, "step");
lua_pushnumber(L, vars.sampleRate);
@ -80,16 +77,23 @@ std::vector<float> LuaParser::run(lua_State*& L, const LuaVariables vars, long&
lua_pushnumber(L, vars.frequency);
lua_setglobal(L, "frequency");
lua_pushnumber(L, phase);
lua_pushnumber(L, vars.phase);
lua_setglobal(L, "phase");
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;
for (int i = 0; i < NUM_SLIDERS; i++) {
lua_pushnumber(L, vars.sliders[i]);
lua_setglobal(L, SLIDER_NAMES[i]);
}
if (vars.isEffect) {
lua_pushnumber(L, vars.x);
lua_setglobal(L, "x");
lua_pushnumber(L, vars.y);
lua_setglobal(L, "y");
lua_pushnumber(L, vars.z);
lua_setglobal(L, "z");
}
lua_geti(L, LUA_REGISTRYINDEX, functionRef);
@ -130,49 +134,15 @@ std::vector<float> LuaParser::run(lua_State*& L, const LuaVariables vars, long&
// clear stack
lua_settop(L, 0);
step++;
phase += 2 * std::numbers::pi * vars.frequency / vars.sampleRate;
if (phase > 2 * std::numbers::pi) {
phase -= 2 * std::numbers::pi;
vars.step++;
vars.phase += 2 * std::numbers::pi * vars.frequency / vars.sampleRate;
if (vars.phase > 2 * std::numbers::pi) {
vars.phase -= 2 * std::numbers::pi;
}
return values;
}
// this CANNOT run at the same time as run()
// many threads can run this function
void LuaParser::setVariable(juce::String variableName, double value) {
juce::SpinLock::ScopedLockType lock(variableLock);
// find variable index
int index = -1;
for (int i = 0; i < variableNames.size(); i++) {
if (variableNames[i] == variableName) {
index = i;
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() {
return functionRef != -1;
}

Wyświetl plik

@ -10,9 +10,52 @@ public:
virtual juce::String getFileName() = 0;
};
const int NUM_SLIDERS = 26;
const char SLIDER_NAMES[NUM_SLIDERS][9] = {
"slider_a",
"slider_b",
"slider_c",
"slider_d",
"slider_e",
"slider_f",
"slider_g",
"slider_h",
"slider_i",
"slider_j",
"slider_k",
"slider_l",
"slider_m",
"slider_n",
"slider_o",
"slider_p",
"slider_q",
"slider_r",
"slider_s",
"slider_t",
"slider_u",
"slider_v",
"slider_w",
"slider_x",
"slider_y",
"slider_z",
};
struct LuaVariables {
double sampleRate;
double frequency;
double sliders[NUM_SLIDERS] = { 0.0 };
double step = 1;
double phase = 0;
double sampleRate = 0;
double frequency = 0;
// x, y, z are only used for effects
bool isEffect = false;
double x = 0;
double y = 0;
double z = 0;
};
struct lua_State;
@ -20,8 +63,7 @@ class LuaParser {
public:
LuaParser(juce::String fileName, juce::String script, std::function<void(int, juce::String, juce::String)> errorCallback, juce::String fallbackScript = "return { 0.0, 0.0 }");
std::vector<float> run(lua_State*& L, const LuaVariables vars, long& step, double& phase);
void setVariable(juce::String variableName, double value);
std::vector<float> run(lua_State*& L, LuaVariables& vars);
bool isFunctionValid();
juce::String getScript();
void resetErrors();
@ -37,11 +79,7 @@ private:
juce::String script;
juce::String fallbackScript;
std::atomic<bool> updateVariables = false;
juce::SpinLock variableLock;
std::vector<juce::String> variableNames;
std::vector<double> variables;
std::function<void(int, juce::String, juce::String)> errorCallback;
juce::String fileName;
std::vector<lua_State*> seenStates;
std::vector<bool> staleStates;
};

Wyświetl plik

@ -49,11 +49,11 @@ std::vector<std::unique_ptr<Shape>> FileParser::nextFrame() {
return tempShapes;
}
Point FileParser::nextSample(lua_State*& L, const LuaVariables vars, long& step, double& phase) {
Point FileParser::nextSample(lua_State*& L, LuaVariables& vars) {
juce::SpinLock::ScopedLockType scope(lock);
if (lua != nullptr) {
auto values = lua->run(L, vars, step, phase);
auto values = lua->run(L, vars);
if (values.size() == 2) {
return Point(values[0], values[1], 0);
} else if (values.size() > 2) {

Wyświetl plik

@ -13,7 +13,7 @@ public:
void parse(juce::String fileName, juce::String extension, std::unique_ptr<juce::InputStream>, juce::Font);
std::vector<std::unique_ptr<Shape>> nextFrame();
Point nextSample(lua_State*& L, const LuaVariables vars, long& step, double& phase);
Point nextSample(lua_State*& L, LuaVariables& vars);
void closeLua(lua_State*& L);
bool isSample();
bool isActive();

Wyświetl plik

@ -5,7 +5,7 @@
pluginCharacteristicsValue="pluginProducesMidiOut,pluginWantsMidiIn"
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
cppLanguageStandard="20" projectLineFeed="&#10;" headerPath="./include"
version="2.1.0" companyName="James H Ball" companyWebsite="https://osci-render.com"
version="2.1.1" companyName="James H Ball" companyWebsite="https://osci-render.com"
companyEmail="james@ball.sh" defines="NOMINMAX=1">
<MAINGROUP id="j5Ge2T" name="osci-render">
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
@ -162,8 +162,6 @@
file="Source/audio/EffectApplication.h"/>
<FILE id="aE0RtD" name="EffectParameter.h" compile="0" resource="0"
file="Source/audio/EffectParameter.h"/>
<FILE id="uhyh7T" name="LuaEffect.cpp" compile="1" resource="0" file="Source/audio/LuaEffect.cpp"/>
<FILE id="jqDcZq" name="LuaEffect.h" compile="0" resource="0" file="Source/audio/LuaEffect.h"/>
<FILE id="QBWW9w" name="PerspectiveEffect.cpp" compile="1" resource="0"
file="Source/audio/PerspectiveEffect.cpp"/>
<FILE id="h0dMim" name="PerspectiveEffect.h" compile="0" resource="0"