From 1b4b82676364c221428e843dc9119c28fa4a9c05 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Tue, 22 Apr 2025 19:13:41 +0100 Subject: [PATCH] Add further support and add a timeline for animatable files --- Source/FrameSettingsComponent.cpp | 56 +++++--- Source/FrameSettingsComponent.h | 2 + Source/PluginProcessor.cpp | 25 ++-- Source/PluginProcessor.h | 3 +- Source/SettingsComponent.cpp | 6 +- .../components/AnimationTimelineComponent.cpp | 133 ++++++++++++++++++ .../components/AnimationTimelineComponent.h | 31 ++++ Source/gpla/LineArtParser.h | 6 +- Source/img/ImageParser.cpp | 78 ++++++---- Source/img/ImageParser.h | 4 +- Source/parser/FileParser.cpp | 32 ++++- Source/parser/FileParser.h | 8 +- osci-render.jucer | 5 + 13 files changed, 316 insertions(+), 73 deletions(-) create mode 100644 Source/components/AnimationTimelineComponent.cpp create mode 100644 Source/components/AnimationTimelineComponent.h diff --git a/Source/FrameSettingsComponent.cpp b/Source/FrameSettingsComponent.cpp index d01dc9f7..2cc00cd9 100644 --- a/Source/FrameSettingsComponent.cpp +++ b/Source/FrameSettingsComponent.cpp @@ -4,23 +4,28 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) { setText("Frame Settings"); - addAndMakeVisible(animate); - addAndMakeVisible(sync); + if (!juce::JUCEApplicationBase::isStandaloneApp()) { + addAndMakeVisible(animate); + addAndMakeVisible(sync); + addAndMakeVisible(offsetLabel); + addAndMakeVisible(offsetBox); + + offsetLabel.setText("Start Frame", juce::dontSendNotification); + offsetBox.setJustification(juce::Justification::left); + + offsetLabel.setTooltip("Offsets the animation's start point by a specified number of frames."); + } else { + audioProcessor.animationSyncBPM->setValueNotifyingHost(false); + addAndMakeVisible(timeline); + } addAndMakeVisible(rateLabel); addAndMakeVisible(rateBox); - addAndMakeVisible(offsetLabel); - addAndMakeVisible(offsetBox); addAndMakeVisible(invertImage); addAndMakeVisible(threshold); addAndMakeVisible(stride); - offsetLabel.setTooltip("Offsets the animation's start point by a specified number of frames."); - rateLabel.setText("Frames per Second", juce::dontSendNotification); rateBox.setJustification(juce::Justification::left); - - offsetLabel.setText("Start Frame", juce::dontSendNotification); - offsetBox.setJustification(juce::Justification::left); update(); @@ -39,12 +44,14 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, Osci stride.slider.onValueChange = [this]() { audioProcessor.imageStride->setValue(stride.slider.getValue()); }; - + audioProcessor.animationRate->addListener(this); audioProcessor.animationOffset->addListener(this); + audioProcessor.animationSyncBPM->addListener(this); } FrameSettingsComponent::~FrameSettingsComponent() { + audioProcessor.animationSyncBPM->removeListener(this); audioProcessor.animationOffset->removeListener(this); audioProcessor.animationRate->removeListener(this); } @@ -52,16 +59,23 @@ FrameSettingsComponent::~FrameSettingsComponent() { void FrameSettingsComponent::resized() { auto area = getLocalBounds().withTrimmedTop(20).reduced(20); double rowHeight = 20; + + auto timelineArea = juce::JUCEApplicationBase::isStandaloneApp() ? area.removeFromBottom(30) : juce::Rectangle(); - auto toggleBounds = area.removeFromTop(rowHeight); + auto toggleBounds = juce::JUCEApplicationBase::isStandaloneApp() ? juce::Rectangle() : area.removeFromTop(rowHeight); auto toggleWidth = juce::jmin(area.getWidth() / 3, 150); + + auto firstColumn = area.removeFromLeft(220); if (animated) { - animate.setBounds(toggleBounds.removeFromLeft(toggleWidth)); - sync.setBounds(toggleBounds.removeFromLeft(toggleWidth)); + if (juce::JUCEApplicationBase::isStandaloneApp()) { + timeline.setBounds(timelineArea); + } else { + animate.setBounds(toggleBounds.removeFromLeft(toggleWidth)); + sync.setBounds(toggleBounds.removeFromLeft(toggleWidth)); + } double rowSpace = 10; - auto firstColumn = area.removeFromLeft(220); firstColumn.removeFromTop(rowSpace); @@ -70,13 +84,19 @@ void FrameSettingsComponent::resized() { rateBox.setBounds(animateBounds.removeFromLeft(60)); firstColumn.removeFromTop(rowSpace); - animateBounds = firstColumn.removeFromTop(rowHeight); - offsetLabel.setBounds(animateBounds.removeFromLeft(140)); - offsetBox.setBounds(animateBounds.removeFromLeft(60)); + if (!juce::JUCEApplicationBase::isStandaloneApp()) { + animateBounds = firstColumn.removeFromTop(rowHeight); + offsetLabel.setBounds(animateBounds.removeFromLeft(140)); + offsetBox.setBounds(animateBounds.removeFromLeft(60)); + } } if (image) { - invertImage.setBounds(toggleBounds.removeFromLeft(toggleWidth)); + if (juce::JUCEApplicationBase::isStandaloneApp()) { + invertImage.setBounds(firstColumn.removeFromTop(rowHeight)); + } else { + invertImage.setBounds(toggleBounds.removeFromLeft(toggleWidth)); + } auto secondColumn = area; secondColumn.removeFromTop(5); diff --git a/Source/FrameSettingsComponent.h b/Source/FrameSettingsComponent.h index cecb7d3a..c6e6ea8b 100644 --- a/Source/FrameSettingsComponent.h +++ b/Source/FrameSettingsComponent.h @@ -5,6 +5,7 @@ #include "components/DoubleTextBox.h" #include "components/EffectComponent.h" #include "components/SwitchButton.h" +#include "components/AnimationTimelineComponent.h" class OscirenderAudioProcessorEditor; class FrameSettingsComponent : public juce::GroupComponent, public juce::AudioProcessorParameter::Listener, juce::AsyncUpdater { @@ -32,6 +33,7 @@ private: juce::Label offsetLabel{ "Offset","Offset" }; DoubleTextBox rateBox{ audioProcessor.animationRate->min, audioProcessor.animationRate->max }; DoubleTextBox offsetBox{ audioProcessor.animationOffset->min, audioProcessor.animationRate->max }; + AnimationTimelineComponent timeline{audioProcessor}; jux::SwitchButton invertImage{audioProcessor.invertImage}; EffectComponent threshold{*audioProcessor.imageThreshold}; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0185fcfe..dc3a5502 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -155,6 +155,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse booleanParameters.push_back(midiEnabled); booleanParameters.push_back(inputEnabled); booleanParameters.push_back(animateFrames); + booleanParameters.push_back(loopAnimation); booleanParameters.push_back(animationSyncBPM); booleanParameters.push_back(invertImage); @@ -537,26 +538,26 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { - // Update frame animation - if (animateFrames->getValue()) { - if (animationSyncBPM->getValue()) { - animationTime = playTimeBeats; + if (animateFrames->getBoolValue()) { + if (juce::JUCEApplicationBase::isStandaloneApp()) { + animationFrame = animationFrame + sTimeSec * animationRate->getValueUnnormalised(); + } else if (animationSyncBPM->getValue()) { + animationFrame = playTimeBeats * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised(); } else { - animationTime = playTimeSeconds; + animationFrame = playTimeSeconds * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised(); } juce::SpinLock::ScopedLockType lock1(parsersLock); juce::SpinLock::ScopedLockType lock2(effectsLock); if (currentFile >= 0 && sounds[currentFile]->parser->isAnimatable) { - int animFrame = (int)(animationTime * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised()); - auto lineArt = sounds[currentFile]->parser->getLineArt(); - auto img = sounds[currentFile]->parser->getImg(); - if (lineArt != nullptr) { - lineArt->setFrame(animFrame); - } else if (img != nullptr) { - img->setFrame(animFrame); + int totalFrames = sounds[currentFile]->parser->getNumFrames(); + if (loopAnimation->getBoolValue()) { + animationFrame = std::fmod(animationFrame, totalFrames); + } else { + animationFrame = juce::jlimit(0.0, (double) totalFrames - 1, animationFrame.load()); } + sounds[currentFile]->parser->setFrame(animationFrame); } } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 7552fa59..e32bb9d1 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -148,6 +148,7 @@ public: IntParameter* voices = new IntParameter("Voices", "voices", VERSION_HINT, 4, 1, 16); BooleanParameter* animateFrames = new BooleanParameter("Animate", "animateFrames", VERSION_HINT, true, "Enables animation for files that have multiple frames, such as GIFs or Line Art."); + BooleanParameter* loopAnimation = new BooleanParameter("Loop Animation", "loopAnimation", VERSION_HINT, true, "Loops the animation. If disabled, the animation will stop at the last frame."); BooleanParameter* animationSyncBPM = new BooleanParameter("Sync To BPM", "animationSyncBPM", VERSION_HINT, false, "Synchronises the animation's framerate with the BPM of your DAW."); FloatParameter* animationRate = new FloatParameter("Animation Rate", "animationRate", VERSION_HINT, 30, -1000, 1000); FloatParameter* animationOffset = new FloatParameter("Animation Offset", "animationOffset", VERSION_HINT, 0, -10000, 10000); @@ -174,7 +175,7 @@ public: ) ); - double animationTime = 0.f; + std::atomic animationFrame = 0.f; std::shared_ptr wobbleEffect = std::make_shared(*this); diff --git a/Source/SettingsComponent.cpp b/Source/SettingsComponent.cpp index 4b128f41..21ae396b 100644 --- a/Source/SettingsComponent.cpp +++ b/Source/SettingsComponent.cpp @@ -30,6 +30,10 @@ void SettingsComponent::resized() { area.removeFromRight(5); area.removeFromTop(5); area.removeFromBottom(5); + + if (area.getWidth() <= 0 || area.getHeight() <= 0) { + return; + } juce::Component dummy; juce::Component dummy2; @@ -57,7 +61,7 @@ void SettingsComponent::resized() { auto dummyBounds = dummy.getBounds(); if (effectSettings != nullptr) { - effectSettings->setBounds(dummyBounds.removeFromBottom(150)); + effectSettings->setBounds(dummyBounds.removeFromBottom(160)); dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE); } diff --git a/Source/components/AnimationTimelineComponent.cpp b/Source/components/AnimationTimelineComponent.cpp new file mode 100644 index 00000000..a2c441a5 --- /dev/null +++ b/Source/components/AnimationTimelineComponent.cpp @@ -0,0 +1,133 @@ +#include "AnimationTimelineComponent.h" +#include "../PluginProcessor.h" + +AnimationTimelineComponent::AnimationTimelineComponent(OscirenderAudioProcessor& processor) + : audioProcessor(processor) +{ + setOpaque(false); + + addAndMakeVisible(slider); + slider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal); + slider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0); + slider.setOpaque(false); + slider.setRange(0, 1, 0.001); + slider.setColour(juce::Slider::ColourIds::thumbColourId, juce::Colours::black); + + slider.onValueChange = [this]() { + juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock); + int currentFileIndex = audioProcessor.getCurrentFileIndex(); + if (currentFileIndex < 0) return; + auto parser = audioProcessor.parsers[currentFileIndex]; + if (parser != nullptr) { + audioProcessor.animationFrame = slider.getValue() * (parser->getNumFrames() - 1); + parser->setFrame((int) audioProcessor.animationFrame); + } + }; + + addChildComponent(playButton); + addChildComponent(pauseButton); + addAndMakeVisible(stopButton); + addAndMakeVisible(repeatButton); + + // Set up button behavior + playButton.onClick = [this]() { + audioProcessor.animateFrames->setValueNotifyingHost(true); + playButton.setVisible(false); + pauseButton.setVisible(true); + }; + + pauseButton.onClick = [this]() { + audioProcessor.animateFrames->setValueNotifyingHost(false); + playButton.setVisible(true); + pauseButton.setVisible(false); + }; + + repeatButton.onClick = [this]() { + audioProcessor.loopAnimation->setValueNotifyingHost(repeatButton.getToggleState()); + }; + + stopButton.onClick = [this]() { + audioProcessor.animateFrames->setValueNotifyingHost(false); + playButton.setVisible(true); + pauseButton.setVisible(false); + slider.setValue(0, juce::sendNotification); + }; + + setup(); + startTimer(20); +} + +AnimationTimelineComponent::~AnimationTimelineComponent() +{ + stopTimer(); +} + +void AnimationTimelineComponent::timerCallback() +{ + juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock); + int currentFileIndex = audioProcessor.getCurrentFileIndex(); + if (currentFileIndex < 0) return; + auto parser = audioProcessor.parsers[currentFileIndex]; + if (parser == nullptr) return; + int totalFrames = parser->getNumFrames(); + double frame = std::fmod(audioProcessor.animationFrame, totalFrames); + slider.setValue(frame / (totalFrames - 1), juce::dontSendNotification); +} + +void AnimationTimelineComponent::setup() +{ + // Get the current file parser + juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock); + int currentFileIndex = audioProcessor.getCurrentFileIndex(); + + bool hasAnimatableContent = false; + + if (currentFileIndex >= 0) { + auto parser = audioProcessor.parsers[currentFileIndex]; + + if (parser->isAnimatable) { + hasAnimatableContent = true; + int totalFrames = parser->getNumFrames(); + int currentFrame = parser->getCurrentFrame(); + + // Update the slider position without triggering the callback + if (totalFrames > 1) { + slider.setValue(static_cast(currentFrame) / (totalFrames - 1), juce::dontSendNotification); + } else { + slider.setValue(0, juce::dontSendNotification); + } + } + } + + // Update visibility of components + slider.setVisible(hasAnimatableContent); + repeatButton.setVisible(hasAnimatableContent); + stopButton.setVisible(hasAnimatableContent); + + if (hasAnimatableContent) { + playButton.setVisible(!audioProcessor.animateFrames->getBoolValue()); + pauseButton.setVisible(audioProcessor.animateFrames->getBoolValue()); + } else { + playButton.setVisible(false); + pauseButton.setVisible(false); + } +} + +void AnimationTimelineComponent::update() +{ + setup(); +} + +void AnimationTimelineComponent::resized() +{ + auto r = getLocalBounds(); + + auto playPauseBounds = r.removeFromLeft(25); + playButton.setBounds(playPauseBounds); + pauseButton.setBounds(playPauseBounds); + stopButton.setBounds(r.removeFromLeft(25)); + + repeatButton.setBounds(r.removeFromRight(25)); + + slider.setBounds(r); +} diff --git a/Source/components/AnimationTimelineComponent.h b/Source/components/AnimationTimelineComponent.h new file mode 100644 index 00000000..9cab1967 --- /dev/null +++ b/Source/components/AnimationTimelineComponent.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "../PluginProcessor.h" +#include "../LookAndFeel.h" +#include "../parser/FileParser.h" +#include "SvgButton.h" + +class AnimationTimelineComponent : public juce::Component, public juce::Timer +{ +public: + AnimationTimelineComponent(OscirenderAudioProcessor& processor); + ~AnimationTimelineComponent() override; + + void resized() override; + void update(); + void timerCallback() override; + +private: + OscirenderAudioProcessor& audioProcessor; + + juce::Slider slider; + SvgButton playButton{"Play", BinaryData::play_svg, juce::Colours::white, juce::Colours::white}; + SvgButton pauseButton{"Pause", BinaryData::pause_svg, juce::Colours::white, juce::Colours::white}; + SvgButton stopButton{"Stop", BinaryData::stop_svg, juce::Colours::white, juce::Colours::white}; + SvgButton repeatButton{"Repeat", BinaryData::repeat_svg, juce::Colours::white, Colours::accentColor, audioProcessor.loopAnimation}; + + void setup(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnimationTimelineComponent) +}; diff --git a/Source/gpla/LineArtParser.h b/Source/gpla/LineArtParser.h index 99da6621..c0e4991d 100644 --- a/Source/gpla/LineArtParser.h +++ b/Source/gpla/LineArtParser.h @@ -18,13 +18,15 @@ public: static std::vector> parseBinaryFrames(char* data, int dataLength); static std::vector generateFrame(juce::Array < juce::var> objects, double focalLength); + + int numFrames = 0; + int frameNumber = 0; private: static std::vector> epicFail(); static double makeDouble(int64_t data); static void makeChars(int64_t data, char* chars); static std::vector> reorderVertices(std::vector> vertices); static std::vector assembleFrame(std::vector>> allVertices, std::vector> allMatrices, double focalLength); - int frameNumber = 0; std::vector> frames; - int numFrames = 0; + }; diff --git a/Source/img/ImageParser.cpp b/Source/img/ImageParser.cpp index 9b9ef0cc..978ecb97 100644 --- a/Source/img/ImageParser.cpp +++ b/Source/img/ImageParser.cpp @@ -4,9 +4,7 @@ #include "../CommonPluginEditor.h" ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, juce::MemoryBlock image) : audioProcessor(p) { - // Set up the temporary file - temp = std::make_unique(); - juce::File file = temp->getFile(); + juce::File file = temp.getFile(); { juce::FileOutputStream output(file); @@ -142,18 +140,17 @@ void ImageParser::processVideoFile(juce::File& file) { } bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& ffmpegFile) { - juce::String altCmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() + - "\" -hide_banner 2>&1"; + juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() + "\" -hide_banner 2>&1"; - ffmpegProcess.start(altCmd); + ffmpegProcess.start(cmd); - char altBuf[2048]; - memset(altBuf, 0, sizeof(altBuf)); - size_t altSize = ffmpegProcess.read(altBuf, sizeof(altBuf) - 1); + char buf[2048]; + memset(buf, 0, sizeof(buf)); + size_t size = ffmpegProcess.read(buf, sizeof(buf) - 1); ffmpegProcess.close(); - if (altSize > 0) { - juce::String output(altBuf, altSize); + if (size > 0) { + juce::String output(buf, size); // Look for resolution in format "1920x1080" std::regex resolutionRegex(R"((\d{2,5})x(\d{2,5}))"); @@ -167,10 +164,23 @@ bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& f } } - // If still no dimensions, use defaults + // If still no dimensions or dimensions are too large, use reasonable defaults if (width <= 0 || height <= 0) { - width = 640; - height = 360; + width = 320; + height = 240; + } else { + // Downscale large videos to improve performance + const int MAX_DIMENSION = 512; + if (width > MAX_DIMENSION || height > MAX_DIMENSION) { + float aspectRatio = static_cast(width) / height; + if (width > height) { + width = MAX_DIMENSION; + height = static_cast(width / aspectRatio); + } else { + height = MAX_DIMENSION; + width = static_cast(height * aspectRatio); + } + } } // Now prepare for frame reading @@ -185,28 +195,36 @@ bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& f // Cap the number of frames to prevent excessive memory usage const int MAX_FRAMES = 10000; - // Start ffmpeg process to read frames - juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() + - "\" -f rawvideo -pix_fmt gray -v error -stats pipe:1"; + // Determine available hardware acceleration options +#if JUCE_MAC + // Try to use videotoolbox on macOS + juce::String hwAccel = " -hwaccel videotoolbox"; +#elif JUCE_WINDOWS + // Try to use DXVA2 on Windows + juce::String hwAccel = " -hwaccel dxva2"; +#else + juce::String hwAccel = ""; +#endif + + // Start ffmpeg process to read frames with optimizations: + // - Use hardware acceleration if available + // - Lower resolution with scale filter + // - Use multiple threads for faster processing + // - Use gray colorspace directly to avoid extra conversion + cmd = "\"" + ffmpegFile.getFullPathName() + "\"" + + hwAccel + + " -i \"" + file.getFullPathName() + "\"" + + " -threads 8" + // Use 8 threads for processing + " -vf \"scale=" + juce::String(width) + ":" + juce::String(height) + "\"" + // Scale to target size + " -f rawvideo -pix_fmt gray" + // Output format + " -v error" + // Only show errors + " pipe:1"; // Output to stdout ffmpegProcess.start(cmd); - if (!ffmpegProcess.isRunning()) { - return false; - } - // Read all frames into memory int framesRead = 0; - // Flag to indicate which frames to save (first, middle, last) - bool shouldSaveFrame = false; - - // Create debug directory in user documents - juce::File debugDir = juce::File::getSpecialLocation(juce::File::userDocumentsDirectory).getChildFile("osci-render-debug"); - if (!debugDir.exists()) { - debugDir.createDirectory(); - } - while (framesRead < MAX_FRAMES) { size_t bytesRead = ffmpegProcess.read(frameBuffer.data(), frameBuffer.size()); diff --git a/Source/img/ImageParser.h b/Source/img/ImageParser.h index e56b86ea..1c83ef23 100644 --- a/Source/img/ImageParser.h +++ b/Source/img/ImageParser.h @@ -16,6 +16,8 @@ public: void setFrame(int index); OsciPoint getSample(); + int getNumFrames() { return frames.size(); } + int getCurrentFrame() const { return frameIndex; } private: void findNearestNeighbour(int searchRadius, float thresholdPow, int stride, bool invert); @@ -47,7 +49,7 @@ private: // Video processing fields ReadProcess ffmpegProcess; bool isVideo = false; - std::unique_ptr temp; + juce::TemporaryFile temp; std::vector frameBuffer; int videoFrameSize = 0; diff --git a/Source/parser/FileParser.cpp b/Source/parser/FileParser.cpp index 0331a891..7acc344f 100644 --- a/Source/parser/FileParser.cpp +++ b/Source/parser/FileParser.cpp @@ -152,12 +152,6 @@ OsciPoint FileParser::nextSample(lua_State*& L, LuaVariables& vars) { return OsciPoint(); } -void FileParser::closeLua(lua_State*& L) { - if (lua != nullptr) { - lua->close(L); - } -} - bool FileParser::isSample() { return sampleSource; } @@ -201,3 +195,29 @@ std::shared_ptr FileParser::getImg() { std::shared_ptr FileParser::getWav() { return wav; } + +int FileParser::getNumFrames() { + if (gpla != nullptr) { + return gpla->numFrames; + } else if (img != nullptr) { + return img->getNumFrames(); + } + return 1; // Default to 1 frame for non-animatable content +} + +int FileParser::getCurrentFrame() { + if (gpla != nullptr) { + return gpla->frameNumber; + } else if (img != nullptr) { + return img->getCurrentFrame(); + } + return 0; // Default to frame 0 for non-animatable content +} + +void FileParser::setFrame(int frame) { + if (gpla != nullptr) { + gpla->setFrame(frame); + } else if (img != nullptr) { + img->setFrame(frame); + } +} diff --git a/Source/parser/FileParser.h b/Source/parser/FileParser.h index dcfa03ac..f2bb371c 100644 --- a/Source/parser/FileParser.h +++ b/Source/parser/FileParser.h @@ -18,12 +18,16 @@ public: void parse(juce::String fileId, juce::String fileName, juce::String extension, std::unique_ptr stream, juce::Font font); std::vector> nextFrame(); OsciPoint nextSample(lua_State*& L, LuaVariables& vars); - void closeLua(lua_State*& L); + bool isSample(); bool isActive(); void disable(); void enable(); - + + int getNumFrames(); + int getCurrentFrame(); + void setFrame(int frame); + std::shared_ptr getObject(); std::shared_ptr getSvg(); std::shared_ptr getText(); diff --git a/osci-render.jucer b/osci-render.jucer index a95b78b4..a27b80ca 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -152,6 +152,10 @@ file="Source/components/AboutComponent.cpp"/> + + +