-
-
Notifications
You must be signed in to change notification settings - Fork 23
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
base: main
Are you sure you want to change the base?
Changes from all commits
de6adce
28081cd
772b968
15131d1
12efc0c
88f5404
9c73a04
09044bf
762b183
fedecab
30ccb1d
a6f5108
9d9d3f8
ac72bbf
899f27d
1f948ac
f60cc0e
f300215
aa2d092
cc7118a
7407a28
f894068
6d170ff
79dbe86
6f4a6ab
e21382f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
// handle all midi messages | ||
auto midiIterator = midiMessages.cbegin(); | ||
std::for_each(midiIterator, | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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); | ||
} | ||
} | ||
|
@@ -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); | ||
} | ||
|
@@ -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 }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplified control flow |
||
|
||
} else { | ||
parser->resetErrors(); | ||
} | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
@@ -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; | ||
|
||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
@@ -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(); | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -180,10 +187,10 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star | |
} | ||
} | ||
} | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
@@ -192,6 +199,7 @@ void ShapeVoice::stopNote(float velocity, bool allowTailOff) { | |
|
||
void ShapeVoice::noteStopped() { | ||
clearCurrentNote(); | ||
currentlyPlaying = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved here to fix animation during MIDI note release |
||
sound = nullptr; | ||
} | ||
|
||
|
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
|
||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Backing up to Lua 5.1 to use LuaJIT |
||
|
||
setMaximumInstructions(L, 5000000); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -50,6 +54,9 @@ struct LuaVariables { | |
double sampleRate = 0; | ||
double frequency = 0; | ||
|
||
double ext_x = 0; | ||
double ext_y = 0; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
||
|
There was a problem hiding this comment.
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.