diff --git a/Source/CommonPluginEditor.cpp b/Source/CommonPluginEditor.cpp index 2486d5a9..f7ecdf1b 100644 --- a/Source/CommonPluginEditor.cpp +++ b/Source/CommonPluginEditor.cpp @@ -112,6 +112,9 @@ CommonPluginEditor::~CommonPluginEditor() { } bool CommonPluginEditor::keyPressed(const juce::KeyPress& key) { + // If we're not accepting special keys, end early + if (!audioProcessor.getAcceptsKeys()) return false; + if (key.getModifiers().isCommandDown() && key.getModifiers().isShiftDown() && key.getKeyCode() == 'S') { saveProjectAs(); } else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'S') { diff --git a/Source/CommonPluginProcessor.cpp b/Source/CommonPluginProcessor.cpp index bd0d0d61..c0b23a08 100644 --- a/Source/CommonPluginProcessor.cpp +++ b/Source/CommonPluginProcessor.cpp @@ -16,6 +16,7 @@ CommonAudioProcessor::CommonAudioProcessor(const BusesProperties& busesPropertie : AudioProcessor(busesProperties) #endif { + if (!applicationFolder.exists()) { applicationFolder.createDirectory(); } diff --git a/Source/CommonPluginProcessor.h b/Source/CommonPluginProcessor.h index ab28ddb5..ee7c24eb 100644 --- a/Source/CommonPluginProcessor.h +++ b/Source/CommonPluginProcessor.h @@ -153,6 +153,12 @@ public: #else "ffmpeg"; #endif + void setAcceptsKeys(bool shouldAcceptKeys) { + setGlobalValue("acceptsAllKeys", shouldAcceptKeys); + } + bool getAcceptsKeys() { + return getGlobalBoolValue("acceptsAllKeys", juce::JUCEApplicationBase::isStandaloneApp()); + } protected: diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 43c1f6e8..ad26577b 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -16,6 +16,7 @@ #include "audio/MultiplexEffect.h" #include "audio/SmoothEffect.h" #include "audio/WobbleEffect.h" +#include "audio/DashedLineEffect.h" #include "audio/VectorCancellingEffect.h" #include "parser/FileParser.h" #include "parser/FrameProducer.h" @@ -144,11 +145,16 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse std::vector{ new osci::EffectParameter("Delay Decay", "Adds repetitions, delays, or echos to the audio. This slider controls the volume of the echo.", "delayDecay", VERSION_HINT, 0.4, 0.0, 1.0), new osci::EffectParameter("Delay Length", "Controls the time in seconds between echos.", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)})); - toggleableEffects.push_back(std::make_shared( - dashedLineEffect, + auto dashedLineEffect = std::make_shared( + std::make_shared(*this), std::vector{ - new osci::EffectParameter("Dash Length", "Controls the length of the dashed line.", "dashLength", VERSION_HINT, 0.2, 0.0, 1.0), - })); + new osci::EffectParameter("Dash Count", "Controls the number of dashed lines in the drawing.", "dashCount", VERSION_HINT, 16.0, 1.0, 32.0), + new osci::EffectParameter("Dash Coverage", "Controls the fraction of each dash unit that is drawn.", "dashCoverage", VERSION_HINT, 0.5, 0.0, 1.0), + new osci::EffectParameter("Dash Offset", "Offsets the location of the dashed lines.", "dashOffset", VERSION_HINT, 0.0, 0.0, 1.0), + }); + dashedLineEffect->getParameter("dashOffset")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); + dashedLineEffect->getParameter("dashOffset")->lfoRate->setUnnormalisedValueNotifyingHost(1.0); + toggleableEffects.push_back(dashedLineEffect); toggleableEffects.push_back(custom); toggleableEffects.push_back(trace); trace->getParameter("traceLength")->lfo->setUnnormalisedValueNotifyingHost((int)osci::LfoType::Sawtooth); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 18730f75..eb65ab5b 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -18,7 +18,6 @@ #include "UGen/Env.h" #include "UGen/ugen_JuceEnvelopeComponent.h" #include "audio/CustomEffect.h" -#include "audio/DashedLineEffect.h" #include "audio/DelayEffect.h" #include "audio/PerspectiveEffect.h" #include "audio/PublicSynthesiser.h" @@ -89,8 +88,6 @@ public: std::shared_ptr delayEffect = std::make_shared(); - std::shared_ptr dashedLineEffect = std::make_shared(); - std::function errorCallback = [this](int lineNum, juce::String fileName, juce::String error) { notifyErrorListeners(lineNum, fileName, error); }; std::shared_ptr customEffect = std::make_shared(errorCallback, luaValues); std::shared_ptr custom = std::make_shared( @@ -102,7 +99,7 @@ public: perspectiveEffect, std::vector{ new osci::EffectParameter("Perspective", "Controls the strength of the 3D perspective projection.", "perspectiveStrength", VERSION_HINT, 1.0, 0.0, 1.0), - new osci::EffectParameter("Focal Length", "Controls the focal length of the 3D perspective effect. A higher focal length makes the image look more flat, and a lower focal length makes the image look more 3D.", "perspectiveFocalLength", VERSION_HINT, 2.0, 0.0, 10.0), + new osci::EffectParameter("FOV", "Controls the camera's field of view in degrees. A lower field of view makes the image look more flat, and a higher field of view makes the image look more 3D.", "perspectiveFov", VERSION_HINT, 50.0, 5.0, 130.0), }); osci::BooleanParameter* midiEnabled = new osci::BooleanParameter("MIDI Enabled", "midiEnabled", VERSION_HINT, false, "Enable MIDI input for the synth. If disabled, the synth will play a constant tone, as controlled by the frequency slider."); diff --git a/Source/audio/DashedLineEffect.h b/Source/audio/DashedLineEffect.h index 99b0851c..e48c3f3c 100644 --- a/Source/audio/DashedLineEffect.h +++ b/Source/audio/DashedLineEffect.h @@ -1,34 +1,42 @@ #pragma once #include +#include "../PluginProcessor.h" class DashedLineEffect : public osci::EffectApplication { public: - osci::Point apply(int index, osci::Point vector, const std::vector>& values, double sampleRate) override { - // dash length in seconds - double dashLength = values[0] / 400; - int dashLengthSamples = (int)(dashLength * sampleRate); - dashLengthSamples = juce::jmin(dashLengthSamples, MAX_BUFFER); - - if (dashIndex >= dashLengthSamples) { - dashIndex = 0; + DashedLineEffect(OscirenderAudioProcessor& p) : audioProcessor(p) {} + + osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { + double dashCount = juce::jmax(1.0, values[0].load()); // Dashes per cycle + double dashCoverage = juce::jlimit(0.0, 1.0, values[1].load()); + double dashOffset = values[2]; + double dashLengthSamples = (sampleRate / audioProcessor.frequency) / dashCount; + double dashPhase = framePhase * dashCount - dashOffset; + dashPhase = dashPhase - std::floor(dashPhase); // Wrap + buffer[bufferIndex] = input; + + // Linear interpolation works much better than nearest for this + double samplePos = bufferIndex - dashLengthSamples * dashPhase * (1 - dashCoverage); + samplePos = samplePos - buffer.size() * std::floor(samplePos / buffer.size()); // Wrap to [0, size] + int lowIndex = (int)std::floor(samplePos) % buffer.size(); + int highIndex = (lowIndex + 1) % buffer.size(); + double mixFactor = samplePos - std::floor(samplePos); // Fractional part + osci::Point output = (1 - mixFactor) * buffer[lowIndex] + mixFactor * buffer[highIndex]; + + bufferIndex++; + if (bufferIndex >= buffer.size()) { bufferIndex = 0; } + framePhase += audioProcessor.frequency / sampleRate; + framePhase = framePhase - std::floor(framePhase); - buffer[bufferIndex] = vector; - bufferIndex++; - - vector = buffer[dashIndex]; - - if (index % 2 == 0) { - dashIndex++; - } - - return vector; + return output; } private: + OscirenderAudioProcessor &audioProcessor; const static int MAX_BUFFER = 192000; std::vector buffer = std::vector(MAX_BUFFER); - int dashIndex = 0; int bufferIndex = 0; -}; + double framePhase = 0.0; // [0, 1] +}; \ No newline at end of file diff --git a/Source/audio/PerspectiveEffect.h b/Source/audio/PerspectiveEffect.h index fcf3dbc1..d237c202 100644 --- a/Source/audio/PerspectiveEffect.h +++ b/Source/audio/PerspectiveEffect.h @@ -6,11 +6,14 @@ class PerspectiveEffect : public osci::EffectApplication { public: osci::Point apply(int index, osci::Point input, const std::vector>& values, double sampleRate) override { auto effectScale = values[0].load(); - auto focalLength = juce::jmax(values[1].load(), 0.001); + // Far plane clipping happens at about 1.2 deg for 100 far plane dist + double fovDegrees = juce::jlimit(1.5, 179.0, values[1].load()); + double fov = juce::degreesToRadians(fovDegrees); - Vec3 origin = Vec3(0, 0, -focalLength); + // Place camera such that field of view is tangent to unit sphere + Vec3 origin = Vec3(0, 0, -1.0f / std::sin(0.5f * (float)fov)); camera.setPosition(origin); - camera.setFocalLength(focalLength); + camera.setFov(fov); Vec3 vec = Vec3(input.x, input.y, input.z); Vec3 projected = camera.project(vec); @@ -23,6 +26,6 @@ public: } private: - + Camera camera; }; diff --git a/Source/components/OsciMainMenuBarModel.cpp b/Source/components/OsciMainMenuBarModel.cpp index 74e9f5a8..c81bf7fe 100644 --- a/Source/components/OsciMainMenuBarModel.cpp +++ b/Source/components/OsciMainMenuBarModel.cpp @@ -60,7 +60,11 @@ void OsciMainMenuBarModel::resetMenuItems() { }); addMenuItem(1, "Randomize Blender Port", [this] { audioProcessor.setObjectServerPort(juce::Random::getSystemRandom().nextInt(juce::Range(51600, 51700))); - }); + }); + addMenuItem(1, audioProcessor.getAcceptsKeys() ? "Disable Special Keys" : "Enable Special Keys", [this] { + audioProcessor.setAcceptsKeys(!audioProcessor.getAcceptsKeys()); + resetMenuItems(); + }); #if !OSCI_PREMIUM addMenuItem(1, "Purchase osci-render premium!", [this] { diff --git a/Source/components/SosciMainMenuBarModel.cpp b/Source/components/SosciMainMenuBarModel.cpp index 84c2299d..7e7d81fd 100644 --- a/Source/components/SosciMainMenuBarModel.cpp +++ b/Source/components/SosciMainMenuBarModel.cpp @@ -4,6 +4,10 @@ #include "../SosciPluginProcessor.h" SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioProcessor& p) : editor(e), processor(p) { + resetMenuItems(); +} + +void SosciMainMenuBarModel::resetMenuItems() { addTopLevelMenu("File"); addTopLevelMenu("About"); addTopLevelMenu("Video"); @@ -92,6 +96,10 @@ SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioPro juce::DialogWindow* dw = options.launchAsync(); }); + addMenuItem(1, processor.getAcceptsKeys() ? "Disable Special Keys" : "Enable Special Keys", [this] { + processor.setAcceptsKeys(!processor.getAcceptsKeys()); + resetMenuItems(); + }); addMenuItem(2, "Settings...", [this] { editor.openRecordingSettings(); diff --git a/Source/components/SosciMainMenuBarModel.h b/Source/components/SosciMainMenuBarModel.h index d5639cd8..4c9c90b3 100644 --- a/Source/components/SosciMainMenuBarModel.h +++ b/Source/components/SosciMainMenuBarModel.h @@ -9,6 +9,7 @@ class SosciAudioProcessor; class SosciMainMenuBarModel : public MainMenuBarModel { public: SosciMainMenuBarModel(SosciPluginEditor& editor, SosciAudioProcessor& processor); + void resetMenuItems(); SosciPluginEditor& editor; SosciAudioProcessor& processor; diff --git a/Source/obj/Camera.cpp b/Source/obj/Camera.cpp index d19fb743..f6c5d4f0 100644 --- a/Source/obj/Camera.cpp +++ b/Source/obj/Camera.cpp @@ -1,6 +1,6 @@ #include "Camera.h" -Camera::Camera() : frustum(1, 1, 0.1, 100) { +Camera::Camera() : frustum(1, 1, 0.001, 100) { viewMatrix = mathter::Identity(); } @@ -18,8 +18,8 @@ Vec3 Camera::toWorldSpace(Vec3& point) { return mathter::Inverse(viewMatrix) * point; } -void Camera::setFocalLength(double focalLength) { - frustum.setCameraInternals(focalLength, frustum.ratio, frustum.nearDistance, frustum.farDistance); +void Camera::setFov(double fov) { + frustum.setCameraInternals(fov, frustum.ratio, frustum.nearDistance, frustum.farDistance); } Vec3 Camera::project(Vec3& pWorld) { @@ -27,10 +27,10 @@ Vec3 Camera::project(Vec3& pWorld) { frustum.clipToFrustum(p); - double start = p.x * frustum.focalLength / p.z; - double end = p.y * frustum.focalLength / p.z; + float x = p.x * frustum.focalLength / p.z; + float y = p.y * frustum.focalLength / p.z; - return Vec3(start, end, 0); + return Vec3(x, y, 0); } Frustum Camera::getFrustum() { diff --git a/Source/obj/Camera.h b/Source/obj/Camera.h index 781458dd..1960aeaa 100644 --- a/Source/obj/Camera.h +++ b/Source/obj/Camera.h @@ -12,7 +12,7 @@ public: void setPosition(Vec3& position); Vec3 toCameraSpace(Vec3& point); Vec3 toWorldSpace(Vec3& point); - void setFocalLength(double focalLength); + void setFov(double fov); Vec3 project(Vec3& p); Frustum getFrustum(); private: diff --git a/Source/obj/Frustum.cpp b/Source/obj/Frustum.cpp index 926a17ae..04b20b77 100644 --- a/Source/obj/Frustum.cpp +++ b/Source/obj/Frustum.cpp @@ -2,16 +2,16 @@ #include "Frustum.h" -void Frustum::setCameraInternals(float focalLength, float ratio, float nearDistance, float farDistance) { +void Frustum::setCameraInternals(float fov, float ratio, float nearDistance, float farDistance) { // store the information - this->focalLength = focalLength; + this->fov = fov; this->ratio = ratio; this->nearDistance = nearDistance; this->farDistance = farDistance; // compute width and height of the near section - float fov = 2 * std::atan(1 / focalLength); - tang = (float) std::tan(fov * 0.5); + tang = std::tan(fov * 0.5f); + focalLength = 1.0f / tang; height = nearDistance * tang; width = height * ratio; } diff --git a/Source/obj/Frustum.h b/Source/obj/Frustum.h index 2929a33f..c942c621 100644 --- a/Source/obj/Frustum.h +++ b/Source/obj/Frustum.h @@ -9,16 +9,13 @@ using Vec3 = mathter::Vector; class Frustum { public: - float ratio, nearDistance, farDistance, width, height, tang, focalLength; + float ratio, nearDistance, farDistance, width, height, tang, fov, focalLength; - Frustum(float focalLength, float ratio, float nearDistance, float farDistance) { - setCameraInternals(focalLength, ratio, nearDistance, farDistance); + Frustum(float fov, float ratio, float nearDistance, float farDistance) { + setCameraInternals(fov, ratio, nearDistance, farDistance); } ~Frustum() {}; - void setCameraInternals(float focalLength, float ratio, float nearD, float farD); + void setCameraInternals(float fov, float ratio, float nearD, float farD); void clipToFrustum(Vec3 &p); - float getFocalLength() { - return focalLength; - } }; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 91c98757..f84ceee9 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -254,6 +254,9 @@ void VisualiserComponent::mouseDown(const juce::MouseEvent &event) { } bool VisualiserComponent::keyPressed(const juce::KeyPress &key) { + // If we're not accepting special keys, end early + if (!audioProcessor.getAcceptsKeys()) return false; + if (key.isKeyCode(juce::KeyPress::escapeKey)) { if (fullScreenCallback) { fullScreenCallback(FullScreenMode::MAIN_COMPONENT);