Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LuaJIT, .gif failsafe, and external input to Lua #259

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

DJLevel3
Copy link
Contributor

As advertised, this PR switches from the basic Lua interpreter to LuaJIT, a just-in-time compiler for Lua that offers immense performance gains. Additionally, it adds a failsafe to .gif loading that prevents a crash if the file fails to load, and it allows Lua programs to access the external audio input to osci-render.

@DJLevel3
Copy link
Contributor Author

Warning: Currently MIDI seems to be bugged. Working on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes it so that when the GIF decoder fails to decode an image, it falls back to a test pattern instead of crashing entirely.

@@ -325,7 +321,7 @@ static int luaPrint(lua_State* L) {
int nargs = lua_gettop(L);

for (int i = 1; i <= nargs; ++i) {
LuaParser::onPrint(luaL_tolstring(L, i, nullptr));
LuaParser::onPrint(lua_tolstring(L, i, nullptr));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backing up to Lua 5.1 to use LuaJIT

@@ -468,7 +467,7 @@ void LuaParser::revertToFallback(lua_State*& L) {
}

void LuaParser::readTable(lua_State*& L, std::vector<float>& values) {
auto length = lua_rawlen(L, -1);
auto length = lua_objlen(L, -1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backing up to Lua 5.1 to use LuaJIT

@@ -493,7 +492,7 @@ std::vector<float> LuaParser::run(lua_State*& L, LuaVariables& vars) {
setGlobalVariables(L, vars);

// Get the function from the registry
lua_geti(L, LUA_REGISTRYINDEX, functionRef);
lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backing up to Lua 5.1 to use LuaJIT

setGlobalVariable(L, "x", vars.x);
setGlobalVariable(L, "y", vars.y);
setGlobalVariable(L, "x", vars.x);
setGlobalVariable(L, "y", vars.y);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced tabs with spaces (marked as a difference for some reason)

@@ -440,9 +436,12 @@ void LuaParser::setGlobalVariables(lua_State*& L, LuaVariables& vars) {
setGlobalVariable(L, SLIDER_NAMES[i], vars.sliders[i]);
}

setGlobalVariable(L, "ext_x", vars.ext_x);
setGlobalVariable(L, "ext_y", vars.ext_y);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass external input into Lua interpreter

@@ -50,6 +54,9 @@ struct LuaVariables {
double sampleRate = 0;
double frequency = 0;

double ext_x = 0;
double ext_y = 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External input variables for Lua

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is auto-generated by LuaJIT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra fields V and W added to the Point class. This is necessary because some audio effects are not able to take extra arguments. V and W are currently used for carrying external audio into effects, but they could also be used for other things in the future.

}
else {
return std::string("(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split this as adding stuff to the string for a field that's usually 0,0 seems unnecessary unless said stuff contains useful data

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding V and W as described in Point.cpp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-generated by Projucer

.gitmodules Outdated
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add LuaJIT as a submodule


if (usingInput && totalNumInputChannels >= 2) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the copy of the input buffer into the output buffer from inside the check to outside. The buffer gets cleared by the synth instead, unless the audio will be used by Lua.

double sx = outputBuffer3d.getSample(0, sample);
double sy = outputBuffer3d.getSample(1, sample);
double sz = outputBuffer3d.getSample(2, sample);
Point channels = { sx, sy, sz, left, right };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be optimized into the old version if building for Release, but by doing it this way debugging is much easier.

auto lua = currentFile >= 0 ? sounds[currentFile]->parser->getLua() : nullptr;
if (lua != nullptr || custom->enabled->getBoolValue()) {
for (auto& effect : luaEffects) {
effect->apply(sample, channels, currentVolume);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is functionally identical but removes some stray brackets that didn't have any conditional or loop attached to them, making them do nothing.

@@ -19,6 +19,8 @@ Point CustomEffect::apply(int index, Point input, const std::vector<std::atomic<
auto x = input.x;
auto y = input.y;
auto z = input.z;
auto eX = input.v;
auto eY = input.w;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get external input variables from the input point

@@ -30,16 +32,20 @@ Point CustomEffect::apply(int index, Point input, const std::vector<std::atomic<
vars.y = y;
vars.z = z;

vars.ext_x = eX;
vars.ext_y = eY;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass external input variables into the Lua variables


x = result[0];
y = (rs > 1) ? result[1] : y;
z = (rs > 2) ? result[2] : z;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified control flow

(1 - effectScale) * input.z + effectScale * z
(1 - effectScale) * input.z + effectScale * z,
input.v,
input.w
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be changed later so Lua can modify the external input, but at the moment that wouldn't change anything as Lua is the only thing that uses the external input points.

return effectApplication->apply(index, input, actualValues, sampleRate);
} else if (application) {
return application(index, input, actualValues, sampleRate);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapped to give precedence to the EffectApplication rather than the function pointer. This likely has no effect right now, but it makes debugging far easier if both are available.

@@ -13,7 +13,7 @@ bool ShapeVoice::canPlaySound(juce::SynthesiserSound* sound) {
void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) {
this->velocity = velocity;
pitchWheelMoved(currentPitchWheelPosition);
auto* shapeSound = dynamic_cast<ShapeSound*>(sound);
ShapeSound* shapeSound = dynamic_cast<ShapeSound*>(sound);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actual effect, but easier to read. I really should do this for all the code...

if (!(parser != nullptr && parser->isSample())) {
outputBuffer.clear(startSample, numSamples);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear the audio buffer if the current sound is not a Lua synth

double x = 0.0;
double y = 0.0;
double z = 0.0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved for debugging an issue that is now fixed, but could still be useful for later debugging

@@ -111,13 +123,16 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
vars.sampleRate = audioProcessor.currentSampleRate;
vars.frequency = actualFrequency;
std::copy(std::begin(audioProcessor.luaValues), std::end(audioProcessor.luaValues), std::begin(vars.sliders));

vars.ext_x = outputBuffer.getSample(0, sample);
vars.ext_y = outputBuffer.getSample(1, sample);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External input strikes back

double length = shape->length();
double drawingProgress = length == 0.0 ? 1 : shapeDrawn / length;
channels = shape->nextVector(drawingProgress);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split open the if block as I had added some more code before the if statement, but it turned out not to be necessary. Gets optimized into the old version by the compiler.

@@ -162,7 +177,7 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
double drawnFrameLength = traceMaxEnabled ? actualTraceMax * frameLength : frameLength;

if (!renderingSample && frameDrawn >= drawnFrameLength) {
if (sound.load() != nullptr && currentlyPlaying) {
if (sound.load() != nullptr && currentlyPlaying) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace that has no effect on the code whatsoever, but I wanted to add a comment because I spent a minute trying to figure out what changed

@@ -180,10 +195,10 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
}
}
}
return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly put here instead of relying on the compiler. Does nothing new, but adds a place to put a breakpoint during debugging.

@@ -192,6 +207,7 @@ void ShapeVoice::stopNote(float velocity, bool allowTailOff) {

void ShapeVoice::noteStopped() {
clearCurrentNote();
currentlyPlaying = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved here to fix animation during MIDI note release

#include "luaimport.h"
#include "../shape/Line.h"
#include "../shape/CircleArc.h"
#include "../shape/QuadraticBezierCurve.h"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to the header because it's good practice and more readable

Copy link
Contributor Author

@DJLevel3 DJLevel3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments to all the important changes. Things like newlines aren't commented, but they should be obvious.

@DJLevel3
Copy link
Contributor Author

Ready to merge if you think it's ready!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant