kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #63 from jameshball/mac-support
Add macos support, add basic MIDI support, fix several major bugspull/170/head
commit
a786f0edab
|
@ -3,6 +3,8 @@
|
|||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
.DS_Store
|
||||
|
||||
# ignore JUCE
|
||||
**/Builds
|
||||
**/JuceLibraryCode
|
||||
|
|
|
@ -284,4 +284,4 @@ double OscirenderAudioProcessor::valueFromLegacy(double value, const juce::Strin
|
|||
return std::pow(12000.0, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ OscirenderLookAndFeel::OscirenderLookAndFeel() {
|
|||
setColour(juce::CodeEditorComponent::highlightColourId, Colours::grey);
|
||||
setColour(juce::CaretComponent::caretColourId, Dracula::foreground);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
|
|||
|
||||
fileButton.onClick = [this] {
|
||||
chooser = std::make_unique<juce::FileChooser>("Open", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.obj;*.svg;*.lua;*.txt");
|
||||
auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectMultipleItems;
|
||||
auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectMultipleItems |
|
||||
juce::FileBrowserComponent::canSelectFiles;
|
||||
|
||||
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <numbers>
|
||||
|
||||
class MathUtil {
|
||||
public:
|
||||
|
||||
// from https://stackoverflow.com/questions/11980292/how-to-wrap-around-a-range
|
||||
static inline double wrapAngle(double angle) {
|
||||
double twoPi = 2.0 * std::numbers::pi;
|
||||
return angle - twoPi * floor(angle / twoPi);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#include "MidiComponent.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
MidiComponent::MidiComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) {
|
||||
addAndMakeVisible(midiToggle);
|
||||
addAndMakeVisible(keyboard);
|
||||
|
||||
midiToggle.onClick = [this]() {
|
||||
audioProcessor.midiEnabled->setBoolValueNotifyingHost(midiToggle.getToggleState());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
#include "components/SvgButton.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class ObjComponent : public juce::GroupComponent, public juce::MouseListener {
|
||||
class ObjComponent : public juce::GroupComponent {
|
||||
public:
|
||||
ObjComponent(OscirenderAudioProcessor&, OscirenderAudioProcessorEditor&);
|
||||
~ObjComponent();
|
||||
|
@ -32,4 +32,4 @@ private:
|
|||
std::shared_ptr<SvgButton> fixedRotateZ = std::make_shared<SvgButton>("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateZ);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ObjComponent)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p), audioProcessor(p), collapseButton("Collapse", juce::Colours::white, juce::Colours::white, juce::Colours::white)
|
||||
{
|
||||
addAndMakeVisible(effects);
|
||||
addAndMakeVisible(main);
|
||||
addChildComponent(lua);
|
||||
addChildComponent(obj);
|
||||
addChildComponent(txt);
|
||||
addAndMakeVisible(tabs);
|
||||
tabs.addTab("Main", juce::Colours::white, &settings, false);
|
||||
tabs.addTab("MIDI", juce::Colours::white, &midi, false);
|
||||
tabs.setTabBackgroundColour(0, juce::Colours::white);
|
||||
tabs.setTabBackgroundColour(1, juce::Colours::white);
|
||||
addAndMakeVisible(volume);
|
||||
|
||||
menuBar.setModel(&menuBarModel);
|
||||
|
@ -82,16 +82,6 @@ void OscirenderAudioProcessorEditor::initialiseCodeEditors() {
|
|||
void OscirenderAudioProcessorEditor::paint(juce::Graphics& g) {
|
||||
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.setFont(15.0f);
|
||||
}
|
||||
|
@ -134,15 +124,8 @@ void OscirenderAudioProcessorEditor::resized() {
|
|||
collapseButton.setShape(path, false, true, true);
|
||||
}
|
||||
|
||||
auto effectsSection = area.removeFromRight(1.2 * getWidth() / sections);
|
||||
main.setBounds(area.reduced(5));
|
||||
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));
|
||||
settings.sections = sections;
|
||||
tabs.setBounds(area);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
@ -213,20 +196,7 @@ void OscirenderAudioProcessorEditor::updateCodeEditor() {
|
|||
|
||||
// parsersLock MUST be locked before calling this function
|
||||
void OscirenderAudioProcessorEditor::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();
|
||||
settings.fileUpdated(fileName);
|
||||
updateCodeEditor();
|
||||
}
|
||||
|
||||
|
@ -237,7 +207,7 @@ void OscirenderAudioProcessorEditor::handleAsyncUpdate() {
|
|||
void OscirenderAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.parsersLock);
|
||||
initialiseCodeEditors();
|
||||
txt.update();
|
||||
settings.update();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessorEditor::editPerspectiveFunction(bool enable) {
|
||||
|
@ -310,7 +280,7 @@ bool OscirenderAudioProcessorEditor::keyPressed(const juce::KeyPress& key) {
|
|||
|
||||
bool consumeKey2 = true;
|
||||
if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
||||
obj.disableMouseRotation();
|
||||
settings.disableMouseRotation();
|
||||
} else if (key.getModifiers().isCommandDown() && key.getModifiers().isShiftDown() && key.getKeyCode() == 'S') {
|
||||
saveProjectAs();
|
||||
} else if (key.getModifiers().isCommandDown() && key.getKeyCode() == 'S') {
|
||||
|
@ -330,7 +300,8 @@ void OscirenderAudioProcessorEditor::newProject() {
|
|||
|
||||
void OscirenderAudioProcessorEditor::openProject() {
|
||||
chooser = std::make_unique<juce::FileChooser>("Load osci-render Project", juce::File::getSpecialLocation(juce::File::userHomeDirectory), "*.osci");
|
||||
auto flags = juce::FileBrowserComponent::openMode;
|
||||
auto flags = juce::FileBrowserComponent::openMode |
|
||||
juce::FileBrowserComponent::canSelectFiles;
|
||||
|
||||
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
|
||||
auto file = chooser.getResult();
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
|
||||
#include <JuceHeader.h>
|
||||
#include "PluginProcessor.h"
|
||||
#include "EffectsComponent.h"
|
||||
#include "MainComponent.h"
|
||||
#include "LuaComponent.h"
|
||||
#include "ObjComponent.h"
|
||||
#include "TxtComponent.h"
|
||||
#include "SettingsComponent.h"
|
||||
#include "MidiComponent.h"
|
||||
#include "components/VolumeComponent.h"
|
||||
#include "components/MainMenuBarModel.h"
|
||||
#include "LookAndFeel.h"
|
||||
|
@ -41,11 +38,9 @@ public:
|
|||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
||||
MainComponent main{audioProcessor, *this};
|
||||
LuaComponent lua{audioProcessor, *this};
|
||||
ObjComponent obj{audioProcessor, *this};
|
||||
TxtComponent txt{audioProcessor, *this};
|
||||
EffectsComponent effects{audioProcessor, *this};
|
||||
juce::TabbedComponent tabs{juce::TabbedButtonBar::TabsAtTop};
|
||||
MidiComponent midi{audioProcessor, *this};
|
||||
SettingsComponent settings{audioProcessor, *this};
|
||||
VolumeComponent volume{audioProcessor};
|
||||
std::vector<std::shared_ptr<juce::CodeDocument>> codeDocuments;
|
||||
std::vector<std::shared_ptr<juce::CodeEditorComponent>> codeEditors;
|
||||
|
|
|
@ -32,62 +32,60 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
)
|
||||
#endif
|
||||
{
|
||||
producer.startThread();
|
||||
|
||||
// locking isn't necessary here because we are in the constructor
|
||||
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<BitCrushEffect>(),
|
||||
new EffectParameter("Bit Crush", "bitCrush", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Bit Crush", "bitCrush", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<BulgeEffect>(),
|
||||
new EffectParameter("Bulge", "bulge", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Bulge", "bulge", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<RotateEffect>(),
|
||||
new EffectParameter("2D Rotate", "2DRotateSpeed", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("2D Rotate", "2DRotateSpeed", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<VectorCancellingEffect>(),
|
||||
new EffectParameter("Vector Cancelling", "vectorCancelling", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Vector Cancelling", "vectorCancelling", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<DistortEffect>(false),
|
||||
new EffectParameter("Distort X", "distortX", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Distort X", "distortX", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<DistortEffect>(true),
|
||||
new EffectParameter("Distort Y", "distortY", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Distort Y", "distortY", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
input.x += values[0];
|
||||
input.y += values[1];
|
||||
return input;
|
||||
}, std::vector<EffectParameter*>{new EffectParameter("Translate X", "translateX", 0.0, -1.0, 1.0), new EffectParameter("Translate Y", "translateY", 0.0, -1.0, 1.0)}
|
||||
}, std::vector<EffectParameter*>{new EffectParameter("Translate X", "translateX", VERSION_HINT, 0.0, -1.0, 1.0), new EffectParameter("Translate Y", "translateY", VERSION_HINT, 0.0, -1.0, 1.0)}
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<SmoothEffect>(),
|
||||
new EffectParameter("Smoothing", "smoothing", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Smoothing", "smoothing", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
wobbleEffect,
|
||||
new EffectParameter("Wobble", "wobble", 0.0, 0.0, 1.0)
|
||||
new EffectParameter("Wobble", "wobble", VERSION_HINT, 0.0, 0.0, 1.0)
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
delayEffect,
|
||||
std::vector<EffectParameter*>{new EffectParameter("Delay Decay", "delayDecay", 0.0, 0.0, 1.0), new EffectParameter("Delay Length", "delayLength", 0.5, 0.0, 1.0)}
|
||||
std::vector<EffectParameter*>{new EffectParameter("Delay Decay", "delayDecay", VERSION_HINT, 0.0, 0.0, 1.0), new EffectParameter("Delay Length", "delayLength", VERSION_HINT, 0.5, 0.0, 1.0)}
|
||||
));
|
||||
toggleableEffects.push_back(std::make_shared<Effect>(
|
||||
perspectiveEffect,
|
||||
std::vector<EffectParameter*>{
|
||||
new EffectParameter("3D Perspective", "perspectiveStrength", 0.0, 0.0, 1.0),
|
||||
new EffectParameter("Depth (z)", "perspectiveZPos", 0.1, 0.0, 1.0),
|
||||
new EffectParameter("Rotate Speed", "perspectiveRotateSpeed", 0.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate X", "perspectiveRotateX", 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Y", "perspectiveRotateY", 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Z", "perspectiveRotateZ", 0.0, -1.0, 1.0),
|
||||
new EffectParameter("3D Perspective", "perspectiveStrength", VERSION_HINT, 0.0, 0.0, 1.0),
|
||||
new EffectParameter("Depth (z)", "perspectiveZPos", VERSION_HINT, 0.1, 0.0, 1.0),
|
||||
new EffectParameter("Rotate Speed", "perspectiveRotateSpeed", VERSION_HINT, 0.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate X", "perspectiveRotateX", VERSION_HINT, 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Y", "perspectiveRotateY", VERSION_HINT, 1.0, -1.0, 1.0),
|
||||
new EffectParameter("Rotate Z", "perspectiveRotateZ", VERSION_HINT, 0.0, -1.0, 1.0),
|
||||
}
|
||||
));
|
||||
toggleableEffects.push_back(traceMax);
|
||||
|
@ -133,10 +131,17 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
|
|||
booleanParameters.push_back(perspectiveEffect->fixedRotateX);
|
||||
booleanParameters.push_back(perspectiveEffect->fixedRotateY);
|
||||
booleanParameters.push_back(perspectiveEffect->fixedRotateZ);
|
||||
booleanParameters.push_back(midiEnabled);
|
||||
|
||||
for (auto parameter : booleanParameters) {
|
||||
addParameter(parameter);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
synth.addVoice(new ShapeVoice(*this));
|
||||
}
|
||||
|
||||
synth.addSound(defaultSound);
|
||||
}
|
||||
|
||||
OscirenderAudioProcessor::~OscirenderAudioProcessor() {}
|
||||
|
@ -194,6 +199,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() {
|
||||
|
@ -240,7 +246,7 @@ void OscirenderAudioProcessor::addLuaSlider() {
|
|||
|
||||
luaEffects.push_back(std::make_shared<Effect>(
|
||||
std::make_shared<LuaEffect>(sliderName, *this),
|
||||
new EffectParameter("Lua " + sliderName, "lua" + sliderName, 0.0, 0.0, 1.0, 0.001, false)
|
||||
new EffectParameter("Lua " + sliderName, "lua" + sliderName, VERSION_HINT, 0.0, 0.0, 1.0, 0.001, false)
|
||||
));
|
||||
|
||||
auto& effect = luaEffects.back();
|
||||
|
@ -304,7 +310,8 @@ void OscirenderAudioProcessor::updateFileBlock(int index, std::shared_ptr<juce::
|
|||
void OscirenderAudioProcessor::addFile(juce::File file) {
|
||||
fileBlocks.push_back(std::make_shared<juce::MemoryBlock>());
|
||||
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());
|
||||
|
||||
openFile(fileBlocks.size() - 1);
|
||||
|
@ -314,7 +321,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<juce::MemoryBlock>());
|
||||
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);
|
||||
|
||||
openFile(fileBlocks.size() - 1);
|
||||
|
@ -324,7 +332,8 @@ void OscirenderAudioProcessor::addFile(juce::String fileName, const char* data,
|
|||
void OscirenderAudioProcessor::addFile(juce::String fileName, std::shared_ptr<juce::MemoryBlock> data) {
|
||||
fileBlocks.push_back(data);
|
||||
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);
|
||||
}
|
||||
|
@ -337,6 +346,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;
|
||||
|
@ -366,18 +376,26 @@ void OscirenderAudioProcessor::openFile(int index) {
|
|||
void OscirenderAudioProcessor::changeCurrentFile(int index) {
|
||||
if (index == -1) {
|
||||
currentFile = -1;
|
||||
producer.setSource(std::make_shared<FileParser>(), -1);
|
||||
changeSound(defaultSound);
|
||||
}
|
||||
if (index < 0 || index >= fileBlocks.size()) {
|
||||
return;
|
||||
}
|
||||
producer.setSource(parsers[index], index);
|
||||
changeSound(sounds[index]);
|
||||
currentFile = index;
|
||||
invalidateFrameBuffer = true;
|
||||
updateLuaValues();
|
||||
updateObjValues();
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::changeSound(ShapeSound::Ptr sound) {
|
||||
synth.clearSounds();
|
||||
synth.addSound(sound);
|
||||
for (int i = 0; i < synth.getNumVoices(); i++) {
|
||||
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
|
||||
voice->updateSound(sound.get());
|
||||
}
|
||||
}
|
||||
|
||||
int OscirenderAudioProcessor::getCurrentFileIndex() {
|
||||
return currentFile;
|
||||
}
|
||||
|
@ -402,113 +420,42 @@ std::shared_ptr<juce::MemoryBlock> OscirenderAudioProcessor::getFileBlock(int in
|
|||
return fileBlocks[index];
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::addFrame(std::vector<std::unique_ptr<Shape>> 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<float>& buffer, juce::MidiBuffer& midiMessages)
|
||||
{
|
||||
void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& 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());
|
||||
|
||||
|
||||
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;
|
||||
buffer.clear();
|
||||
bool usingMidi = midiEnabled->getBoolValue();
|
||||
if (!usingMidi) {
|
||||
midiMessages.clear();
|
||||
}
|
||||
|
||||
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);
|
||||
// if midi enabled has changed state
|
||||
if (prevMidiEnabled != usingMidi) {
|
||||
for (int i = 1; i <= 16; i++) {
|
||||
midiMessages.addEvent(juce::MidiMessage::allNotesOff(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
// if midi has just been disabled
|
||||
if (prevMidiEnabled && !usingMidi) {
|
||||
midiMessages.addEvent(juce::MidiMessage::noteOn(1, 60, 1.0f), 17);
|
||||
}
|
||||
|
||||
prevMidiEnabled = usingMidi;
|
||||
|
||||
{
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
|
||||
}
|
||||
midiMessages.clear();
|
||||
|
||||
auto* channelData = buffer.getArrayOfWritePointers();
|
||||
|
||||
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 +470,8 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
|||
}
|
||||
}
|
||||
|
||||
x = channels.x;
|
||||
y = channels.y;
|
||||
double x = channels.x;
|
||||
double y = channels.y;
|
||||
|
||||
x *= volume;
|
||||
y *= volume;
|
||||
|
@ -540,58 +487,17 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
|||
channelData[0][sample] = x;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
{
|
||||
juce::SpinLock::ScopedLockType scope(consumerLock);
|
||||
for (auto consumer : consumers) {
|
||||
consumer->write(x);
|
||||
consumer->write(y);
|
||||
consumer->notifyIfFull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -721,6 +627,28 @@ void OscirenderAudioProcessor::setStateInformation(const void* data, int sizeInB
|
|||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferConsumer> OscirenderAudioProcessor::consumerRegister(std::vector<float>& buffer) {
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(buffer);
|
||||
juce::SpinLock::ScopedLockType scope(consumerLock);
|
||||
consumers.push_back(consumer);
|
||||
|
||||
return consumer;
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::consumerRead(std::shared_ptr<BufferConsumer> consumer) {
|
||||
consumer->waitUntilFull();
|
||||
juce::SpinLock::ScopedLockType scope(consumerLock);
|
||||
consumers.erase(std::remove(consumers.begin(), consumers.end(), consumer), consumers.end());
|
||||
}
|
||||
|
||||
void OscirenderAudioProcessor::consumerStop(std::shared_ptr<BufferConsumer> consumer) {
|
||||
if (consumer != nullptr) {
|
||||
juce::SpinLock::ScopedLockType scope(consumerLock);
|
||||
consumer->forceNotify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// This creates new instances of the plugin..
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
|
|
|
@ -10,12 +10,11 @@
|
|||
|
||||
#include <JuceHeader.h>
|
||||
#include "shape/Shape.h"
|
||||
#include "parser/FileParser.h"
|
||||
#include "parser/FrameProducer.h"
|
||||
#include "parser/FrameConsumer.h"
|
||||
#include "concurrency/BufferConsumer.h"
|
||||
#include "audio/Effect.h"
|
||||
#include "audio/ShapeSound.h"
|
||||
#include "audio/ShapeVoice.h"
|
||||
#include <numbers>
|
||||
#include "concurrency/BufferProducer.h"
|
||||
#include "audio/AudioWebSocketServer.h"
|
||||
#include "audio/DelayEffect.h"
|
||||
#include "audio/PitchDetector.h"
|
||||
|
@ -29,7 +28,6 @@ class OscirenderAudioProcessor : public juce::AudioProcessor
|
|||
#if JucePlugin_Enable_ARA
|
||||
, public juce::AudioProcessorARAExtension
|
||||
#endif
|
||||
, public FrameConsumer
|
||||
{
|
||||
public:
|
||||
OscirenderAudioProcessor();
|
||||
|
@ -60,6 +58,12 @@ public:
|
|||
void changeProgramName(int index, const juce::String& newName) override;
|
||||
void getStateInformation(juce::MemoryBlock& destData) override;
|
||||
void setStateInformation(const void* data, int sizeInBytes) override;
|
||||
std::shared_ptr<BufferConsumer> consumerRegister(std::vector<float>& buffer);
|
||||
void consumerStop(std::shared_ptr<BufferConsumer> consumer);
|
||||
void consumerRead(std::shared_ptr<BufferConsumer> consumer);
|
||||
void setMidiEnabled(bool enabled);
|
||||
|
||||
int VERSION_HINT = 1;
|
||||
|
||||
std::atomic<double> currentSampleRate = 0.0;
|
||||
|
||||
|
@ -71,21 +75,21 @@ public:
|
|||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
frequency = values[0];
|
||||
return input;
|
||||
}, new EffectParameter("Frequency", "frequency", 440.0, 0.0, 12000.0, 0.1)
|
||||
}, new EffectParameter("Frequency", "frequency", VERSION_HINT, 440.0, 0.0, 12000.0, 0.1)
|
||||
);
|
||||
|
||||
std::shared_ptr<Effect> volumeEffect = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
volume = values[0];
|
||||
return input;
|
||||
}, new EffectParameter("Volume", "volume", 1.0, 0.0, 3.0)
|
||||
}, new EffectParameter("Volume", "volume", VERSION_HINT, 1.0, 0.0, 3.0)
|
||||
);
|
||||
|
||||
std::shared_ptr<Effect> thresholdEffect = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
threshold = values[0];
|
||||
return input;
|
||||
}, new EffectParameter("Threshold", "threshold", 1.0, 0.0, 1.0)
|
||||
}, new EffectParameter("Threshold", "threshold", VERSION_HINT, 1.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
std::shared_ptr<Effect> focalLength = std::make_shared<Effect>(
|
||||
|
@ -96,12 +100,12 @@ public:
|
|||
camera->setFocalLength(values[0]);
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Focal length", "objFocalLength", 1.0, 0.0, 2.0)
|
||||
}, new EffectParameter("Focal length", "objFocalLength", VERSION_HINT, 1.0, 0.0, 2.0)
|
||||
);
|
||||
|
||||
BooleanParameter* fixedRotateX = new BooleanParameter("Object Fixed Rotate X", "objFixedRotateX", false);
|
||||
BooleanParameter* fixedRotateY = new BooleanParameter("Object Fixed Rotate Y", "objFixedRotateY", false);
|
||||
BooleanParameter* fixedRotateZ = new BooleanParameter("Object Fixed Rotate Z", "objFixedRotateZ", false);
|
||||
BooleanParameter* fixedRotateX = new BooleanParameter("Object Fixed Rotate X", "objFixedRotateX", VERSION_HINT, false);
|
||||
BooleanParameter* fixedRotateY = new BooleanParameter("Object Fixed Rotate Y", "objFixedRotateY", VERSION_HINT, false);
|
||||
BooleanParameter* fixedRotateZ = new BooleanParameter("Object Fixed Rotate Z", "objFixedRotateZ", VERSION_HINT, false);
|
||||
std::shared_ptr<Effect> rotateX = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
if (getCurrentFileIndex() != -1) {
|
||||
|
@ -115,7 +119,7 @@ public:
|
|||
}
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Rotate X", "objRotateX", 1.0, -1.0, 1.0)
|
||||
}, new EffectParameter("Rotate X", "objRotateX", VERSION_HINT, 1.0, -1.0, 1.0)
|
||||
);
|
||||
std::shared_ptr<Effect> rotateY = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
|
@ -130,7 +134,7 @@ public:
|
|||
}
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Rotate Y", "objRotateY", 1.0, -1.0, 1.0)
|
||||
}, new EffectParameter("Rotate Y", "objRotateY", VERSION_HINT, 1.0, -1.0, 1.0)
|
||||
);
|
||||
std::shared_ptr<Effect> rotateZ = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
|
@ -145,7 +149,7 @@ public:
|
|||
}
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Rotate Z", "objRotateZ", 0.0, -1.0, 1.0)
|
||||
}, new EffectParameter("Rotate Z", "objRotateZ", VERSION_HINT, 0.0, -1.0, 1.0)
|
||||
);
|
||||
std::shared_ptr<Effect> rotateSpeed = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
|
@ -155,25 +159,41 @@ public:
|
|||
obj->setRotationSpeed(values[0]);
|
||||
}
|
||||
return input;
|
||||
}, new EffectParameter("Rotate Speed", "objRotateSpeed", 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<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>();
|
||||
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>(VERSION_HINT);
|
||||
|
||||
BooleanParameter* midiEnabled = new BooleanParameter("MIDI Enabled", "midiEnabled", VERSION_HINT, false);
|
||||
std::atomic<float> frequency = 440.0f;
|
||||
|
||||
juce::SpinLock parsersLock;
|
||||
std::vector<std::shared_ptr<FileParser>> parsers;
|
||||
std::vector<ShapeSound::Ptr> sounds;
|
||||
std::vector<std::shared_ptr<juce::MemoryBlock>> fileBlocks;
|
||||
std::vector<juce::String> fileNames;
|
||||
std::atomic<int> currentFile = -1;
|
||||
|
||||
juce::ChangeBroadcaster broadcaster;
|
||||
|
||||
private:
|
||||
juce::SpinLock consumerLock;
|
||||
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
||||
public:
|
||||
|
||||
FrameProducer producer = FrameProducer(*this, std::make_shared<FileParser>());
|
||||
|
||||
BufferProducer audioProducer;
|
||||
|
||||
PitchDetector pitchDetector{audioProducer};
|
||||
PitchDetector pitchDetector{*this};
|
||||
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
|
||||
|
||||
// shouldn't be accessed by audio thread, but needs to persist when GUI is closed
|
||||
|
@ -184,7 +204,6 @@ public:
|
|||
juce::Font font = juce::Font(juce::Font::getDefaultSansSerifFontName(), 1.0f, juce::Font::plain);
|
||||
|
||||
void addLuaSlider();
|
||||
void addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) override;
|
||||
void updateEffectPrecedence();
|
||||
void updateFileBlock(int index, std::shared_ptr<juce::MemoryBlock> block);
|
||||
void addFile(juce::File file);
|
||||
|
@ -200,54 +219,20 @@ public:
|
|||
juce::String getFileName(int index);
|
||||
std::shared_ptr<juce::MemoryBlock> getFileBlock(int index);
|
||||
private:
|
||||
std::atomic<float> frequency = 440.0f;
|
||||
std::atomic<double> volume = 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;
|
||||
|
||||
bool prevMidiEnabled = !midiEnabled->getBoolValue();
|
||||
|
||||
std::vector<BooleanParameter*> booleanParameters;
|
||||
std::vector<std::shared_ptr<Effect>> allEffects;
|
||||
std::vector<std::shared_ptr<Effect>> permanentEffects;
|
||||
|
||||
std::shared_ptr<Effect> traceMax = std::make_shared<Effect>(
|
||||
[this](int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
traceMaxValue = values[0];
|
||||
traceMaxEnabled = true;
|
||||
return input;
|
||||
}, new EffectParameter("Trace max", "traceMax", 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", 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;
|
||||
ShapeSound::Ptr defaultSound = new ShapeSound(std::make_shared<FileParser>());
|
||||
juce::Synthesiser synth;
|
||||
|
||||
AudioWebSocketServer softwareOscilloscopeServer{audioProducer};
|
||||
AudioWebSocketServer softwareOscilloscopeServer{*this};
|
||||
|
||||
void updateFrame();
|
||||
void updateLengthIncrement();
|
||||
void incrementShapeDrawing();
|
||||
void updateLuaValues();
|
||||
void updateObjValues();
|
||||
std::shared_ptr<Effect> getEffect(juce::String id);
|
||||
|
@ -256,6 +241,7 @@ private:
|
|||
std::pair<std::shared_ptr<Effect>, EffectParameter*> effectFromLegacyId(const juce::String& id, bool updatePrecedence = false);
|
||||
LfoType lfoTypeFromLegacyAnimationType(const juce::String& type);
|
||||
double valueFromLegacy(double value, const juce::String& id);
|
||||
void changeSound(ShapeSound::Ptr sound);
|
||||
|
||||
const double MIN_LENGTH_INCREMENT = 0.000001;
|
||||
|
||||
|
|
|
@ -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)
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
#include "AudioWebSocketServer.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
AudioWebSocketServer::AudioWebSocketServer(BufferProducer& producer) : juce::Thread("AudioWebSocketServer"), producer(producer) {
|
||||
AudioWebSocketServer::AudioWebSocketServer(OscirenderAudioProcessor& audioProcessor) : juce::Thread("AudioWebSocketServer"), audioProcessor(audioProcessor) {
|
||||
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
|
||||
// The ConnectionState object contains information about the connection,
|
||||
// at this point only the client ip address and the port.
|
||||
|
@ -40,18 +41,17 @@ AudioWebSocketServer::AudioWebSocketServer(BufferProducer& producer) : juce::Thr
|
|||
AudioWebSocketServer::~AudioWebSocketServer() {
|
||||
server.stop();
|
||||
ix::uninitNetSystem();
|
||||
producer.unregisterConsumer(consumer);
|
||||
audioProcessor.consumerStop(consumer);
|
||||
stopThread(1000);
|
||||
}
|
||||
|
||||
void AudioWebSocketServer::run() {
|
||||
producer.registerConsumer(consumer);
|
||||
|
||||
while (!threadShouldExit()) {
|
||||
auto floatBuffer = consumer->startProcessing();
|
||||
consumer = audioProcessor.consumerRegister(floatBuffer);
|
||||
audioProcessor.consumerRead(consumer);
|
||||
|
||||
for (int i = 0; i < floatBuffer->size(); i++) {
|
||||
short sample = floatBuffer->at(i) * 32767;
|
||||
for (int i = 0; i < floatBuffer.size(); i++) {
|
||||
short sample = floatBuffer[i] * 32767;
|
||||
char b0 = sample & 0xff;
|
||||
char b1 = (sample >> 8) & 0xff;
|
||||
buffer[2 * i] = b0;
|
||||
|
@ -62,8 +62,6 @@ void AudioWebSocketServer::run() {
|
|||
ix::IXWebSocketSendData data{buffer, sizeof(buffer)};
|
||||
client->sendBinary(data);
|
||||
}
|
||||
|
||||
consumer->finishedProcessing();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../ixwebsocket/IXWebSocketServer.h"
|
||||
#include "../concurrency/BufferProducer.h"
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
|
||||
class OscirenderAudioProcessor;
|
||||
class AudioWebSocketServer : juce::Thread {
|
||||
public:
|
||||
AudioWebSocketServer(BufferProducer& producer);
|
||||
AudioWebSocketServer(OscirenderAudioProcessor& audioProcessor);
|
||||
~AudioWebSocketServer();
|
||||
|
||||
void run() override;
|
||||
private:
|
||||
ix::WebSocketServer server{ 42988 };
|
||||
|
||||
BufferProducer& producer;
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(4096);
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::vector<float> floatBuffer = std::vector<float>(2 * 4096);
|
||||
char buffer[4096 * 2 * 2];
|
||||
};
|
||||
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
};
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
BitCrushEffect::BitCrushEffect() {}
|
||||
|
||||
BitCrushEffect::~BitCrushEffect() {}
|
||||
|
||||
// algorithm from https://www.kvraudio.com/forum/viewtopic.php?t=163880
|
||||
Vector2 BitCrushEffect::apply(int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
double value = values[0];
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
class BitCrushEffect : public EffectApplication {
|
||||
public:
|
||||
BitCrushEffect();
|
||||
~BitCrushEffect();
|
||||
|
||||
Vector2 apply(int index, Vector2 input, const std::vector<double>& values, double sampleRate) override;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
class BooleanParameter : public juce::AudioProcessorParameterWithID {
|
||||
public:
|
||||
BooleanParameter(juce::String name, juce::String id, bool value) : AudioProcessorParameterWithID(id, name), value(value) {}
|
||||
BooleanParameter(juce::String name, juce::String id, int versionHint, bool value) : AudioProcessorParameterWithID(juce::ParameterID(id, versionHint), name), value(value) {}
|
||||
|
||||
juce::String getName(int maximumStringLength) const override {
|
||||
return name.substring(0, maximumStringLength);
|
||||
|
@ -86,4 +86,4 @@ public:
|
|||
|
||||
private:
|
||||
std::atomic<bool> value = false;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -33,47 +33,47 @@ void Effect::animateValues() {
|
|||
float percentage = phase / (2 * std::numbers::pi);
|
||||
LfoType type = lfoEnabled ? (LfoType)(int)parameter->lfo->getValueUnnormalised() : LfoType::Static;
|
||||
|
||||
switch (type) {
|
||||
case LfoType::Sine:
|
||||
actualValues[i] = std::sin(phase) * 0.5 + 0.5;
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Square:
|
||||
actualValues[i] = (percentage < 0.5) ? maxValue : minValue;
|
||||
break;
|
||||
case LfoType::Seesaw:
|
||||
// modified sigmoid function
|
||||
actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2;
|
||||
actualValues[i] = 1 / (1 + std::exp(-16 * (actualValues[i] - 0.5)));
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Triangle:
|
||||
actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2;
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Sawtooth:
|
||||
actualValues[i] = percentage * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::ReverseSawtooth:
|
||||
actualValues[i] = (1 - percentage) * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Noise:
|
||||
actualValues[i] = ((float)rand() / RAND_MAX) * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
switch (type) {
|
||||
case LfoType::Sine:
|
||||
actualValues[i] = std::sin(phase) * 0.5 + 0.5;
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Square:
|
||||
actualValues[i] = (percentage < 0.5) ? maxValue : minValue;
|
||||
break;
|
||||
case LfoType::Seesaw:
|
||||
// modified sigmoid function
|
||||
actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2;
|
||||
actualValues[i] = 1 / (1 + std::exp(-16 * (actualValues[i] - 0.5)));
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Triangle:
|
||||
actualValues[i] = (percentage < 0.5) ? percentage * 2 : (1 - percentage) * 2;
|
||||
actualValues[i] = actualValues[i] * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Sawtooth:
|
||||
actualValues[i] = percentage * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::ReverseSawtooth:
|
||||
actualValues[i] = (1 - percentage) * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
case LfoType::Noise:
|
||||
actualValues[i] = ((float)rand() / RAND_MAX) * (maxValue - minValue) + minValue;
|
||||
break;
|
||||
default:
|
||||
double weight = parameter->smoothValueChange ? 0.0005 : 1.0;
|
||||
actualValues[i] = (1.0 - weight) * actualValues[i] + weight * parameter->getValueUnnormalised();
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should only be the audio thread calling this, but either way it's not a big deal
|
||||
float Effect::nextPhase(EffectParameter* parameter) {
|
||||
parameter->phase += parameter->lfoRate->getValueUnnormalised() / sampleRate;
|
||||
parameter->phase = parameter->phase + parameter->lfoRate->getValueUnnormalised() / sampleRate;
|
||||
|
||||
if (parameter->phase > 1) {
|
||||
parameter->phase -= 1;
|
||||
parameter->phase = parameter->phase - 1;
|
||||
}
|
||||
|
||||
return parameter->phase * 2 * std::numbers::pi;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -137,7 +147,7 @@ void Effect::markEnableable(bool enable) {
|
|||
if (enabled != nullptr) {
|
||||
enabled->setValue(enable);
|
||||
} else {
|
||||
enabled = new BooleanParameter(getName() + " Enabled", getId() + "Enabled", enable);
|
||||
enabled = new BooleanParameter(getName() + " Enabled", getId() + "Enabled", parameters[0]->getVersionHint(), enable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,14 +160,14 @@ juce::String Effect::getName() {
|
|||
}
|
||||
|
||||
void Effect::save(juce::XmlElement* xml) {
|
||||
if (enabled != nullptr) {
|
||||
auto enabledXml = xml->createNewChildElement("enabled");
|
||||
enabled->save(enabledXml);
|
||||
}
|
||||
xml->setAttribute("id", getId());
|
||||
xml->setAttribute("precedence", precedence);
|
||||
for (auto parameter : parameters) {
|
||||
parameter->save(xml->createNewChildElement("parameter"));
|
||||
if (enabled != nullptr) {
|
||||
auto enabledXml = xml->createNewChildElement("enabled");
|
||||
enabled->save(enabledXml);
|
||||
}
|
||||
xml->setAttribute("id", getId());
|
||||
xml->setAttribute("precedence", precedence);
|
||||
for (auto parameter : parameters) {
|
||||
parameter->save(xml->createNewChildElement("parameter"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
#include "EffectApplication.h"
|
||||
#include <numbers>
|
||||
#include "../MathUtil.h"
|
||||
|
||||
void EffectApplication::resetPhase() {
|
||||
phase = 0.0;
|
||||
}
|
||||
|
||||
double EffectApplication::nextPhase(double frequency, double sampleRate) {
|
||||
phase += frequency / sampleRate;
|
||||
phase += 2 * std::numbers::pi * frequency / sampleRate;
|
||||
phase = MathUtil::wrapAngle(phase);
|
||||
|
||||
if (phase > 1) {
|
||||
phase -= 1;
|
||||
}
|
||||
|
||||
return phase * 2 * std::numbers::pi;
|
||||
return phase;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
class FloatParameter : public juce::AudioProcessorParameterWithID {
|
||||
public:
|
||||
std::atomic<float> min = 0.0;
|
||||
std::atomic<float> max = 1.0;
|
||||
std::atomic<float> step = 0.001;
|
||||
std::atomic<float> max = 0.0;
|
||||
std::atomic<float> step = 0.0;
|
||||
|
||||
FloatParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, juce::String label = "") : juce::AudioProcessorParameterWithID(id, name), value(value), min(min), max(max), step(step), label(label) {}
|
||||
FloatParameter(juce::String name, juce::String id, int versionHint, float value, float min, float max, float step = 0.69, juce::String label = "") : juce::AudioProcessorParameterWithID(juce::ParameterID(id, versionHint), name), step(step), value(value), label(label) {
|
||||
// need to initialise here because of naming conflicts on Windows
|
||||
this->min = min;
|
||||
this->max = max;
|
||||
}
|
||||
|
||||
juce::String getName(int maximumStringLength) const override {
|
||||
return name.substring(0, maximumStringLength);
|
||||
|
@ -131,7 +135,11 @@ public:
|
|||
std::atomic<int> min = 0;
|
||||
std::atomic<int> max = 10;
|
||||
|
||||
IntParameter(juce::String name, juce::String id, int value, int min, int max) : AudioProcessorParameterWithID(id, name), value(value), min(min), max(max) {}
|
||||
IntParameter(juce::String name, juce::String id, int versionHint, int value, int min, int max) : AudioProcessorParameterWithID(juce::ParameterID(id, versionHint), name), value(value) {
|
||||
// need to initialise here because of naming conflicts on Windows
|
||||
this->min = min;
|
||||
this->max = max;
|
||||
}
|
||||
|
||||
juce::String getName(int maximumStringLength) const override {
|
||||
return name.substring(0, maximumStringLength);
|
||||
|
@ -237,7 +245,7 @@ enum class LfoType : int {
|
|||
|
||||
class LfoTypeParameter : public IntParameter {
|
||||
public:
|
||||
LfoTypeParameter(juce::String name, juce::String id, int value) : IntParameter(name, id, value, 1, 8) {}
|
||||
LfoTypeParameter(juce::String name, juce::String id, int versionHint, int value) : IntParameter(name, id, versionHint, value, 1, 8) {}
|
||||
|
||||
juce::String getText(float value, int maximumStringLength) const override {
|
||||
switch ((LfoType)(int)getUnnormalisedValue(value)) {
|
||||
|
@ -298,8 +306,8 @@ public:
|
|||
class EffectParameter : public FloatParameter {
|
||||
public:
|
||||
std::atomic<bool> smoothValueChange = true;
|
||||
LfoTypeParameter* lfo = new LfoTypeParameter(name + " LFO", paramID + "Lfo", 1);
|
||||
FloatParameter* lfoRate = new FloatParameter(name + " LFO Rate", paramID + "LfoRate", 1.0f, 0.0f, 100.0f, 0.1f, "Hz");
|
||||
LfoTypeParameter* lfo = new LfoTypeParameter(name + " LFO", paramID + "Lfo", getVersionHint(), 1);
|
||||
FloatParameter* lfoRate = new FloatParameter(name + " LFO Rate", paramID + "LfoRate", getVersionHint(), 1.0f, 0.0f, 100.0f, 0.1f, "Hz");
|
||||
std::atomic<float> phase = 0.0f;
|
||||
|
||||
std::vector<juce::AudioProcessorParameter*> getParameters() {
|
||||
|
@ -341,5 +349,5 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
EffectParameter(juce::String name, juce::String id, float value, float min, float max, float step = 0.001, bool smoothValueChange = true) : FloatParameter(name, id, value, min, max, step), smoothValueChange(smoothValueChange) {}
|
||||
};
|
||||
EffectParameter(juce::String name, juce::String id, int versionHint, float value, float min, float max, float step = 0.01, bool smoothValueChange = true) : FloatParameter(name, id, versionHint, value, min, max, step), smoothValueChange(smoothValueChange) {}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
#include "PerspectiveEffect.h"
|
||||
#include <numbers>
|
||||
#include "../MathUtil.h"
|
||||
|
||||
PerspectiveEffect::PerspectiveEffect() {}
|
||||
PerspectiveEffect::PerspectiveEffect(int versionHint) : versionHint(versionHint) {
|
||||
fixedRotateX = new BooleanParameter("Perspective Fixed Rotate X", "perspectiveFixedRotateX", versionHint, false);
|
||||
fixedRotateY = new BooleanParameter("Perspective Fixed Rotate Y", "perspectiveFixedRotateY", versionHint, false);
|
||||
fixedRotateZ = new BooleanParameter("Perspective Fixed Rotate Z", "perspectiveFixedRotateZ", versionHint, false);
|
||||
}
|
||||
|
||||
Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector<double>& values, double sampleRate) {
|
||||
auto effectScale = values[0];
|
||||
|
@ -27,19 +32,9 @@ Vector2 PerspectiveEffect::apply(int index, Vector2 input, const std::vector<dou
|
|||
baseRotateZ = values[5] * std::numbers::pi;
|
||||
}
|
||||
|
||||
currentRotateX += baseRotateX * rotateSpeed;
|
||||
currentRotateY += baseRotateY * rotateSpeed;
|
||||
currentRotateZ += baseRotateZ * rotateSpeed;
|
||||
|
||||
if (currentRotateX > std::numbers::pi * 8) {
|
||||
currentRotateX -= std::numbers::pi * 8;
|
||||
}
|
||||
if (currentRotateY > std::numbers::pi * 8) {
|
||||
currentRotateY -= std::numbers::pi * 8;
|
||||
}
|
||||
if (currentRotateZ > std::numbers::pi * 8) {
|
||||
currentRotateZ -= std::numbers::pi * 8;
|
||||
}
|
||||
currentRotateX = MathUtil::wrapAngle(currentRotateX + baseRotateX * rotateSpeed);
|
||||
currentRotateY = MathUtil::wrapAngle(currentRotateY + baseRotateY * rotateSpeed);
|
||||
currentRotateZ = MathUtil::wrapAngle(currentRotateZ + baseRotateZ * rotateSpeed);
|
||||
|
||||
auto x = input.x;
|
||||
auto y = input.y;
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
|
||||
class PerspectiveEffect : public EffectApplication {
|
||||
public:
|
||||
PerspectiveEffect();
|
||||
PerspectiveEffect(int versionHint);
|
||||
|
||||
Vector2 apply(int index, Vector2 input, const std::vector<double>& values, double sampleRate) override;
|
||||
void updateCode(const juce::String& newCode);
|
||||
juce::String getCode();
|
||||
|
||||
BooleanParameter* fixedRotateX = new BooleanParameter("Perspective Fixed Rotate X", "perspectiveFixedRotateX", false);
|
||||
BooleanParameter* fixedRotateY = new BooleanParameter("Perspective Fixed Rotate Y", "perspectiveFixedRotateY", false);
|
||||
BooleanParameter* fixedRotateZ = new BooleanParameter("Perspective Fixed Rotate Z", "perspectiveFixedRotateZ", false);
|
||||
BooleanParameter* fixedRotateX;
|
||||
BooleanParameter* fixedRotateY;
|
||||
BooleanParameter* fixedRotateZ;
|
||||
private:
|
||||
const juce::String DEFAULT_SCRIPT = "return { x, y, z }";
|
||||
juce::String code = DEFAULT_SCRIPT;
|
||||
|
@ -25,8 +25,14 @@ private:
|
|||
float currentRotateX = 0;
|
||||
float currentRotateY = 0;
|
||||
float currentRotateZ = 0;
|
||||
|
||||
int versionHint;
|
||||
|
||||
float linearSpeedToActualSpeed(float rotateSpeed) {
|
||||
return (std::exp(3 * juce::jmin(10.0f, std::abs(rotateSpeed))) - 1) / 50000.0;
|
||||
}
|
||||
};
|
||||
double linearSpeedToActualSpeed(double rotateSpeed) {
|
||||
double actualSpeed = (std::exp(3 * std::min(10.0, std::abs(rotateSpeed))) - 1) / 50000;
|
||||
if (rotateSpeed < 0) {
|
||||
actualSpeed *= -1;
|
||||
}
|
||||
return actualSpeed;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
#include "PitchDetector.h"
|
||||
#include "PitchDetector.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
PitchDetector::PitchDetector(BufferProducer& producer) : juce::Thread("PitchDetector"), producer(producer) {
|
||||
PitchDetector::PitchDetector(OscirenderAudioProcessor& audioProcessor) : juce::Thread("PitchDetector"), audioProcessor(audioProcessor) {
|
||||
startThread();
|
||||
}
|
||||
|
||||
PitchDetector::~PitchDetector() {
|
||||
producer.unregisterConsumer(consumer);
|
||||
audioProcessor.consumerStop(consumer);
|
||||
stopThread(1000);
|
||||
}
|
||||
|
||||
void PitchDetector::run() {
|
||||
producer.registerConsumer(consumer);
|
||||
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
consumer = audioProcessor.consumerRegister(buffer);
|
||||
audioProcessor.consumerRead(consumer);
|
||||
|
||||
// buffer is for 2 channels, so we need to only use one
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
fftData[i] = buffer->at(2 * i);
|
||||
fftData[i] = buffer[2 * i];
|
||||
}
|
||||
|
||||
forwardFFT.performFrequencyOnlyForwardTransform(fftData.data());
|
||||
|
@ -37,8 +36,6 @@ void PitchDetector::run() {
|
|||
}
|
||||
|
||||
frequency = frequencyFromIndex(maxIndex);
|
||||
|
||||
consumer->finishedProcessing();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
#include "../concurrency/BufferProducer.h"
|
||||
|
||||
|
||||
class OscirenderAudioProcessor;
|
||||
class PitchDetector : public juce::Thread, public juce::AsyncUpdater {
|
||||
public:
|
||||
PitchDetector(BufferProducer& producer);
|
||||
PitchDetector(OscirenderAudioProcessor& audioProcessor);
|
||||
~PitchDetector();
|
||||
|
||||
void run() override;
|
||||
|
@ -21,10 +20,11 @@ private:
|
|||
static constexpr int fftOrder = 15;
|
||||
static constexpr int fftSize = 1 << fftOrder;
|
||||
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(fftSize);
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
std::vector<float> buffer = std::vector<float>(2 * fftSize);
|
||||
juce::dsp::FFT forwardFFT{fftOrder};
|
||||
std::array<float, fftSize * 2> fftData;
|
||||
BufferProducer& producer;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::vector<std::function<void(float)>> callbacks;
|
||||
juce::SpinLock lock;
|
||||
float sampleRate = 192000.0f;
|
||||
|
@ -32,4 +32,4 @@ private:
|
|||
float frequencyFromIndex(int index);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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,168 @@
|
|||
#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);
|
||||
|
||||
currentlyPlaying = true;
|
||||
this->sound = shapeSound;
|
||||
if (shapeSound != nullptr) {
|
||||
int tries = 0;
|
||||
while (frame.empty() && tries < 50) {
|
||||
frameLength = shapeSound->updateFrame(frame);
|
||||
tries++;
|
||||
}
|
||||
tailOff = 0.0;
|
||||
if (audioProcessor.midiEnabled->getBoolValue()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// should be called if the current file is changed so that we interrupt
|
||||
// any currently playing sounds / voices
|
||||
void ShapeVoice::updateSound(juce::SynthesiserSound* sound) {
|
||||
if (currentlyPlaying) {
|
||||
this->sound = dynamic_cast<ShapeSound*>(sound);
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) {
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
|
||||
int numChannels = outputBuffer.getNumChannels();
|
||||
|
||||
if (!audioProcessor.midiEnabled->getBoolValue()) {
|
||||
frequency = audioProcessor.frequency;
|
||||
}
|
||||
|
||||
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.load() != nullptr) {
|
||||
renderingSample = sound.load()->parser->isSample();
|
||||
|
||||
if (renderingSample) {
|
||||
channels = sound.load()->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.load() != nullptr) {
|
||||
frameLength = sound.load()->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) {
|
||||
currentlyPlaying = false;
|
||||
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,39 @@
|
|||
#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 updateSound(juce::SynthesiserSound* sound);
|
||||
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;
|
||||
std::atomic<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;
|
||||
|
||||
bool currentlyPlaying = false;
|
||||
double tailOff = 0.0;
|
||||
double frequency = 1.0;
|
||||
};
|
|
@ -12,8 +12,10 @@ Graph::Graph(int n, list< pair<int, int> > & edges):
|
|||
{
|
||||
int u = (*it).first;
|
||||
int v = (*it).second;
|
||||
|
||||
AddEdge(u, v);
|
||||
|
||||
if (v < n && u < n) {
|
||||
AddEdge(u, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,16 +116,16 @@ EffectComponent::~EffectComponent() {
|
|||
effect.removeListener(index, this);
|
||||
}
|
||||
|
||||
void EffectComponent::resized() {
|
||||
auto bounds = getLocalBounds();
|
||||
auto componentBounds = bounds.removeFromRight(25);
|
||||
if (component != nullptr) {
|
||||
component->setBounds(componentBounds);
|
||||
}
|
||||
|
||||
if (lfoEnabled) {
|
||||
lfo.setBounds(bounds.removeFromRight(100).reduced(5));
|
||||
}
|
||||
void EffectComponent::resized() {
|
||||
auto bounds = getLocalBounds();
|
||||
auto componentBounds = bounds.removeFromRight(25);
|
||||
if (component != nullptr) {
|
||||
component->setBounds(componentBounds);
|
||||
}
|
||||
|
||||
if (lfoEnabled) {
|
||||
lfo.setBounds(bounds.removeFromRight(100).reduced(5));
|
||||
}
|
||||
|
||||
auto checkboxLabel = bounds.removeFromLeft(120);
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ int LuaListBoxModel::getNumRows() {
|
|||
void LuaListBoxModel::paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) {}
|
||||
|
||||
juce::Component* LuaListBoxModel::refreshComponentForRow(int rowNum, bool isRowSelected, juce::Component *existingComponentToUpdate) {
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
// TODO: We should REALLY be locking here but it causes a deadlock :( works fine without.....
|
||||
// juce::SpinLock::ScopedLockType lock1(audioProcessor.effectsLock);
|
||||
std::unique_ptr<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate));
|
||||
if (juce::isPositiveAndBelow(rowNum, getNumRows())) {
|
||||
item = std::make_unique<LuaListComponent>(audioProcessor, *audioProcessor.luaEffects[rowNum]);
|
||||
|
|
|
@ -7,7 +7,7 @@ VisualiserComponent::VisualiserComponent(int numChannels, OscirenderAudioProcess
|
|||
}
|
||||
|
||||
VisualiserComponent::~VisualiserComponent() {
|
||||
audioProcessor.audioProducer.unregisterConsumer(consumer);
|
||||
audioProcessor.consumerStop(consumer);
|
||||
stopThread(1000);
|
||||
}
|
||||
|
||||
|
@ -44,12 +44,10 @@ void VisualiserComponent::timerCallback() {
|
|||
}
|
||||
|
||||
void VisualiserComponent::run() {
|
||||
audioProcessor.audioProducer.registerConsumer(consumer);
|
||||
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
setBuffer(*buffer);
|
||||
consumer->finishedProcessing();
|
||||
consumer = audioProcessor.consumerRegister(tempBuffer);
|
||||
audioProcessor.consumerRead(consumer);
|
||||
setBuffer(tempBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,10 @@ private:
|
|||
int numChannels = 2;
|
||||
juce::Colour backgroundColour, waveformColour;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(4096);
|
||||
std::vector<float> tempBuffer = std::vector<float>(2 * 4096);
|
||||
int precision = 4;
|
||||
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VisualiserComponent)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ VolumeComponent::VolumeComponent(OscirenderAudioProcessor& p) : audioProcessor(p
|
|||
}
|
||||
|
||||
VolumeComponent::~VolumeComponent() {
|
||||
audioProcessor.audioProducer.unregisterConsumer(consumer);
|
||||
audioProcessor.consumerStop(consumer);
|
||||
stopThread(1000);
|
||||
}
|
||||
|
||||
|
@ -92,29 +92,26 @@ void VolumeComponent::timerCallback() {
|
|||
}
|
||||
|
||||
void VolumeComponent::run() {
|
||||
audioProcessor.audioProducer.registerConsumer(consumer);
|
||||
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
consumer = audioProcessor.consumerRegister(buffer);
|
||||
audioProcessor.consumerRead(consumer);
|
||||
|
||||
float leftVolume = 0;
|
||||
float rightVolume = 0;
|
||||
|
||||
for (int i = 0; i < buffer->size(); i += 2) {
|
||||
leftVolume += buffer->at(i) * buffer->at(i);
|
||||
rightVolume += buffer->at(i + 1) * buffer->at(i + 1);
|
||||
for (int i = 0; i < buffer.size(); i += 2) {
|
||||
leftVolume += buffer[i] * buffer[i];
|
||||
rightVolume += buffer[i + 1] * buffer[i + 1];
|
||||
}
|
||||
// RMS
|
||||
leftVolume = std::sqrt(leftVolume / (buffer->size() / 2));
|
||||
rightVolume = std::sqrt(rightVolume / (buffer->size() / 2));
|
||||
leftVolume = std::sqrt(leftVolume / (buffer.size() / 2));
|
||||
rightVolume = std::sqrt(rightVolume / (buffer.size() / 2));
|
||||
|
||||
this->leftVolume = leftVolume;
|
||||
this->rightVolume = rightVolume;
|
||||
|
||||
avgLeftVolume = (avgLeftVolume * 0.95) + (leftVolume * 0.05);
|
||||
avgRightVolume = (avgRightVolume * 0.95) + (rightVolume * 0.05);
|
||||
|
||||
consumer->finishedProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
#include "../PluginProcessor.h"
|
||||
#include "../LookAndFeel.h"
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
|
||||
class ThumbRadiusLookAndFeel : public OscirenderLookAndFeel {
|
||||
public:
|
||||
|
@ -71,7 +71,7 @@ public:
|
|||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(1 << 11);
|
||||
std::vector<float> buffer = std::vector<float>(2 * 1 << 11);
|
||||
|
||||
std::atomic<float> leftVolume = 0;
|
||||
std::atomic<float> rightVolume = 0;
|
||||
|
@ -85,6 +85,8 @@ private:
|
|||
|
||||
std::unique_ptr<juce::Drawable> volumeIcon;
|
||||
std::unique_ptr<juce::Drawable> thresholdIcon;
|
||||
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VolumeComponent)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,123 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
// This is a helper class for the producer and consumer threads.
|
||||
//
|
||||
// ORDER OF OPERATIONS:
|
||||
// 1. Consumer is created.
|
||||
// 2. The thread that owns the consumers calls registerConsumer() on the producer, which acquires the lock on the first buffer to be written to by calling getBuffer().
|
||||
// LOOP:
|
||||
// 3. The consumer calls startProcessing() to signal that they want to start processing the current buffer.
|
||||
// 4. The producer calls finishedWriting() to signal that they have finished writing to the current buffer, which gives the lock to the consumer.
|
||||
// 5. The consumer calls finishedProcessing() to signal that they have finished processing the current buffer.
|
||||
// 6. The producer calls getBuffer() to acquire the lock on the next buffer to be written to.
|
||||
// GOTO LOOP
|
||||
// 7. The thread that owns the consumer calls unregisterConsumer() on the producer at some point during the loop, which releases the lock on the current buffer.
|
||||
//
|
||||
class BufferConsumer {
|
||||
// FROM https://gist.github.com/Kuxe/6bdd5b748b5f11b303a5cccbb8c8a731
|
||||
/** General semaphore with N permissions **/
|
||||
class Semaphore {
|
||||
const size_t num_permissions;
|
||||
size_t avail;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
public:
|
||||
BufferConsumer(int bufferSize) {
|
||||
firstBuffer->resize(2 * bufferSize, 0.0);
|
||||
secondBuffer->resize(2 * bufferSize, 0.0);
|
||||
/** Default constructor. Default semaphore is a binary semaphore **/
|
||||
explicit Semaphore(const size_t& num_permissions = 1) : num_permissions(num_permissions), avail(num_permissions) { }
|
||||
|
||||
/** Copy constructor. Does not copy state of mutex or condition variable,
|
||||
only the number of permissions and number of available permissions **/
|
||||
Semaphore(const Semaphore& s) : num_permissions(s.num_permissions), avail(s.avail) { }
|
||||
|
||||
void acquire() {
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [this] { return avail > 0; });
|
||||
avail--;
|
||||
lk.unlock();
|
||||
}
|
||||
|
||||
~BufferConsumer() {}
|
||||
|
||||
// Returns the buffer that is ready to be written to.
|
||||
// This is only called by the producer thread.
|
||||
// force forces the lock to be acquired.
|
||||
// Returns nullptr if the lock can't be acquired when force is false.
|
||||
// It is only called when the global producer lock is held.
|
||||
std::shared_ptr<std::vector<float>> getBuffer(bool force) {
|
||||
auto buffer = firstBufferWriting ? firstBuffer : secondBuffer;
|
||||
if (lockHeldForWriting) {
|
||||
return buffer;
|
||||
}
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
|
||||
if (force) {
|
||||
bufferLock->enter();
|
||||
lockHeldForWriting = true;
|
||||
return buffer;
|
||||
} else if (bufferLock->tryEnter()) {
|
||||
lockHeldForWriting = true;
|
||||
return buffer;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
void release() {
|
||||
m.lock();
|
||||
avail++;
|
||||
m.unlock();
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
// This is only called by the producer thread. It is only called when the global
|
||||
// producer lock is held.
|
||||
void finishedWriting() {
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
lockHeldForWriting = false;
|
||||
firstBufferWriting = !firstBufferWriting;
|
||||
// Try locking before we unlock the current buffer so that
|
||||
// the consumer doesn't start processing before we
|
||||
// unlock the buffer. Ignore if we can't get the lock
|
||||
// because the consumer is still processing.
|
||||
getBuffer(false);
|
||||
bufferLock->exit();
|
||||
size_t available() const {
|
||||
return avail;
|
||||
}
|
||||
|
||||
void releaseLock() {
|
||||
if (lockHeldForWriting) {
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
bufferLock->exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the buffer that has been written to fully and is ready to be processed.
|
||||
// This will lock the buffer so that the producer can't write to it while we're processing.
|
||||
std::shared_ptr<std::vector<float>> startProcessing() {
|
||||
auto buffer = firstBufferProcessing ? firstBuffer : secondBuffer;
|
||||
auto bufferLock = firstBufferProcessing ? firstBufferLock : secondBufferLock;
|
||||
|
||||
bufferLock->enter();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// This should be called after processing has finished.
|
||||
// It releases the lock on the buffer so that the producer can write to it again.
|
||||
void finishedProcessing() {
|
||||
auto bufferLock = firstBufferProcessing ? firstBufferLock : secondBufferLock;
|
||||
firstBufferProcessing = !firstBufferProcessing;
|
||||
bufferLock->exit();
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<float>> firstBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<std::vector<float>> secondBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<juce::CriticalSection> firstBufferLock = std::make_shared<juce::CriticalSection>();
|
||||
std::shared_ptr<juce::CriticalSection> secondBufferLock = std::make_shared<juce::CriticalSection>();
|
||||
private:
|
||||
// Indirectly used by the producer to signal whether it holds the lock on the buffer.
|
||||
// This is accurate if the global producer lock is held as the buffer lock is acquired
|
||||
// and this is set to true before the global producer lock is released.
|
||||
bool lockHeldForWriting = false;
|
||||
bool firstBufferWriting = true;
|
||||
bool firstBufferProcessing = true;
|
||||
};
|
||||
|
||||
class DummyConsumer : public juce::Thread {
|
||||
|
||||
class BufferConsumer {
|
||||
public:
|
||||
DummyConsumer(std::shared_ptr<BufferConsumer> consumer) : juce::Thread("DummyConsumer"), consumer(consumer) {}
|
||||
~DummyConsumer() {}
|
||||
BufferConsumer(std::vector<float>& buffer) : buffer(buffer) {}
|
||||
|
||||
void run() override {
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
~BufferConsumer() {}
|
||||
|
||||
void waitUntilFull() {
|
||||
sema.acquire();
|
||||
}
|
||||
|
||||
float total = 0.0;
|
||||
for (int i = 0; i < buffer->size(); i++) {
|
||||
total += (*buffer)[i];
|
||||
}
|
||||
DBG(total);
|
||||
|
||||
consumer->finishedProcessing();
|
||||
void notifyIfFull() {
|
||||
if (offset >= buffer.size()) {
|
||||
sema.release();
|
||||
}
|
||||
}
|
||||
|
||||
// to be used when the audio thread is being destroyed to
|
||||
// make sure that everything waiting on it stops waiting.
|
||||
void forceNotify() {
|
||||
sema.release();
|
||||
}
|
||||
|
||||
void write(double d) {
|
||||
if (offset < buffer.size()) {
|
||||
buffer[offset++] = d;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
};
|
||||
std::vector<float>& buffer;
|
||||
Semaphore sema{0};
|
||||
int offset = 0;
|
||||
};
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "BufferConsumer.h"
|
||||
|
||||
class BufferProducer {
|
||||
public:
|
||||
BufferProducer() {}
|
||||
~BufferProducer() {}
|
||||
|
||||
// This should add the buffers and locks to the vectors
|
||||
// and then lock the first buffer lock so it can start
|
||||
// being written to.
|
||||
// This is only called by the thread that owns the consumer thread.
|
||||
void registerConsumer(std::shared_ptr<BufferConsumer> consumer) {
|
||||
juce::CriticalSection::ScopedLockType l(lock);
|
||||
consumers.push_back(consumer);
|
||||
bufferPositions.push_back(0);
|
||||
consumer->getBuffer(true);
|
||||
}
|
||||
|
||||
// This is only called by the thread that owns the consumer thread.
|
||||
// This can't happen at the same time as write() it locks the producer lock.
|
||||
void unregisterConsumer(std::shared_ptr<BufferConsumer> consumer) {
|
||||
juce::CriticalSection::ScopedLockType l(lock);
|
||||
for (int i = 0; i < consumers.size(); i++) {
|
||||
if (consumers[i] == consumer) {
|
||||
consumer->releaseLock();
|
||||
consumers.erase(consumers.begin() + i);
|
||||
bufferPositions.erase(bufferPositions.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a sample to the current buffer for all consumers.
|
||||
void write(float left, float right) {
|
||||
juce::CriticalSection::ScopedLockType l(lock);
|
||||
for (int i = 0; i < consumers.size(); i++) {
|
||||
std::shared_ptr<std::vector<float>> buffer = consumers[i]->getBuffer(false);
|
||||
if (buffer == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(*buffer)[bufferPositions[i]] = left;
|
||||
(*buffer)[bufferPositions[i] + 1] = right;
|
||||
bufferPositions[i] += 2;
|
||||
|
||||
// If we've reached the end of the buffer, switch
|
||||
// to the other buffer and unlock it. This signals
|
||||
// to the consumer that it can start processing!
|
||||
if (bufferPositions[i] >= buffer->size()) {
|
||||
bufferPositions[i] = 0;
|
||||
consumers[i]->finishedWriting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
juce::CriticalSection lock;
|
||||
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
||||
std::vector<int> bufferPositions;
|
||||
};
|
|
@ -47,8 +47,8 @@ std::vector<Vector2> Camera::sampleVerticesInRender(WorldObject& object) {
|
|||
double z = object.vs[j * 3 + 2];
|
||||
vertices.push_back(project(object.rotateX, object.rotateY, object.rotateZ, x, y, z));
|
||||
}
|
||||
object.rotateY += rotation;
|
||||
object.rotateZ += rotation;
|
||||
object.rotateY = object.rotateY + rotation;
|
||||
object.rotateZ = object.rotateY + rotation;
|
||||
}
|
||||
|
||||
return vertices;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "WorldObject.h"
|
||||
#include "../chinese_postman/ChinesePostman.h"
|
||||
#include "tiny_obj_loader.h"
|
||||
#include "../MathUtil.h"
|
||||
|
||||
struct pair_hash {
|
||||
inline std::size_t operator()(const std::pair<int, int>& v) const {
|
||||
|
@ -238,9 +239,9 @@ void WorldObject::setRotationSpeed(double rotateSpeed) {
|
|||
// called whenever a new frame is drawn, so that the object can update its
|
||||
// rotation
|
||||
void WorldObject::nextFrame() {
|
||||
currentRotateX += baseRotateX * rotateSpeed;
|
||||
currentRotateY += baseRotateY * rotateSpeed;
|
||||
currentRotateZ += baseRotateZ * rotateSpeed;
|
||||
currentRotateX = MathUtil::wrapAngle(currentRotateX + baseRotateX * rotateSpeed);
|
||||
currentRotateY = MathUtil::wrapAngle(currentRotateY + baseRotateY * rotateSpeed);
|
||||
currentRotateZ = MathUtil::wrapAngle(currentRotateZ + baseRotateZ * rotateSpeed);
|
||||
rotateX = baseRotateX + currentRotateX;
|
||||
rotateY = baseRotateY + currentRotateY;
|
||||
rotateZ = baseRotateZ + currentRotateZ;
|
||||
|
|
|
@ -26,4 +26,4 @@ private:
|
|||
std::atomic<double> baseRotateX = 0.0, baseRotateY = 0.0, baseRotateZ = 0.0;
|
||||
std::atomic<double> currentRotateX = 0.0, currentRotateY = 0.0, currentRotateZ = 0.0;
|
||||
std::atomic<double> rotateSpeed;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -37,4 +37,4 @@ private:
|
|||
std::shared_ptr<SvgParser> svg;
|
||||
std::shared_ptr<TextParser> text;
|
||||
std::shared_ptr<LuaParser> lua;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
|
||||
class FrameConsumer {
|
||||
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() {
|
||||
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<FileParser> source, int fileIndex) {
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
frameSource->disable();
|
||||
frameSource = source;
|
||||
sourceFileIndex = fileIndex;
|
||||
}
|
||||
|
|
|
@ -10,10 +10,8 @@ public:
|
|||
~FrameProducer() override;
|
||||
|
||||
void run() override;
|
||||
void setSource(std::shared_ptr<FileParser>, int fileIndex);
|
||||
private:
|
||||
juce::SpinLock lock;
|
||||
FrameConsumer& frameConsumer;
|
||||
std::shared_ptr<FileParser> frameSource;
|
||||
int sourceFileIndex = -1;
|
||||
};
|
|
@ -56,11 +56,11 @@ void CircleArc::translate(double x, double y) {
|
|||
double CircleArc::length() {
|
||||
if (len < 0) {
|
||||
len = 0;
|
||||
double angle = startAngle;
|
||||
double step = (endAngle - startAngle) / 500;
|
||||
for (int i = 0; i < 500; i++) {
|
||||
Vector2 v1 = nextVector(i / 500.0);
|
||||
Vector2 v2 = nextVector((i + 1) / 500.0);
|
||||
// TODO: Replace this, it's stupid. Do a real approximation.
|
||||
int segments = 5;
|
||||
for (int i = 0; i < segments; i++) {
|
||||
Vector2 v1 = nextVector(i / (double) segments);
|
||||
Vector2 v2 = nextVector((i + 1) / (double) segments);
|
||||
len += Line(v1.x, v1.y, v2.x, v2.y).length();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ double Vector2::magnitude() {
|
|||
}
|
||||
|
||||
std::unique_ptr<Shape> Vector2::clone() {
|
||||
return std::unique_ptr<Shape>();
|
||||
return std::make_unique<Vector2>(x, y);
|
||||
}
|
||||
|
||||
std::string Vector2::type() {
|
||||
|
|
|
@ -5,17 +5,26 @@
|
|||
|
||||
SvgParser::SvgParser(juce::String svgFile) {
|
||||
auto doc = juce::XmlDocument::parse(svgFile);
|
||||
std::unique_ptr<juce::Drawable> svg = juce::Drawable::createFromSVG(*doc);
|
||||
juce::DrawableComposite* composite = dynamic_cast<juce::DrawableComposite*>(svg.get());
|
||||
auto contentArea = composite->getContentArea();
|
||||
auto path = svg->getOutlineAsPath();
|
||||
// apply transform to path to get the content area in the bounds -1 to 1
|
||||
path.applyTransform(juce::AffineTransform::translation(-contentArea.getX(), -contentArea.getY()));
|
||||
path.applyTransform(juce::AffineTransform::scale(2 / contentArea.getWidth(), 2 / contentArea.getHeight()));
|
||||
path.applyTransform(juce::AffineTransform::translation(-1, -1));
|
||||
if (doc != nullptr) {
|
||||
std::unique_ptr<juce::Drawable> svg = juce::Drawable::createFromSVG(*doc);
|
||||
juce::DrawableComposite* composite = dynamic_cast<juce::DrawableComposite*>(svg.get());
|
||||
if (composite != nullptr) {
|
||||
auto contentArea = composite->getContentArea();
|
||||
auto path = svg->getOutlineAsPath();
|
||||
// apply transform to path to get the content area in the bounds -1 to 1
|
||||
path.applyTransform(juce::AffineTransform::translation(-contentArea.getX(), -contentArea.getY()));
|
||||
path.applyTransform(juce::AffineTransform::scale(2 / contentArea.getWidth(), 2 / contentArea.getHeight()));
|
||||
path.applyTransform(juce::AffineTransform::translation(-1, -1));
|
||||
|
||||
pathToShapes(path, shapes);
|
||||
Shape::removeOutOfBounds(shapes);
|
||||
pathToShapes(path, shapes);
|
||||
Shape::removeOutOfBounds(shapes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// draw an X to indicate an error.
|
||||
shapes.push_back(std::make_unique<Line>(-0.5, -0.5, 0.5, 0.5));
|
||||
shapes.push_back(std::make_unique<Line>(-0.5, 0.5, 0.5, -0.5));
|
||||
}
|
||||
|
||||
SvgParser::~SvgParser() {}
|
||||
|
@ -69,4 +78,4 @@ std::vector<std::unique_ptr<Shape>> SvgParser::draw() {
|
|||
tempShapes.push_back(shape->clone());
|
||||
}
|
||||
return tempShapes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<JUCERPROJECT id="HH2E72" name="osci-render" projectType="audioplug" useAppConfig="0"
|
||||
addUsingNamespaceToJuceHeader="0" displaySplashScreen="0" jucerFormatVersion="1"
|
||||
addUsingNamespaceToJuceHeader="0" displaySplashScreen="1" jucerFormatVersion="1"
|
||||
pluginCharacteristicsValue="pluginProducesMidiOut,pluginWantsMidiIn"
|
||||
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
|
||||
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include"
|
||||
|
@ -66,6 +66,10 @@
|
|||
<FILE id="PbbNqz" name="RotateEffect.cpp" compile="1" resource="0"
|
||||
file="Source/audio/RotateEffect.cpp"/>
|
||||
<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="Source/audio/SmoothEffect.cpp"/>
|
||||
<FILE id="Vwjht7" name="SmoothEffect.h" compile="0" resource="0" file="Source/audio/SmoothEffect.h"/>
|
||||
|
@ -133,8 +137,6 @@
|
|||
<GROUP id="{9F5970A9-8094-E7F3-7AC1-812AE5589B9F}" name="concurrency">
|
||||
<FILE id="WQ2W15" name="BufferConsumer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferConsumer.h"/>
|
||||
<FILE id="yWTiQQ" name="BufferProducer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferProducer.h"/>
|
||||
</GROUP>
|
||||
<FILE id="I44EdJ" name="EffectsComponent.cpp" compile="1" resource="0"
|
||||
file="Source/EffectsComponent.cpp"/>
|
||||
|
@ -378,6 +380,10 @@
|
|||
<FILE id="GKBQ8j" name="MainComponent.cpp" compile="1" resource="0"
|
||||
file="Source/MainComponent.cpp"/>
|
||||
<FILE id="RU8fGr" name="MainComponent.h" compile="0" resource="0" file="Source/MainComponent.h"/>
|
||||
<FILE id="cFVaxu" name="MathUtil.h" compile="0" resource="0" file="Source/MathUtil.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">
|
||||
<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"/>
|
||||
|
@ -409,6 +415,10 @@
|
|||
file="Source/PluginProcessor.cpp"/>
|
||||
<FILE id="G4mTsK" name="PluginProcessor.h" compile="0" resource="0"
|
||||
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">
|
||||
<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"/>
|
||||
|
|
Ładowanie…
Reference in New Issue