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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
de6adce
Update README.md
DJLevel3 Aug 13, 2024
28081cd
Add support for binary GPLA files, both in osci-render and in Blender
DJLevel3 Aug 13, 2024
772b968
Merge Json and Binary Line Art Files
DJLevel3 Aug 14, 2024
15131d1
whoops left the .jucer in an old state lol
DJLevel3 Aug 14, 2024
12efc0c
Typo lol
DJLevel3 Aug 14, 2024
88f5404
Merge branch 'jameshball:main' into gpla-binary
DJLevel3 Aug 21, 2024
9c73a04
Merge osci-render.jucer
DJLevel3 Aug 21, 2024
09044bf
Fix blender 3.1
DJLevel3 Aug 21, 2024
762b183
Make some updates
DJLevel3 Sep 23, 2024
fedecab
Add failsafe to prevent crash when .gif files fail to load
DJLevel3 Sep 23, 2024
30ccb1d
Add external audio input support to the Lua effect
DJLevel3 Sep 23, 2024
a6f5108
Tell Lua about the ext_x and ext_y variables
DJLevel3 Sep 23, 2024
9d9d3f8
More fixes
DJLevel3 Sep 24, 2024
ac72bbf
Revert "Make some updates"
DJLevel3 Sep 24, 2024
899f27d
Merge branch 'gpla-binary' into gif-fix
DJLevel3 Sep 24, 2024
1f948ac
Revert "Merge branch 'gpla-binary' into gif-fix"
DJLevel3 Sep 24, 2024
f60cc0e
Add LuaJIT as a submodule
DJLevel3 Sep 25, 2024
f300215
LuaJIT, baby!
DJLevel3 Sep 25, 2024
aa2d092
Work on getting Lua completely working
DJLevel3 Sep 28, 2024
cc7118a
Get external input working correctly for Lua synths and effects
DJLevel3 Sep 28, 2024
7407a28
Add Xcode support back in
DJLevel3 Sep 28, 2024
f894068
Make MIDI messages work again
DJLevel3 Sep 28, 2024
6d170ff
Convert file extensions to lowercase before checking
DJLevel3 Sep 29, 2024
79dbe86
Remove some debugging code
DJLevel3 Sep 29, 2024
6f4a6ab
Fix bug that made compilation fail
DJLevel3 Sep 30, 2024
e21382f
Make osci-render blender plugin compatible with Blender 4.2+
DJLevel3 Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "LuaJIT"]
path = LuaJIT
url = https://luajit.org/git/luajit.git
Binary file added Include/windows/lua51.lib
Binary file not shown.
1 change: 1 addition & 0 deletions LuaJIT
Submodule LuaJIT added at 87ae18
8 changes: 7 additions & 1 deletion Source/PluginEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,13 @@ OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
}

bool OscirenderAudioProcessorEditor::isBinaryFile(juce::String name) {
return name.endsWith(".gpla") || name.endsWith(".gif") || name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".wav") || name.endsWith(".aiff");
return name.endsWithIgnoreCase(".gpla")
|| name.endsWithIgnoreCase(".gif")
|| name.endsWithIgnoreCase(".png")
|| name.endsWithIgnoreCase(".jpg")
|| name.endsWithIgnoreCase(".jpeg")
|| name.endsWithIgnoreCase(".wav")
|| name.endsWithIgnoreCase(".aiff");
}

// parsersLock must be held
Expand Down
56 changes: 31 additions & 25 deletions Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,11 +629,13 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
juce::AudioBuffer<float> outputBuffer3d = juce::AudioBuffer<float>(3, buffer.getNumSamples());
outputBuffer3d.clear();

