kopia lustrzana https://github.com/jameshball/osci-render
Merge synthesiser branch into mac-support
commit
c6c3b21e06
|
@ -33,6 +33,11 @@ OscirenderLookAndFeel::OscirenderLookAndFeel() {
|
||||||
setColour(juce::CodeEditorComponent::highlightColourId, Colours::grey);
|
setColour(juce::CodeEditorComponent::highlightColourId, Colours::grey);
|
||||||
setColour(juce::CaretComponent::caretColourId, Dracula::foreground);
|
setColour(juce::CaretComponent::caretColourId, Dracula::foreground);
|
||||||
setColour(juce::TextEditor::highlightColourId, Colours::grey);
|
setColour(juce::TextEditor::highlightColourId, Colours::grey);
|
||||||
|
setColour(juce::TabbedButtonBar::tabOutlineColourId, Colours::veryDark);
|
||||||
|
setColour(juce::TabbedButtonBar::frontOutlineColourId, Colours::veryDark);
|
||||||
|
setColour(juce::TabbedButtonBar::tabTextColourId, juce::Colours::black);
|
||||||
|
setColour(juce::TabbedButtonBar::frontTextColourId, juce::Colours::black);
|
||||||
|
setColour(juce::TabbedComponent::outlineColourId, Colours::veryDark);
|
||||||
|
|
||||||
getCurrentColourScheme().setUIColour(ColourScheme::widgetBackground, Colours::veryDark);
|
getCurrentColourScheme().setUIColour(ColourScheme::widgetBackground, Colours::veryDark);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "MidiComponent.h"
|
||||||
|
#include "PluginEditor.h"
|
||||||
|
|
||||||
|
MidiComponent::MidiComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) {
|
||||||
|
addAndMakeVisible(midiToggle);
|
||||||
|
addAndMakeVisible(keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MidiComponent::resized() {
|
||||||
|
auto area = getLocalBounds().reduced(5);
|
||||||
|
midiToggle.setBounds(area.removeFromTop(50));
|
||||||
|
keyboard.setBounds(area.removeFromBottom(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiComponent::paint(juce::Graphics& g) {
|
||||||
|
auto rect = getLocalBounds().reduced(5);
|
||||||
|
g.setColour(getLookAndFeel().findColour(groupComponentBackgroundColourId));
|
||||||
|
g.fillRect(rect);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "PluginProcessor.h"
|
||||||
|
|
||||||
|
class OscirenderAudioProcessorEditor;
|
||||||
|
class MidiComponent : public juce::Component {
|
||||||
|
public:
|
||||||
|
MidiComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||||
|
|
||||||
|
void resized() override;
|
||||||
|
void paint(juce::Graphics& g) override;
|
||||||
|
private:
|
||||||
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
OscirenderAudioProcessorEditor& pluginEditor;
|
||||||
|
|
||||||
|
juce::ToggleButton midiToggle{"Enable MIDI"};
|
||||||
|
juce::MidiKeyboardState keyboardState;
|
||||||
|
juce::MidiKeyboardComponent keyboard{keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard};
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiComponent)
|
||||||
|
};
|
|
@ -4,11 +4,11 @@
|
||||||
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p)
|
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p)
|
||||||
: AudioProcessorEditor(&p), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white)
|
: AudioProcessorEditor(&p), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white)
|
||||||
{
|
{
|
||||||
addAndMakeVisible(effects);
|
addAndMakeVisible(tabs);
|
||||||
addAndMakeVisible(main);
|
tabs.addTab("Main", juce::Colours::white, &settings, false);
|
||||||
addChildComponent(lua);
|
tabs.addTab("MIDI", juce::Colours::white, &midi, false);
|
||||||
addChildComponent(obj);
|
tabs.setTabBackgroundColour(0, juce::Colours::white);
|
||||||
addChildComponent(txt);
|
tabs.setTabBackgroundColour(1, juce::Colours::white);
|
||||||
addAndMakeVisible(volume);
|
addAndMakeVisible(volume);
|
||||||
|
|
||||||
menuBar.setModel(&menuBarModel);
|
menuBar.setModel(&menuBarModel);
|
||||||
|
@ -82,16 +82,6 @@ void OscirenderAudioProcessorEditor::initialiseCodeEditors() {
|
||||||
void OscirenderAudioProcessorEditor::paint(juce::Graphics& g) {
|
void OscirenderAudioProcessorEditor::paint(juce::Graphics& g) {
|
||||||
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
|
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
|
||||||
|
|
||||||
juce::DropShadow ds(juce::Colours::black, 10, juce::Point<int>(0, 0));
|
|
||||||
ds.drawForRectangle(g, main.getBounds());
|
|
||||||
ds.drawForRectangle(g, effects.getBounds());
|
|
||||||
if (lua.isVisible()) {
|
|
||||||
ds.drawForRectangle(g, lua.getBounds());
|
|
||||||
}
|
|
||||||
if (obj.isVisible()) {
|
|
||||||
ds.drawForRectangle(g, obj.getBounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
g.setColour(juce::Colours::white);
|
g.setColour(juce::Colours::white);
|
||||||
g.setFont(15.0f);
|
g.setFont(15.0f);
|
||||||
}
|
}
|
||||||
|
@ -134,15 +124,8 @@ void OscirenderAudioProcessorEditor::resized() {
|
||||||
collapseButton.setShape(path, false, true, true);
|
collapseButton.setShape(path, false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto effectsSection = area.removeFromRight(1.2 * getWidth() / sections);
|
settings.sections = sections;
|
||||||
main.setBounds(area.reduced(5));
|
tabs.setBounds(area);
|
||||||
if (lua.isVisible() || obj.isVisible() || txt.isVisible()) {
|
|
||||||
auto altEffectsSection = effectsSection.removeFromBottom(juce::jmin(effectsSection.getHeight() / 2, txt.isVisible() ? 150 : 300));
|
|
||||||
lua.setBounds(altEffectsSection.reduced(5));
|
|
||||||
obj.setBounds(altEffectsSection.reduced(5));
|
|
||||||
txt.setBounds(altEffectsSection.reduced(5));
|
|
||||||
}
|
|
||||||
effects.setBounds(effectsSection.reduced(5));
|
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
@ -213,20 +196,7 @@ void OscirenderAudioProcessorEditor::updateCodeEditor() {
|
||||||
|
|
||||||
// parsersLock MUST be locked before calling this function
|
// parsersLock MUST be locked before calling this function
|
||||||
void OscirenderAudioProcessorEditor::fileUpdated(juce::String fileName) {
|
void OscirenderAudioProcessorEditor::fileUpdated(juce::String fileName) {
|
||||||
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false);
|
settings.fileUpdated(fileName);
|
||||||
lua.setVisible(false);
|
|
||||||
obj.setVisible(false);
|
|
||||||
txt.setVisible(false);
|
|
||||||
if (fileName.isEmpty()) {
|
|
||||||
// do nothing
|
|
||||||
} else if (extension == ".lua") {
|
|
||||||
lua.setVisible(true);
|
|
||||||
} else if (extension == ".obj") {
|
|
||||||
obj.setVisible(true);
|
|
||||||
} else if (extension == ".txt") {
|
|
||||||
txt.setVisible(true);
|
|
||||||
}
|
|
||||||
main.updateFileLabel();
|
|
||||||
updateCodeEditor();
|
updateCodeEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +207,7 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() {
|
||||||
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||||
initialiseCodeEditors();
|
initialiseCodeEditors();
|
||||||
txt.update();
|
settings.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
||||||
|
@ -310,7 +280,7 @@ bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) {
|
||||||
|
|
||||||
bool consumeKey2 = true;
|
bool consumeKey2 = true;
|
||||||
if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
||||||
obj.disableMouseRotation();
|
settings.disableMouseRotation();
|
||||||
} else if (key.getModifiers().isCommandDown() && key.getModifiers().isShiftDown() && key.getKeyCode() == 'S') {
|
} else if (key.getModifiers().isCommandDown() && key.getModifiers().isShiftDown() && key.getKeyCode() == 'S') {
|
||||||
saveProjectAs();
|
saveProjectAs();
|
||||||
} else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'S') {
|
} else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'S') {
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
#include <JuceHeader.h>
|
#include <JuceHeader.h>
|
||||||
#include "PluginProcessor.h"
|
#include "PluginProcessor.h"
|
||||||
#include "EffectsComponent.h"
|
#include "SettingsComponent.h"
|
||||||
#include "MainComponent.h"
|
#include "MidiComponent.h"
|
||||||
#include "LuaComponent.h"
|
|
||||||
#include "ObjComponent.h"
|
|
||||||
#include "TxtComponent.h"
|
|
||||||
#include "components/VolumeComponent.h"
|
#include "components/VolumeComponent.h"
|
||||||
#include "components/MainMenuBarModel.h"
|
#include "components/MainMenuBarModel.h"
|
||||||
#include "LookAndFeel.h"
|
#include "LookAndFeel.h"
|
||||||
|
@ -41,11 +38,9 @@ public:
|
||||||
private:
|
private:
|
||||||
OscirenderAudioProcessor& audioProcessor;
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
|
||||||
MainComponent main{audioProcessor, *this};
|
juce::TabbedComponent tabs{juce::TabbedButtonBar::TabsAtTop};
|
||||||
LuaComponent lua{audioProcessor, *this};
|
MidiComponent midi{audioProcessor, *this};
|
||||||
ObjComponent obj{audioProcessor, *this};
|
SettingsComponent settings{audioProcessor, *this};
|
||||||
TxtComponent txt{audioProcessor, *this};
|
|
||||||
EffectsComponent effects{audioProcessor, *this};
|
|
||||||
VolumeComponent volume{audioProcessor};
|
VolumeComponent volume{audioProcessor};
|
||||||
std::vector<std::shared_ptr<juce::CodeDocument>> codeDocuments;
|
std::vector<std::shared_ptr<juce::CodeDocument>> codeDocuments;
|
||||||
std::vector<std::shared_ptr<juce::CodeEditorComponent>> codeEditors;
|
std::vector<std::shared_ptr<juce::CodeEditorComponent>> codeEditors;
|
||||||
|
|
|
@ -32,8 +32,6 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
producer.startThread();
|
|
||||||
|
|
||||||
// locking isn't necessary here because we are in the constructor
|
// locking isn't necessary here because we are in the constructor
|
||||||
|
|
||||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||||
|
@ -137,6 +135,10 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
||||||
for (auto parameter : booleanParameters) {
|
for (auto parameter : booleanParameters) {
|
||||||
addParameter(parameter);
|
addParameter(parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
synth.addVoice(new ShapeVoice(*this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OscirenderAudioProcessor::~OscirenderAudioProcessor() {}
|
OscirenderAudioProcessor::~OscirenderAudioProcessor() {}
|
||||||
|
@ -194,6 +196,7 @@ void OscirenderAudioProcessor::changeProgramName(int index, const juce::String&
|
||||||
void OscirenderAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
|
void OscirenderAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
|
||||||
currentSampleRate = sampleRate;
|
currentSampleRate = sampleRate;
|
||||||
pitchDetector.setSampleRate(sampleRate);
|
pitchDetector.setSampleRate(sampleRate);
|
||||||
|
synth.setCurrentPlaybackSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessor::releaseResources() {
|
void OscirenderAudioProcessor::releaseResources() {
|
||||||
|
@ -304,7 +307,8 @@ void OscirenderAudioProcessor::updateFileBlock(int index, std::shared_ptr<juce::
|
||||||
void OscirenderAudioProcessor::addFile(juce::File file) {
|
void OscirenderAudioProcessor::addFile(juce::File file) {
|
||||||
fileBlocks.push_back(std::make_shared<juce::MemoryBlock>());
|
fileBlocks.push_back(std::make_shared<juce::MemoryBlock>());
|
||||||
fileNames.push_back(file.getFileName());
|
fileNames.push_back(file.getFileName());
|
||||||
parsers.push_back(std::make_unique<FileParser>());
|
parsers.push_back(std::make_shared<FileParser>());
|
||||||
|
sounds.push_back(new ShapeSound(parsers.back()));
|
||||||
file.createInputStream()->readIntoMemoryBlock(*fileBlocks.back());
|
file.createInputStream()->readIntoMemoryBlock(*fileBlocks.back());
|
||||||
|
|
||||||
openFile(fileBlocks.size() - 1);
|
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) {
|
void OscirenderAudioProcessor::addFile(juce::String fileName, const char* data, const int size) {
|
||||||
fileBlocks.push_back(std::make_shared<juce::MemoryBlock>());
|
fileBlocks.push_back(std::make_shared<juce::MemoryBlock>());
|
||||||
fileNames.push_back(fileName);
|
fileNames.push_back(fileName);
|
||||||
parsers.push_back(std::make_unique<FileParser>());
|
parsers.push_back(std::make_shared<FileParser>());
|
||||||
|
sounds.push_back(new ShapeSound(parsers.back()));
|
||||||
fileBlocks.back()->append(data, size);
|
fileBlocks.back()->append(data, size);
|
||||||
|
|
||||||
openFile(fileBlocks.size() - 1);
|
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<juce::MemoryBlock> data) {
|
void OscirenderAudioProcessor::addFile(juce::String fileName, std::shared_ptr<juce::MemoryBlock> data) {
|
||||||
fileBlocks.push_back(data);
|
fileBlocks.push_back(data);
|
||||||
fileNames.push_back(fileName);
|
fileNames.push_back(fileName);
|
||||||
parsers.push_back(std::make_unique<FileParser>());
|
parsers.push_back(std::make_shared<FileParser>());
|
||||||
|
sounds.push_back(new ShapeSound(parsers.back()));
|
||||||
|
|
||||||
openFile(fileBlocks.size() - 1);
|
openFile(fileBlocks.size() - 1);
|
||||||
}
|
}
|
||||||
|
@ -337,6 +343,7 @@ void OscirenderAudioProcessor::removeFile(int index) {
|
||||||
fileBlocks.erase(fileBlocks.begin() + index);
|
fileBlocks.erase(fileBlocks.begin() + index);
|
||||||
fileNames.erase(fileNames.begin() + index);
|
fileNames.erase(fileNames.begin() + index);
|
||||||
parsers.erase(parsers.begin() + index);
|
parsers.erase(parsers.begin() + index);
|
||||||
|
sounds.erase(sounds.begin() + index);
|
||||||
auto newFileIndex = index;
|
auto newFileIndex = index;
|
||||||
if (newFileIndex >= fileBlocks.size()) {
|
if (newFileIndex >= fileBlocks.size()) {
|
||||||
newFileIndex = fileBlocks.size() - 1;
|
newFileIndex = fileBlocks.size() - 1;
|
||||||
|
@ -363,17 +370,18 @@ void OscirenderAudioProcessor::openFile(int index) {
|
||||||
// used ONLY for changing the current file to an EXISTING file.
|
// used ONLY for changing the current file to an EXISTING file.
|
||||||
// much faster than openFile(int index) because it doesn't reparse any files.
|
// much faster than openFile(int index) because it doesn't reparse any files.
|
||||||
// parsersLock AND effectsLock must be locked before calling this function
|
// 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) {
|
void OscirenderAudioProcessor::changeCurrentFile(int index) {
|
||||||
|
synth.clearSounds();
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
currentFile = -1;
|
currentFile = -1;
|
||||||
producer.setSource(std::make_shared<FileParser>(), -1);
|
|
||||||
}
|
}
|
||||||
if (index < 0 || index >= fileBlocks.size()) {
|
if (index < 0 || index >= fileBlocks.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
producer.setSource(parsers[index], index);
|
synth.addSound(sounds[index]);
|
||||||
currentFile = index;
|
currentFile = index;
|
||||||
invalidateFrameBuffer = true;
|
|
||||||
updateLuaValues();
|
updateLuaValues();
|
||||||
updateObjValues();
|
updateObjValues();
|
||||||
}
|
}
|
||||||
|
@ -402,113 +410,19 @@ std::shared_ptr<juce::MemoryBlock> OscirenderAudioProcessor::getFileBlock(int in
|
||||||
return fileBlocks[index];
|
return fileBlocks[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void OscirenderAudioProcessor::addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) {
|
void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
|
||||||
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<float>& buffer, juce::MidiBuffer& midiMessages)
|
|
||||||
{
|
|
||||||
juce::ScopedNoDenormals noDenormals;
|
juce::ScopedNoDenormals noDenormals;
|
||||||
auto totalNumInputChannels = getTotalNumInputChannels();
|
auto totalNumInputChannels = getTotalNumInputChannels();
|
||||||
auto totalNumOutputChannels = getTotalNumOutputChannels();
|
auto totalNumOutputChannels = getTotalNumOutputChannels();
|
||||||
|
|
||||||
// In case we have more outputs than inputs, this code clears any output
|
buffer.clear();
|
||||||
// channels that didn't contain input data, (because these aren't
|
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
|
||||||
// guaranteed to be empty - they may contain garbage).
|
midiMessages.clear();
|
||||||
// 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());
|
|
||||||
|
|
||||||
|
|
||||||
auto* channelData = buffer.getArrayOfWritePointers();
|
auto* channelData = buffer.getArrayOfWritePointers();
|
||||||
auto numSamples = buffer.getNumSamples();
|
|
||||||
|
|
||||||
if (invalidateFrameBuffer) {
|
for (auto sample = 0; sample < buffer.getNumSamples(); ++sample) {
|
||||||
frameFifo.reset();
|
Vector2 channels = {buffer.getSample(0, sample), buffer.getSample(1, sample)};
|
||||||
// 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<FileParser> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||||
|
@ -523,8 +437,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x = channels.x;
|
double x = channels.x;
|
||||||
y = channels.y;
|
double y = channels.y;
|
||||||
|
|
||||||
x *= volume;
|
x *= volume;
|
||||||
y *= volume;
|
y *= volume;
|
||||||
|
@ -548,57 +462,9 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
||||||
consumer->notifyIfFull();
|
consumer->notifyIfFull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
bool OscirenderAudioProcessor::hasEditor() const {
|
||||||
return true; // (change this to false if you choose to not supply an editor)
|
return true; // (change this to false if you choose to not supply an editor)
|
||||||
|
|
|
@ -10,11 +10,10 @@
|
||||||
|
|
||||||
#include <JuceHeader.h>
|
#include <JuceHeader.h>
|
||||||
#include "shape/Shape.h"
|
#include "shape/Shape.h"
|
||||||
#include "parser/FileParser.h"
|
|
||||||
#include "parser/FrameProducer.h"
|
|
||||||
#include "parser/FrameConsumer.h"
|
|
||||||
#include "concurrency/BufferConsumer.h"
|
#include "concurrency/BufferConsumer.h"
|
||||||
#include "audio/Effect.h"
|
#include "audio/Effect.h"
|
||||||
|
#include "audio/ShapeSound.h"
|
||||||
|
#include "audio/ShapeVoice.h"
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include "audio/AudioWebSocketServer.h"
|
#include "audio/AudioWebSocketServer.h"
|
||||||
#include "audio/DelayEffect.h"
|
#include "audio/DelayEffect.h"
|
||||||
|
@ -29,7 +28,6 @@ class OscirenderAudioProcessor : public juce::AudioProcessor
|
||||||
#if JucePlugin_Enable_ARA
|
#if JucePlugin_Enable_ARA
|
||||||
, public juce::AudioProcessorARAExtension
|
, public juce::AudioProcessorARAExtension
|
||||||
#endif
|
#endif
|
||||||
, public FrameConsumer
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OscirenderAudioProcessor();
|
OscirenderAudioProcessor();
|
||||||
|
@ -161,19 +159,29 @@ public:
|
||||||
}, new EffectParameter("Rotate Speed", "objRotateSpeed", VERSION_HINT, 0.0, -1.0, 1.0)
|
}, new EffectParameter("Rotate Speed", "objRotateSpeed", VERSION_HINT, 0.0, -1.0, 1.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
std::shared_ptr<Effect> traceMax = std::make_shared<Effect>(
|
||||||
|
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||||
|
return input;
|
||||||
|
}, new EffectParameter("Trace max", "traceMax", VERSION_HINT, 1.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
std::shared_ptr<Effect> traceMin = std::make_shared<Effect>(
|
||||||
|
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||||
|
return input;
|
||||||
|
}, new EffectParameter("Trace min", "traceMin", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
std::shared_ptr<DelayEffect> delayEffect = std::make_shared<DelayEffect>();
|
std::shared_ptr<DelayEffect> delayEffect = std::make_shared<DelayEffect>();
|
||||||
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>(VERSION_HINT);
|
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>(VERSION_HINT);
|
||||||
|
|
||||||
juce::SpinLock parsersLock;
|
juce::SpinLock parsersLock;
|
||||||
std::vector<std::shared_ptr<FileParser>> parsers;
|
std::vector<std::shared_ptr<FileParser>> parsers;
|
||||||
|
std::vector<ShapeSound::Ptr> sounds;
|
||||||
std::vector<std::shared_ptr<juce::MemoryBlock>> fileBlocks;
|
std::vector<std::shared_ptr<juce::MemoryBlock>> fileBlocks;
|
||||||
std::vector<juce::String> fileNames;
|
std::vector<juce::String> fileNames;
|
||||||
std::atomic<int> currentFile = -1;
|
std::atomic<int> currentFile = -1;
|
||||||
|
|
||||||
juce::ChangeBroadcaster broadcaster;
|
juce::ChangeBroadcaster broadcaster;
|
||||||
|
|
||||||
FrameProducer producer = FrameProducer(*this, std::make_shared<FileParser>());
|
|
||||||
|
|
||||||
PitchDetector pitchDetector{*this};
|
PitchDetector pitchDetector{*this};
|
||||||
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
|
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
|
||||||
|
|
||||||
|
@ -185,7 +193,6 @@ public:
|
||||||
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
|
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
|
||||||
|
|
||||||
void addLuaSlider();
|
void addLuaSlider();
|
||||||
void addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) override;
|
|
||||||
void updateEffectPrecedence();
|
void updateEffectPrecedence();
|
||||||
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
||||||
void addFile(juce::File file);
|
void addFile(juce::File file);
|
||||||
|
@ -205,53 +212,17 @@ private:
|
||||||
std::atomic<double> volume = 1.0;
|
std::atomic<double> volume = 1.0;
|
||||||
std::atomic<double> threshold = 1.0;
|
std::atomic<double> threshold = 1.0;
|
||||||
|
|
||||||
juce::AbstractFifo frameFifo{ 10 };
|
|
||||||
std::vector<std::unique_ptr<Shape>> frameBuffer[10];
|
|
||||||
int frameBufferIndices[10];
|
|
||||||
|
|
||||||
int currentShape = 0;
|
|
||||||
std::vector<std::unique_ptr<Shape>> frame;
|
|
||||||
int currentBufferIndex = -1;
|
|
||||||
double frameLength;
|
|
||||||
double shapeDrawn = 0.0;
|
|
||||||
double frameDrawn = 0.0;
|
|
||||||
double lengthIncrement = 0.0;
|
|
||||||
bool invalidateFrameBuffer = false;
|
|
||||||
|
|
||||||
std::vector<BooleanParameter*> booleanParameters;
|
std::vector<BooleanParameter*> booleanParameters;
|
||||||
std::vector<std::shared_ptr<Effect>> allEffects;
|
std::vector<std::shared_ptr<Effect>> allEffects;
|
||||||
std::vector<std::shared_ptr<Effect>> permanentEffects;
|
std::vector<std::shared_ptr<Effect>> permanentEffects;
|
||||||
|
|
||||||
std::shared_ptr<Effect> traceMax = std::make_shared<Effect>(
|
juce::Synthesiser synth;
|
||||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
|
||||||
traceMaxValue = values[0];
|
|
||||||
traceMaxEnabled = true;
|
|
||||||
return input;
|
|
||||||
}, new EffectParameter("Trace max", "traceMax", VERSION_HINT, 1.0, 0.0, 1.0)
|
|
||||||
);
|
|
||||||
std::shared_ptr<Effect> traceMin = std::make_shared<Effect>(
|
|
||||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
|
||||||
traceMinValue = values[0];
|
|
||||||
traceMinEnabled = true;
|
|
||||||
return input;
|
|
||||||
}, new EffectParameter("Trace min", "traceMin", VERSION_HINT, 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::SpinLock consumerLock;
|
juce::SpinLock consumerLock;
|
||||||
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
||||||
|
|
||||||
AudioWebSocketServer softwareOscilloscopeServer{*this};
|
AudioWebSocketServer softwareOscilloscopeServer{*this};
|
||||||
|
|
||||||
void updateFrame();
|
|
||||||
void updateLengthIncrement();
|
|
||||||
void incrementShapeDrawing();
|
|
||||||
void updateLuaValues();
|
void updateLuaValues();
|
||||||
void updateObjValues();
|
void updateObjValues();
|
||||||
std::shared_ptr<Effect> getEffect(juce::String id);
|
std::shared_ptr<Effect> getEffect(juce::String id);
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#include "SettingsComponent.h"
|
||||||
|
#include "PluginEditor.h"
|
||||||
|
|
||||||
|
SettingsComponent::SettingsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) {
|
||||||
|
addAndMakeVisible(effects);
|
||||||
|
addAndMakeVisible(main);
|
||||||
|
addChildComponent(lua);
|
||||||
|
addChildComponent(obj);
|
||||||
|
addChildComponent(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SettingsComponent::resized() {
|
||||||
|
auto area = getLocalBounds();
|
||||||
|
auto effectsSection = area.removeFromRight(1.2 * pluginEditor.getWidth() / sections);
|
||||||
|
area.removeFromLeft(5);
|
||||||
|
area.removeFromRight(3);
|
||||||
|
area.removeFromTop(5);
|
||||||
|
area.removeFromBottom(5);
|
||||||
|
|
||||||
|
main.setBounds(area);
|
||||||
|
if (lua.isVisible() || obj.isVisible() || txt.isVisible()) {
|
||||||
|
int height = txt.isVisible() ? 150 : 300;
|
||||||
|
auto altEffectsSection = effectsSection.removeFromBottom(juce::jmin(effectsSection.getHeight() / 2, height));
|
||||||
|
altEffectsSection.removeFromTop(3);
|
||||||
|
altEffectsSection.removeFromLeft(2);
|
||||||
|
altEffectsSection.removeFromRight(5);
|
||||||
|
altEffectsSection.removeFromBottom(5);
|
||||||
|
|
||||||
|
lua.setBounds(altEffectsSection);
|
||||||
|
obj.setBounds(altEffectsSection);
|
||||||
|
txt.setBounds(altEffectsSection);
|
||||||
|
|
||||||
|
effectsSection.removeFromBottom(2);
|
||||||
|
} else {
|
||||||
|
effectsSection.removeFromBottom(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
effectsSection.removeFromLeft(2);
|
||||||
|
effectsSection.removeFromRight(5);
|
||||||
|
effectsSection.removeFromTop(5);
|
||||||
|
effects.setBounds(effectsSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsComponent::fileUpdated(juce::String fileName) {
|
||||||
|
juce::String extension = fileName.fromLastOccurrenceOf(".", true, false);
|
||||||
|
lua.setVisible(false);
|
||||||
|
obj.setVisible(false);
|
||||||
|
txt.setVisible(false);
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
// do nothing
|
||||||
|
} else if (extension == ".lua") {
|
||||||
|
lua.setVisible(true);
|
||||||
|
} else if (extension == ".obj") {
|
||||||
|
obj.setVisible(true);
|
||||||
|
} else if (extension == ".txt") {
|
||||||
|
txt.setVisible(true);
|
||||||
|
}
|
||||||
|
main.updateFileLabel();
|
||||||
|
resized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsComponent::update() {
|
||||||
|
txt.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsComponent::disableMouseRotation() {
|
||||||
|
obj.disableMouseRotation();
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "PluginProcessor.h"
|
||||||
|
#include "MainComponent.h"
|
||||||
|
#include "LuaComponent.h"
|
||||||
|
#include "ObjComponent.h"
|
||||||
|
#include "TxtComponent.h"
|
||||||
|
#include "EffectsComponent.h"
|
||||||
|
|
||||||
|
class OscirenderAudioProcessorEditor;
|
||||||
|
class SettingsComponent : public juce::Component {
|
||||||
|
public:
|
||||||
|
SettingsComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||||
|
|
||||||
|
void resized() override;
|
||||||
|
void fileUpdated(juce::String fileName);
|
||||||
|
void update();
|
||||||
|
void disableMouseRotation();
|
||||||
|
|
||||||
|
int sections = 2;
|
||||||
|
private:
|
||||||
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
OscirenderAudioProcessorEditor& pluginEditor;
|
||||||
|
|
||||||
|
MainComponent main{audioProcessor, pluginEditor};
|
||||||
|
LuaComponent lua{audioProcessor, pluginEditor};
|
||||||
|
ObjComponent obj{audioProcessor, pluginEditor};
|
||||||
|
TxtComponent txt{audioProcessor, pluginEditor};
|
||||||
|
EffectsComponent effects{audioProcessor, pluginEditor};
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsComponent)
|
||||||
|
};
|
|
@ -91,6 +91,16 @@ double Effect::getValue() {
|
||||||
return getValue(0);
|
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) {
|
void Effect::setValue(int index, double value) {
|
||||||
parameters[index]->setUnnormalisedValueNotifyingHost(value);
|
parameters[index]->setUnnormalisedValueNotifyingHost(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ public:
|
||||||
void apply();
|
void apply();
|
||||||
double getValue(int index);
|
double getValue(int index);
|
||||||
double getValue();
|
double getValue();
|
||||||
|
double getActualValue(int index);
|
||||||
|
double getActualValue();
|
||||||
void setValue(int index, double value);
|
void setValue(int index, double value);
|
||||||
void setValue(double value);
|
void setValue(double value);
|
||||||
int getPrecedence();
|
int getPrecedence();
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "ShapeSound.h"
|
||||||
|
|
||||||
|
ShapeSound::ShapeSound(std::shared_ptr<FileParser> parser) : parser(parser) {
|
||||||
|
if (parser->isSample()) {
|
||||||
|
producer = std::make_unique<FrameProducer>(*this, std::make_shared<FileParser>());
|
||||||
|
} else {
|
||||||
|
producer = std::make_unique<FrameProducer>(*this, parser);
|
||||||
|
}
|
||||||
|
producer->startThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShapeSound::appliesToNote(int note) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShapeSound::appliesToChannel(int channel) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeSound::addFrame(std::vector<std::unique_ptr<Shape>>& 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<std::unique_ptr<Shape>>& 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;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "../parser/FileParser.h"
|
||||||
|
#include "../parser/FrameProducer.h"
|
||||||
|
#include "../parser/FrameConsumer.h"
|
||||||
|
|
||||||
|
class ShapeSound : public juce::SynthesiserSound, public FrameConsumer {
|
||||||
|
public:
|
||||||
|
ShapeSound(std::shared_ptr<FileParser> parser);
|
||||||
|
|
||||||
|
bool appliesToNote(int note) override;
|
||||||
|
bool appliesToChannel(int channel) override;
|
||||||
|
void addFrame(std::vector<std::unique_ptr<Shape>>& frame) override;
|
||||||
|
double updateFrame(std::vector<std::unique_ptr<Shape>>& frame);
|
||||||
|
|
||||||
|
std::shared_ptr<FileParser> parser;
|
||||||
|
|
||||||
|
using Ptr = juce::ReferenceCountedObjectPtr<ShapeSound>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
juce::AbstractFifo frameFifo{ 10 };
|
||||||
|
std::vector<std::unique_ptr<Shape>> frameBuffer[10];
|
||||||
|
std::unique_ptr<FrameProducer> producer;
|
||||||
|
double frameLength = 0.0;
|
||||||
|
};
|
|
@ -0,0 +1,152 @@
|
||||||
|
#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<ShapeSound*> (sound) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) {
|
||||||
|
auto* shapeSound = dynamic_cast<ShapeSound*>(sound);
|
||||||
|
|
||||||
|
if (shapeSound != nullptr) {
|
||||||
|
this->sound = shapeSound;
|
||||||
|
int tries = 0;
|
||||||
|
while (frame.empty() && tries < 20) {
|
||||||
|
frameLength = shapeSound->updateFrame(frame);
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
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) {}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#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<std::unique_ptr<Shape>> 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;
|
||||||
|
};
|
|
@ -6,5 +6,5 @@
|
||||||
|
|
||||||
class FrameConsumer {
|
class FrameConsumer {
|
||||||
public:
|
public:
|
||||||
virtual void addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) = 0;
|
virtual void addFrame(std::vector<std::unique_ptr<Shape>>& frame) = 0;
|
||||||
};
|
};
|
|
@ -9,15 +9,7 @@ FrameProducer::~FrameProducer() {
|
||||||
|
|
||||||
void FrameProducer::run() {
|
void FrameProducer::run() {
|
||||||
while (!threadShouldExit()) {
|
while (!threadShouldExit()) {
|
||||||
// this lock is needed so that frameSource isn't deleted whilst nextFrame() is being called
|
auto frame = frameSource->nextFrame();
|
||||||
juce::SpinLock::ScopedLockType scope(lock);
|
frameConsumer.addFrame(frame);
|
||||||
frameConsumer.addFrame(frameSource->nextFrame(), sourceFileIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameProducer::setSource(std::shared_ptr<FileParser> source, int fileIndex) {
|
|
||||||
juce::SpinLock::ScopedLockType scope(lock);
|
|
||||||
frameSource->disable();
|
|
||||||
frameSource = source;
|
|
||||||
sourceFileIndex = fileIndex;
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,10 +10,8 @@ public:
|
||||||
~FrameProducer() override;
|
~FrameProducer() override;
|
||||||
|
|
||||||
void run() override;
|
void run() override;
|
||||||
void setSource(std::shared_ptr<FileParser>, int fileIndex);
|
|
||||||
private:
|
private:
|
||||||
juce::SpinLock lock;
|
juce::SpinLock lock;
|
||||||
FrameConsumer& frameConsumer;
|
FrameConsumer& frameConsumer;
|
||||||
std::shared_ptr<FileParser> frameSource;
|
std::shared_ptr<FileParser> frameSource;
|
||||||
int sourceFileIndex = -1;
|
|
||||||
};
|
};
|
|
@ -66,6 +66,10 @@
|
||||||
<FILE id="PbbNqz" name="RotateEffect.cpp" compile="1" resource="0"
|
<FILE id="PbbNqz" name="RotateEffect.cpp" compile="1" resource="0"
|
||||||
file="Source/audio/RotateEffect.cpp"/>
|
file="Source/audio/RotateEffect.cpp"/>
|
||||||
<FILE id="tUwNZV" name="RotateEffect.h" compile="0" resource="0" file="Source/audio/RotateEffect.h"/>
|
<FILE id="tUwNZV" name="RotateEffect.h" compile="0" resource="0" file="Source/audio/RotateEffect.h"/>
|
||||||
|
<FILE id="dBaZAV" name="ShapeSound.cpp" compile="1" resource="0" file="Source/audio/ShapeSound.cpp"/>
|
||||||
|
<FILE id="VKBirB" name="ShapeSound.h" compile="0" resource="0" file="Source/audio/ShapeSound.h"/>
|
||||||
|
<FILE id="UcPZ09" name="ShapeVoice.cpp" compile="1" resource="0" file="Source/audio/ShapeVoice.cpp"/>
|
||||||
|
<FILE id="WId4vx" name="ShapeVoice.h" compile="0" resource="0" file="Source/audio/ShapeVoice.h"/>
|
||||||
<FILE id="iUEfwT" name="SmoothEffect.cpp" compile="1" resource="0"
|
<FILE id="iUEfwT" name="SmoothEffect.cpp" compile="1" resource="0"
|
||||||
file="Source/audio/SmoothEffect.cpp"/>
|
file="Source/audio/SmoothEffect.cpp"/>
|
||||||
<FILE id="Vwjht7" name="SmoothEffect.h" compile="0" resource="0" file="Source/audio/SmoothEffect.h"/>
|
<FILE id="Vwjht7" name="SmoothEffect.h" compile="0" resource="0" file="Source/audio/SmoothEffect.h"/>
|
||||||
|
@ -376,6 +380,9 @@
|
||||||
<FILE id="GKBQ8j" name="MainComponent.cpp" compile="1" resource="0"
|
<FILE id="GKBQ8j" name="MainComponent.cpp" compile="1" resource="0"
|
||||||
file="Source/MainComponent.cpp"/>
|
file="Source/MainComponent.cpp"/>
|
||||||
<FILE id="RU8fGr" name="MainComponent.h" compile="0" resource="0" file="Source/MainComponent.h"/>
|
<FILE id="RU8fGr" name="MainComponent.h" compile="0" resource="0" file="Source/MainComponent.h"/>
|
||||||
|
<FILE id="eB92KJ" name="MidiComponent.cpp" compile="1" resource="0"
|
||||||
|
file="Source/MidiComponent.cpp"/>
|
||||||
|
<FILE id="GJqoJa" name="MidiComponent.h" compile="0" resource="0" file="Source/MidiComponent.h"/>
|
||||||
<GROUP id="{E6ED85A9-3843-825F-EF48-BCF81E38F8AD}" name="obj">
|
<GROUP id="{E6ED85A9-3843-825F-EF48-BCF81E38F8AD}" name="obj">
|
||||||
<FILE id="Tyz6WY" name="Camera.cpp" compile="1" resource="0" file="Source/obj/Camera.cpp"/>
|
<FILE id="Tyz6WY" name="Camera.cpp" compile="1" resource="0" file="Source/obj/Camera.cpp"/>
|
||||||
<FILE id="ix12FT" name="Camera.h" compile="0" resource="0" file="Source/obj/Camera.h"/>
|
<FILE id="ix12FT" name="Camera.h" compile="0" resource="0" file="Source/obj/Camera.h"/>
|
||||||
|
@ -407,6 +414,10 @@
|
||||||
file="Source/PluginProcessor.cpp"/>
|
file="Source/PluginProcessor.cpp"/>
|
||||||
<FILE id="G4mTsK" name="PluginProcessor.h" compile="0" resource="0"
|
<FILE id="G4mTsK" name="PluginProcessor.h" compile="0" resource="0"
|
||||||
file="Source/PluginProcessor.h"/>
|
file="Source/PluginProcessor.h"/>
|
||||||
|
<FILE id="x57ccs" name="SettingsComponent.cpp" compile="1" resource="0"
|
||||||
|
file="Source/SettingsComponent.cpp"/>
|
||||||
|
<FILE id="Vlmozi" name="SettingsComponent.h" compile="0" resource="0"
|
||||||
|
file="Source/SettingsComponent.h"/>
|
||||||
<GROUP id="{92CEA658-C82C-9CEB-15EB-945EF6B6B5C8}" name="shape">
|
<GROUP id="{92CEA658-C82C-9CEB-15EB-945EF6B6B5C8}" name="shape">
|
||||||
<FILE id="iglTFG" name="CircleArc.cpp" compile="1" resource="0" file="Source/shape/CircleArc.cpp"/>
|
<FILE id="iglTFG" name="CircleArc.cpp" compile="1" resource="0" file="Source/shape/CircleArc.cpp"/>
|
||||||
<FILE id="T3S8Sg" name="CircleArc.h" compile="0" resource="0" file="Source/shape/CircleArc.h"/>
|
<FILE id="T3S8Sg" name="CircleArc.h" compile="0" resource="0" file="Source/shape/CircleArc.h"/>
|
||||||
|
|
Ładowanie…
Reference in New Issue