From 13a6a6e2e3bc8acdc599578b9c623270bae08e14 Mon Sep 17 00:00:00 2001 From: James Ball Date: Mon, 28 Aug 2023 22:06:21 +0100 Subject: [PATCH] Get basic MIDI input working --- Source/PluginProcessor.cpp | 182 +++++--------------------------- Source/PluginProcessor.h | 59 +++-------- Source/audio/Effect.cpp | 10 ++ Source/audio/Effect.h | 2 + Source/audio/ShapeSound.cpp | 54 ++++++++++ Source/audio/ShapeSound.h | 26 +++++ Source/audio/ShapeVoice.cpp | 150 ++++++++++++++++++++++++++ Source/audio/ShapeVoice.h | 37 +++++++ Source/parser/FrameConsumer.h | 2 +- Source/parser/FrameProducer.cpp | 12 +-- Source/parser/FrameProducer.h | 2 - osci-render.jucer | 4 + 12 files changed, 325 insertions(+), 215 deletions(-) create mode 100644 Source/audio/ShapeSound.cpp create mode 100644 Source/audio/ShapeSound.h create mode 100644 Source/audio/ShapeVoice.cpp create mode 100644 Source/audio/ShapeVoice.h diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 8861b8f..b072e61 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -32,8 +32,6 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() ) #endif { - producer.startThread(); - // locking isn't necessary here because we are in the constructor toggleableEffects.push_back(std::make_shared( @@ -137,6 +135,10 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() for (auto parameter : booleanParameters) { addParameter(parameter); } + + for (int i = 0; i < 4; i++) { + synth.addVoice(new ShapeVoice(*this)); + } } OscirenderAudioProcessor::~OscirenderAudioProcessor() {} @@ -194,6 +196,7 @@ void OscirenderAudioProcessor::changeProgramName(int index, const juce::String& void OscirenderAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { currentSampleRate = sampleRate; pitchDetector.setSampleRate(sampleRate); + synth.setCurrentPlaybackSampleRate(sampleRate); } void OscirenderAudioProcessor::releaseResources() { @@ -304,7 +307,8 @@ void OscirenderAudioProcessor::updateFileBlock(int index, std::shared_ptr()); fileNames.push_back(file.getFileName()); - parsers.push_back(std::make_unique()); + parsers.push_back(std::make_shared()); + sounds.push_back(new ShapeSound(parsers.back())); file.createInputStream()->readIntoMemoryBlock(*fileBlocks.back()); openFile(fileBlocks.size() - 1); @@ -314,7 +318,8 @@ void OscirenderAudioProcessor::addFile(juce::File file) { void OscirenderAudioProcessor::addFile(juce::String fileName, const char* data, const int size) { fileBlocks.push_back(std::make_shared()); fileNames.push_back(fileName); - parsers.push_back(std::make_unique()); + parsers.push_back(std::make_shared()); + sounds.push_back(new ShapeSound(parsers.back())); fileBlocks.back()->append(data, size); openFile(fileBlocks.size() - 1); @@ -324,7 +329,8 @@ void OscirenderAudioProcessor::addFile(juce::String fileName, const char* data, void OscirenderAudioProcessor::addFile(juce::String fileName, std::shared_ptr data) { fileBlocks.push_back(data); fileNames.push_back(fileName); - parsers.push_back(std::make_unique()); + parsers.push_back(std::make_shared()); + sounds.push_back(new ShapeSound(parsers.back())); openFile(fileBlocks.size() - 1); } @@ -337,6 +343,7 @@ void OscirenderAudioProcessor::removeFile(int index) { fileBlocks.erase(fileBlocks.begin() + index); fileNames.erase(fileNames.begin() + index); parsers.erase(parsers.begin() + index); + sounds.erase(sounds.begin() + index); auto newFileIndex = index; if (newFileIndex >= fileBlocks.size()) { newFileIndex = fileBlocks.size() - 1; @@ -363,17 +370,18 @@ void OscirenderAudioProcessor::openFile(int index) { // used ONLY for changing the current file to an EXISTING file. // much faster than openFile(int index) because it doesn't reparse any files. // parsersLock AND effectsLock must be locked before calling this function + +// TODO: This should change whatever the ShapeSound is to the new index void OscirenderAudioProcessor::changeCurrentFile(int index) { + synth.clearSounds(); if (index == -1) { currentFile = -1; - producer.setSource(std::make_shared(), -1); } if (index < 0 || index >= fileBlocks.size()) { return; } - producer.setSource(parsers[index], index); + synth.addSound(sounds[index]); currentFile = index; - invalidateFrameBuffer = true; updateLuaValues(); updateObjValues(); } @@ -402,113 +410,19 @@ std::shared_ptr OscirenderAudioProcessor::getFileBlock(int in return fileBlocks[index]; } -void OscirenderAudioProcessor::addFrame(std::vector> frame, int fileIndex) { - const auto scope = frameFifo.write(1); - - if (scope.blockSize1 > 0) { - frameBuffer[scope.startIndex1].clear(); - for (auto& shape : frame) { - frameBuffer[scope.startIndex1].push_back(std::move(shape)); - } - frameBufferIndices[scope.startIndex1] = fileIndex; - } - - if (scope.blockSize2 > 0) { - frameBuffer[scope.startIndex2].clear(); - for (auto& shape : frame) { - frameBuffer[scope.startIndex2].push_back(std::move(shape)); - } - frameBufferIndices[scope.startIndex2] = fileIndex; - } -} - -void OscirenderAudioProcessor::updateFrame() { - currentShape = 0; - shapeDrawn = 0.0; - frameDrawn = 0.0; - - if (frameFifo.getNumReady() > 0) { - { - const auto scope = frameFifo.read(1); - - if (scope.blockSize1 > 0) { - frame.swap(frameBuffer[scope.startIndex1]); - currentBufferIndex = frameBufferIndices[scope.startIndex1]; - } else if (scope.blockSize2 > 0) { - frame.swap(frameBuffer[scope.startIndex2]); - currentBufferIndex = frameBufferIndices[scope.startIndex2]; - } - - frameLength = Shape::totalLength(frame); - } - } -} - -void OscirenderAudioProcessor::updateLengthIncrement() { - double traceMaxValue = traceMaxEnabled ? actualTraceMax : 1.0; - double traceMinValue = traceMinEnabled ? actualTraceMin : 0.0; - double proportionalLength = (traceMaxValue - traceMinValue) * frameLength; - lengthIncrement = juce::jmax(proportionalLength / (currentSampleRate / frequency), MIN_LENGTH_INCREMENT); -} - -void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) -{ +void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); - // In case we have more outputs than inputs, this code clears any output - // channels that didn't contain input data, (because these aren't - // guaranteed to be empty - they may contain garbage). - // This is here to avoid people getting screaming feedback - // when they first compile a plugin, but obviously you don't need to keep - // this code if your algorithm always overwrites all the output channels. - for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) - buffer.clear (i, 0, buffer.getNumSamples()); - + buffer.clear(); + synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); + midiMessages.clear(); auto* channelData = buffer.getArrayOfWritePointers(); - auto numSamples = buffer.getNumSamples(); - - if (invalidateFrameBuffer) { - frameFifo.reset(); - // keeps getting the next frame until the frame comes from the file that we want to render. - // this MIGHT be hacky and cause issues later down the line, but for now it works as a - // solution to get instant changing of current file when pressing j and k. - while (currentBufferIndex != currentFile) { - updateFrame(); - } - invalidateFrameBuffer = false; - } - for (auto sample = 0; sample < numSamples; ++sample) { - updateLengthIncrement(); - - traceMaxEnabled = false; - traceMinEnabled = false; - - Vector2 channels; - double x = 0.0; - double y = 0.0; - - - std::shared_ptr sampleParser; - { - juce::SpinLock::ScopedLockType lock(parsersLock); - if (currentFile >= 0 && parsers[currentFile]->isSample()) { - sampleParser = parsers[currentFile]; - } - } - bool renderingSample = sampleParser != nullptr; - - if (renderingSample) { - channels = sampleParser->nextSample(); - } 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); - } + for (auto sample = 0; sample < buffer.getNumSamples(); ++sample) { + Vector2 channels = {buffer.getSample(0, sample), buffer.getSample(1, sample)}; { juce::SpinLock::ScopedLockType lock1(parsersLock); @@ -523,8 +437,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju } } - x = channels.x; - y = channels.y; + double x = channels.x; + double y = channels.y; x *= volume; y *= volume; @@ -541,57 +455,9 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju } audioProducer.write(x, y); - - actualTraceMax = juce::jmax(actualTraceMin + MIN_TRACE, juce::jmin(traceMaxValue, 1.0)); - actualTraceMin = juce::jmax(MIN_TRACE, juce::jmin(traceMinValue, actualTraceMax - MIN_TRACE)); - - if (!renderingSample) { - incrementShapeDrawing(); - } - - double drawnFrameLength = traceMaxEnabled ? actualTraceMax * frameLength : frameLength; - - if (!renderingSample && frameDrawn >= drawnFrameLength) { - updateFrame(); - // TODO: updateFrame already iterates over all the shapes, - // so we can improve performance by calculating frameDrawn - // and shapeDrawn directly. frameDrawn is simply actualTraceMin * frameLength - // but shapeDrawn is the amount of the current shape that has been drawn so - // we need to iterate over all the shapes to calculate it. - if (traceMinEnabled) { - while (frameDrawn < actualTraceMin * frameLength) { - incrementShapeDrawing(); - } - } - } } } -// TODO this is the slowest part of the program - any way to improve this would help! -void OscirenderAudioProcessor::incrementShapeDrawing() { - double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0; - // hard cap on how many times it can be over the length to - // prevent audio stuttering - auto increment = juce::jmin(lengthIncrement, 20 * length); - frameDrawn += increment; - shapeDrawn += increment; - - // Need to skip all shapes that the lengthIncrement draws over. - // This is especially an issue when there are lots of small lines being - // drawn. - while (shapeDrawn > length) { - shapeDrawn -= length; - currentShape++; - if (currentShape >= frame.size()) { - currentShape = 0; - break; - } - // POTENTIAL TODO: Think of a way to make this more efficient when iterating - // this loop many times - length = frame[currentShape]->len; - } -} - //============================================================================== bool OscirenderAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index c511b99..be7999f 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -10,10 +10,9 @@ #include #include "shape/Shape.h" -#include "parser/FileParser.h" -#include "parser/FrameProducer.h" -#include "parser/FrameConsumer.h" #include "audio/Effect.h" +#include "audio/ShapeSound.h" +#include "audio/ShapeVoice.h" #include #include "concurrency/BufferProducer.h" #include "audio/AudioWebSocketServer.h" @@ -29,7 +28,6 @@ class OscirenderAudioProcessor : public juce::AudioProcessor #if JucePlugin_Enable_ARA , public juce::AudioProcessorARAExtension #endif - , public FrameConsumer { public: OscirenderAudioProcessor(); @@ -158,18 +156,28 @@ public: }, new EffectParameter("Rotate Speed", "objRotateSpeed", 0.0, -1.0, 1.0) ); + std::shared_ptr traceMax = std::make_shared( + [this](int index, Vector2 input, const std::vector& values, double sampleRate) { + return input; + }, new EffectParameter("Trace max", "traceMax", 1.0, 0.0, 1.0) + ); + std::shared_ptr traceMin = std::make_shared( + [this](int index, Vector2 input, const std::vector& values, double sampleRate) { + return input; + }, new EffectParameter("Trace min", "traceMin", 0.0, 0.0, 1.0) + ); + std::shared_ptr delayEffect = std::make_shared(); std::shared_ptr perspectiveEffect = std::make_shared(); juce::SpinLock parsersLock; std::vector> parsers; + std::vector sounds; std::vector> fileBlocks; std::vector fileNames; std::atomic currentFile = -1; juce::ChangeBroadcaster broadcaster; - - FrameProducer producer = FrameProducer(*this, std::make_shared()); BufferProducer audioProducer; @@ -184,7 +192,6 @@ public: juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain); void addLuaSlider(); - void addFrame(std::vector> frame, int fileIndex) override; void updateEffectPrecedence(); void updateFileBlock(int index, std::shared_ptr block); void addFile(juce::File file); @@ -204,50 +211,14 @@ private: std::atomic volume = 1.0; std::atomic threshold = 1.0; - juce::AbstractFifo frameFifo{ 10 }; - std::vector> frameBuffer[10]; - int frameBufferIndices[10]; - - int currentShape = 0; - std::vector> frame; - int currentBufferIndex = -1; - double frameLength; - double shapeDrawn = 0.0; - double frameDrawn = 0.0; - double lengthIncrement = 0.0; - bool invalidateFrameBuffer = false; - std::vector booleanParameters; std::vector> allEffects; std::vector> permanentEffects; - std::shared_ptr traceMax = std::make_shared( - [this](int index, Vector2 input, const std::vector& values, double sampleRate) { - traceMaxValue = values[0]; - traceMaxEnabled = true; - return input; - }, new EffectParameter("Trace max", "traceMax", 1.0, 0.0, 1.0) - ); - std::shared_ptr traceMin = std::make_shared( - [this](int index, Vector2 input, const std::vector& values, double sampleRate) { - traceMinValue = values[0]; - traceMinEnabled = true; - return input; - }, new EffectParameter("Trace min", "traceMin", 0.0, 0.0, 1.0) - ); - const double MIN_TRACE = 0.005; - double traceMaxValue = traceMax->getValue(); - double traceMinValue = traceMin->getValue(); - double actualTraceMax = traceMaxValue; - double actualTraceMin = traceMinValue; - bool traceMaxEnabled = false; - bool traceMinEnabled = false; + juce::Synthesiser synth; AudioWebSocketServer softwareOscilloscopeServer{audioProducer}; - void updateFrame(); - void updateLengthIncrement(); - void incrementShapeDrawing(); void updateLuaValues(); void updateObjValues(); std::shared_ptr getEffect(juce::String id); diff --git a/Source/audio/Effect.cpp b/Source/audio/Effect.cpp index a87ee7a..dafb6e9 100644 --- a/Source/audio/Effect.cpp +++ b/Source/audio/Effect.cpp @@ -91,6 +91,16 @@ double Effect::getValue() { return getValue(0); } +// Not thread safe! Should only be called from the audio thread +double Effect::getActualValue(int index) { + return actualValues[index]; +} + +// Not thread safe! Should only be called from the audio thread +double Effect::getActualValue() { + return actualValues[0]; +} + void Effect::setValue(int index, double value) { parameters[index]->setUnnormalisedValueNotifyingHost(value); } diff --git a/Source/audio/Effect.h b/Source/audio/Effect.h index 1658484..6bfbca6 100644 --- a/Source/audio/Effect.h +++ b/Source/audio/Effect.h @@ -18,6 +18,8 @@ public: void apply(); double getValue(int index); double getValue(); + double getActualValue(int index); + double getActualValue(); void setValue(int index, double value); void setValue(double value); int getPrecedence(); diff --git a/Source/audio/ShapeSound.cpp b/Source/audio/ShapeSound.cpp new file mode 100644 index 0000000..5ad6db9 --- /dev/null +++ b/Source/audio/ShapeSound.cpp @@ -0,0 +1,54 @@ +#include "ShapeSound.h" + +ShapeSound::ShapeSound(std::shared_ptr parser) : parser(parser) { + if (parser->isSample()) { + producer = std::make_unique(*this, std::make_shared()); + } else { + producer = std::make_unique(*this, parser); + } + producer->startThread(); +} + +bool ShapeSound::appliesToNote(int note) { + return true; +} + +bool ShapeSound::appliesToChannel(int channel) { + return true; +} + +void ShapeSound::addFrame(std::vector>& frame) { + const auto scope = frameFifo.write(1); + + if (scope.blockSize1 > 0) { + frameBuffer[scope.startIndex1].clear(); + for (auto& shape : frame) { + frameBuffer[scope.startIndex1].push_back(std::move(shape)); + } + } + + if (scope.blockSize2 > 0) { + frameBuffer[scope.startIndex2].clear(); + for (auto& shape : frame) { + frameBuffer[scope.startIndex2].push_back(std::move(shape)); + } + } +} + +double ShapeSound::updateFrame(std::vector>& frame) { + if (frameFifo.getNumReady() > 0) { + { + const auto scope = frameFifo.read(1); + + if (scope.blockSize1 > 0) { + frame.swap(frameBuffer[scope.startIndex1]); + } else if (scope.blockSize2 > 0) { + frame.swap(frameBuffer[scope.startIndex2]); + } + + frameLength = Shape::totalLength(frame); + } + } + + return frameLength; +} diff --git a/Source/audio/ShapeSound.h b/Source/audio/ShapeSound.h new file mode 100644 index 0000000..bdafebc --- /dev/null +++ b/Source/audio/ShapeSound.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include "../parser/FileParser.h" +#include "../parser/FrameProducer.h" +#include "../parser/FrameConsumer.h" + +class ShapeSound : public juce::SynthesiserSound, public FrameConsumer { +public: + ShapeSound(std::shared_ptr parser); + + bool appliesToNote(int note) override; + bool appliesToChannel(int channel) override; + void addFrame(std::vector>& frame) override; + double updateFrame(std::vector>& frame); + + std::shared_ptr parser; + + using Ptr = juce::ReferenceCountedObjectPtr; + +private: + + juce::AbstractFifo frameFifo{ 10 }; + std::vector> frameBuffer[10]; + std::unique_ptr producer; + double frameLength = 0.0; +}; \ No newline at end of file diff --git a/Source/audio/ShapeVoice.cpp b/Source/audio/ShapeVoice.cpp new file mode 100644 index 0000000..ac5c4bf --- /dev/null +++ b/Source/audio/ShapeVoice.cpp @@ -0,0 +1,150 @@ +#include "ShapeVoice.h" +#include "../PluginProcessor.h" + +ShapeVoice::ShapeVoice(OscirenderAudioProcessor& p) : audioProcessor(p) { + actualTraceMin = audioProcessor.traceMin->getValue(); + actualTraceMax = audioProcessor.traceMax->getValue(); +} + +bool ShapeVoice::canPlaySound(juce::SynthesiserSound* sound) { + return dynamic_cast (sound) != nullptr; +} + +void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) { + auto* shapeSound = dynamic_cast(sound); + + if (shapeSound != nullptr) { + this->sound = shapeSound; + while (frame.empty()) { + frameLength = shapeSound->updateFrame(frame); + } + tailOff = 0.0; + frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber); + } +} + +// TODO this is the slowest part of the program - any way to improve this would help! +void ShapeVoice::incrementShapeDrawing() { + double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0; + // hard cap on how many times it can be over the length to + // prevent audio stuttering + auto increment = juce::jmin(lengthIncrement, 20 * length); + frameDrawn += increment; + shapeDrawn += increment; + + // Need to skip all shapes that the lengthIncrement draws over. + // This is especially an issue when there are lots of small lines being + // drawn. + while (shapeDrawn > length) { + shapeDrawn -= length; + currentShape++; + if (currentShape >= frame.size()) { + currentShape = 0; + break; + } + // POTENTIAL TODO: Think of a way to make this more efficient when iterating + // this loop many times + length = frame[currentShape]->len; + } +} + +void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) { + juce::ScopedNoDenormals noDenormals; + + int numChannels = outputBuffer.getNumChannels(); + + for (auto sample = startSample; sample < startSample + numSamples; ++sample) { + bool traceMinEnabled = audioProcessor.traceMin->enabled->getBoolValue(); + bool traceMaxEnabled = audioProcessor.traceMax->enabled->getBoolValue(); + + // update length increment + double traceMax = traceMaxEnabled ? actualTraceMax : 1.0; + double traceMin = traceMinEnabled ? actualTraceMin : 0.0; + double proportionalLength = (traceMax - traceMin) * frameLength; + // double frequency = audioProcessor.frequencyEffect->getActualValue(); + lengthIncrement = juce::jmax(proportionalLength / (audioProcessor.currentSampleRate / frequency), MIN_LENGTH_INCREMENT); + + Vector2 channels; + double x = 0.0; + double y = 0.0; + + bool renderingSample = true; + + if (sound != nullptr) { + renderingSample = sound->parser->isSample(); + + if (renderingSample) { + channels = sound->parser->nextSample(); + } 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); + } + } + + x = channels.x; + y = channels.y; + + if (tailOff > 0.0) { + tailOff *= 0.99999; + + if (tailOff < 0.005) { + clearCurrentNote(); + sound = nullptr; + break; + } + } + + double gain = tailOff == 0.0 ? 1.0 : tailOff; + + 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); + } + + double traceMinValue = audioProcessor.traceMin->getActualValue(); + double traceMaxValue = audioProcessor.traceMax->getActualValue(); + actualTraceMax = juce::jmax(actualTraceMin + MIN_TRACE, juce::jmin(traceMaxValue, 1.0)); + actualTraceMin = juce::jmax(MIN_TRACE, juce::jmin(traceMinValue, actualTraceMax - MIN_TRACE)); + + if (!renderingSample) { + incrementShapeDrawing(); + } + + double drawnFrameLength = traceMaxEnabled ? actualTraceMax * frameLength : frameLength; + + if (!renderingSample && frameDrawn >= drawnFrameLength) { + if (sound != nullptr) { + frameLength = sound->updateFrame(frame); + } + // TODO: updateFrame already iterates over all the shapes, + // so we can improve performance by calculating frameDrawn + // and shapeDrawn directly. frameDrawn is simply actualTraceMin * frameLength + // but shapeDrawn is the amount of the current shape that has been drawn so + // we need to iterate over all the shapes to calculate it. + if (traceMinEnabled) { + while (frameDrawn < actualTraceMin * frameLength) { + incrementShapeDrawing(); + } + } + } + } +} + +void ShapeVoice::stopNote(float velocity, bool allowTailOff) { + if (allowTailOff) { + if (tailOff == 0.0) { + tailOff = 1.0; + } + } else { + clearCurrentNote(); + sound = nullptr; + } +} + +void ShapeVoice::pitchWheelMoved(int newPitchWheelValue) {} + +void ShapeVoice::controllerMoved(int controllerNumber, int newControllerValue) {} diff --git a/Source/audio/ShapeVoice.h b/Source/audio/ShapeVoice.h new file mode 100644 index 0000000..ecd33a8 --- /dev/null +++ b/Source/audio/ShapeVoice.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include "ShapeSound.h" + +class OscirenderAudioProcessor; +class ShapeVoice : public juce::SynthesiserVoice { +public: + ShapeVoice(OscirenderAudioProcessor& p); + + bool canPlaySound(juce::SynthesiserSound* sound) override; + void startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) override; + void renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override; + void stopNote(float velocity, bool allowTailOff) override; + void pitchWheelMoved(int newPitchWheelValue) override; + void controllerMoved(int controllerNumber, int newControllerValue) override; + + void incrementShapeDrawing(); + +private: + const double MIN_TRACE = 0.005; + const double MIN_LENGTH_INCREMENT = 0.000001; + + OscirenderAudioProcessor& audioProcessor; + std::vector> frame; + ShapeSound* sound = nullptr; + double actualTraceMin; + double actualTraceMax; + + double frameLength = 0.0; + int currentShape = 0; + double shapeDrawn = 0.0; + double frameDrawn = 0.0; + double lengthIncrement = 0.0; + + double tailOff = 0.0; + double frequency = 1.0; +}; \ No newline at end of file diff --git a/Source/parser/FrameConsumer.h b/Source/parser/FrameConsumer.h index 5ba4907..b1ab19e 100644 --- a/Source/parser/FrameConsumer.h +++ b/Source/parser/FrameConsumer.h @@ -6,5 +6,5 @@ class FrameConsumer { public: - virtual void addFrame(std::vector> frame, int fileIndex) = 0; + virtual void addFrame(std::vector>& frame) = 0; }; \ No newline at end of file diff --git a/Source/parser/FrameProducer.cpp b/Source/parser/FrameProducer.cpp index 38828bb..755fe0f 100644 --- a/Source/parser/FrameProducer.cpp +++ b/Source/parser/FrameProducer.cpp @@ -9,15 +9,7 @@ FrameProducer::~FrameProducer() { void FrameProducer::run() { while (!threadShouldExit()) { - // this lock is needed so that frameSource isn't deleted whilst nextFrame() is being called - juce::SpinLock::ScopedLockType scope(lock); - frameConsumer.addFrame(frameSource->nextFrame(), sourceFileIndex); + auto frame = frameSource->nextFrame(); + frameConsumer.addFrame(frame); } } - -void FrameProducer::setSource(std::shared_ptr source, int fileIndex) { - juce::SpinLock::ScopedLockType scope(lock); - frameSource->disable(); - frameSource = source; - sourceFileIndex = fileIndex; -} diff --git a/Source/parser/FrameProducer.h b/Source/parser/FrameProducer.h index 0057830..c9e9743 100644 --- a/Source/parser/FrameProducer.h +++ b/Source/parser/FrameProducer.h @@ -10,10 +10,8 @@ public: ~FrameProducer() override; void run() override; - void setSource(std::shared_ptr, int fileIndex); private: juce::SpinLock lock; FrameConsumer& frameConsumer; std::shared_ptr frameSource; - int sourceFileIndex = -1; }; \ No newline at end of file diff --git a/osci-render.jucer b/osci-render.jucer index 653d8a3..2900487 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -66,6 +66,10 @@ + + + +