if (usingInput && totalNumInputChannels >= 2) {
for (auto channel = 0; channel < juce::jmin(2, totalNumInputChannels); channel++) {
outputBuffer3d.copyFrom(channel, 0, inputBuffer, channel, 0, buffer.getNumSamples());
}
// Put the external input data into the output buffer in case the synth voice is a Lua synth.
// It will be cleared by the synth if this is not the case.
for (int channel = 0; channel < juce::jmin(2, totalNumInputChannels); channel++) {
outputBuffer3d.copyFrom(channel, 0, inputBuffer, channel, 0, buffer.getNumSamples());
}

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.

// handle all midi messages
auto midiIterator = midiMessages.cbegin();
std::for_each(midiIterator,
Expand All @@ -658,13 +660,14 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
auto* channelData = buffer.getArrayOfWritePointers();


for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {

// Update frame animation
if (animateFrames->getValue()) {
if (animationSyncBPM->getValue()) {
animationTime = playTimeBeats;
} else {
}
else {
animationTime = playTimeSeconds;
}

Expand All @@ -676,7 +679,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
auto img = sounds[currentFile]->parser->getImg();
if (lineArt != nullptr) {
lineArt->setFrame(animFrame);
} else if (img != nullptr) {
}
else if (img != nullptr) {
img->setFrame(animFrame);
}
}
Expand All @@ -687,7 +691,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
if (totalNumInputChannels >= 2) {
left = inputBuffer.getSample(0, sample);
right = inputBuffer.getSample(1, sample);
} else if (totalNumInputChannels == 1) {
}
else if (totalNumInputChannels == 1) {
left = inputBuffer.getSample(0, sample);
right = inputBuffer.getSample(0, sample);
}
Expand All @@ -701,26 +706,27 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
currentVolume = std::sqrt(squaredVolume);
currentVolume = juce::jlimit(0.0, 1.0, currentVolume);

Point channels = { outputBuffer3d.getSample(0, sample), outputBuffer3d.getSample(1, sample), outputBuffer3d.getSample(2, sample) };
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.


{
juce::SpinLock::ScopedLockType lock1(parsersLock);
juce::SpinLock::ScopedLockType lock2(effectsLock);
if (volume > EPSILON) {
for (auto& effect : toggleableEffects) {
if (effect->enabled->getValue()) {
channels = effect->apply(sample, channels, currentVolume);
}
juce::SpinLock::ScopedLockType lock1(parsersLock);
juce::SpinLock::ScopedLockType lock2(effectsLock);
if (volume > EPSILON) {
for (auto& effect : toggleableEffects) {
if (effect->enabled->getValue()) {
channels = effect->apply(sample, channels, currentVolume);
}
}
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);
}
}
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);
}
}
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.


Expand Down
24 changes: 16 additions & 8 deletions Source/audio/CustomEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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


{
juce::SpinLock::ScopedLockType lock(codeLock);
Expand All @@ -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


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];
if (result.size() >= 3) {
z = result[2];
}
}
int rs = result.size();

if (rs < 1) return input;

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


} else {
parser->resetErrors();
}
Expand All @@ -48,7 +54,9 @@ Point CustomEffect::apply(int index, Point input, const std::vector<std::atomic<
return Point(
(1 - effectScale) * input.x + effectScale * x,
(1 - effectScale) * input.y + effectScale * y,
(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.

);
}

Expand Down
6 changes: 3 additions & 3 deletions Source/audio/Effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ Effect::Effect(EffectParameter* parameter) : Effect([](int index, Point input, c

Point Effect::apply(int index, Point input, double volume) {
animateValues(volume);
if (application) {
return application(index, input, actualValues, sampleRate);
} else if (effectApplication != nullptr) {
if (effectApplication != nullptr) {
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.

}
return input;
}
Expand Down
58 changes: 33 additions & 25 deletions Source/audio/ShapeVoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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...


currentlyPlaying = true;
this->sound = shapeSound;
Expand Down Expand Up @@ -86,6 +86,15 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
actualFrequency = audioProcessor.frequency;
}

std::shared_ptr<FileParser> parser;

if (sound.load() != nullptr) {
parser = sound.load()->parser;
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


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

for (auto sample = startSample; sample < startSample + numSamples; ++sample) {
bool traceMinEnabled = audioProcessor.traceMin->enabled->getBoolValue();
bool traceMaxEnabled = audioProcessor.traceMax->enabled->getBoolValue();
Expand All @@ -96,10 +105,7 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
double proportionalLength = (traceMax - traceMin) * frameLength;
lengthIncrement = juce::jmax(proportionalLength / (audioProcessor.currentSampleRate / actualFrequency), MIN_LENGTH_INCREMENT);

Point channels;
double x = 0.0;
double y = 0.0;
double z = 0.0;
Point channels = { 0,0,0 };

bool renderingSample = true;

Expand All @@ -111,20 +117,19 @@ 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

channels = parser->nextSample(L, vars);
} else if (currentShape < frame.size()) {
auto& shape = frame[currentShape];
double length = shape->length();
double drawingProgress = length == 0.0 ? 1 : shapeDrawn / length;
channels = shape->nextVector(drawingProgress);
} else {
if (currentShape < frame.size()) {
auto& shape = frame[currentShape];
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.

}
}

x = channels.x;
y = channels.y;
z = channels.z;

time += 1.0 / audioProcessor.currentSampleRate;

if (waitingForRelease) {
Expand All @@ -138,14 +143,16 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
gain *= velocity;

if (numChannels >= 3) {
outputBuffer.addSample(0, sample, x * gain);
outputBuffer.addSample(1, sample, y * gain);
outputBuffer.addSample(2, sample, z * gain);
} else if (numChannels == 2) {
outputBuffer.addSample(0, sample, x * gain);
outputBuffer.addSample(1, sample, y * gain);
} else if (numChannels == 1) {
outputBuffer.addSample(0, sample, x * gain);
outputBuffer.addSample(0, sample, channels.x * gain);
outputBuffer.addSample(1, sample, channels.y * gain);
outputBuffer.addSample(2, sample, channels.z * gain);
}
else if (numChannels == 2) {
outputBuffer.addSample(0, sample, channels.x * gain);
outputBuffer.addSample(1, sample, channels.y * gain);
}
else if (numChannels == 1) {
outputBuffer.addSample(0, sample, channels.x * gain);
}

double traceMinValue = audioProcessor.traceMin->getActualValue();
Expand All @@ -162,7 +169,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

frameLength = sound.load()->updateFrame(frame);
}
frameDrawn -= drawnFrameLength;
Expand All @@ -180,10 +187,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.

}

void ShapeVoice::stopNote(float velocity, bool allowTailOff) {
currentlyPlaying = false;
waitingForRelease = false;
if (!allowTailOff) {
noteStopped();
Expand All @@ -192,6 +199,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

sound = nullptr;
}

Expand Down
11 changes: 11 additions & 0 deletions Source/img/ImageParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, ju

gd_close_gif(gif);
}
else {
width = 128;
height = 128;
visited = std::vector<bool>(width * height, false);
for (int fr = 0; fr < 14; fr++) {
frames.emplace_back(std::vector<uint8_t>(width * height));
for (int px = 0; px < width * height; px++) {
frames[fr][px] = ((px + 9 * fr) % 126) * 2;
}
}
}
} else {
juce::Image image = juce::ImageFileFormat::loadFrom(file);
image.desaturate();
Expand Down
17 changes: 8 additions & 9 deletions Source/lua/LuaParser.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
#include "LuaParser.h"
#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


std::function<void(const std::string&)> LuaParser::onPrint;
std::function<void()> LuaParser::onClear;
Expand Down Expand Up @@ -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

lua_pop(L, 1);
}

Expand Down Expand Up @@ -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


if (vars.isEffect) {
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)

setGlobalVariable(L, "z", vars.z);
}
}
Expand All @@ -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


for (int i = 1; i <= length; i++) {
lua_pushinteger(L, i);
Expand All @@ -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


setMaximumInstructions(L, 5000000);

Expand Down
7 changes: 7 additions & 0 deletions Source/lua/LuaParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#include <JuceHeader.h>
#include <regex>
#include <numbers>
#include "lua.hpp"
#include "../shape/Line.h"
#include "../shape/CircleArc.h"
#include "../shape/QuadraticBezierCurve.h"
Copy link
Contributor Author

@DJLevel3 DJLevel3 Sep 29, 2024

Choose a reason for hiding this comment

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

Moved here from the .cpp file to improve readability

#include "../shape/Shape.h"

class ErrorListener {
Expand Down Expand Up @@ -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

// x, y, z are only used for effects
bool isEffect = false;

Expand Down
Loading