Get standalone oscilloscope working with three channel inputs, and migrate BufferConsumer to use Points rather than raw samples

pull/263/head
James H Ball 2024-09-22 18:49:58 +01:00
rodzic 44b6dea7ba
commit 08ef65c377
23 zmienionych plików z 366 dodań i 222 usunięć

Wyświetl plik

@ -222,69 +222,76 @@
from woscope by e1ml : https://github.com/m1el/woscope --> from woscope by e1ml : https://github.com/m1el/woscope -->
<script id="gaussianVertex" type="x-shader"> <script id="gaussianVertex" type="x-shader">
#define EPS 1E-6 #define EPS 1E-6
uniform float uInvert; uniform float uInvert;
uniform float uSize; uniform float uSize;
uniform float uNEdges; uniform float uNEdges;
uniform float uFadeAmount; uniform float uFadeAmount;
uniform float uIntensity; uniform float uIntensity;
uniform float uGain; uniform float uGain;
attribute vec2 aStart, aEnd; attribute vec3 aStart, aEnd;
attribute float aIdx; attribute float aIdx;
varying vec4 uvl; varying vec4 uvl;
varying vec2 vTexCoord; varying vec2 vTexCoord;
varying float vLen; varying float vLen;
varying float vSize; varying float vSize;
void main () { void main () {
float tang; float tang;
vec2 current; vec2 current;
// All points in quad contain the same data: // All points in quad contain the same data:
// segment start point and segment end point. // segment start point and segment end point.
// We determine point position using its index. // We determine point position using its index.
float idx = mod(aIdx,4.0); float idx = mod(aIdx,4.0);
// `dir` vector is storing the normalized difference vec2 aStartPos = aStart.xy;
// between end and start vec2 aEndPos = aEnd.xy;
vec2 dir = (aEnd-aStart)*uGain; float aStartBrightness = aStart.z;
uvl.z = length(dir); float aEndBrightness = aEnd.z;
if (uvl.z > EPS) // `dir` vector is storing the normalized difference
{ // between end and start
dir = dir / uvl.z; vec2 dir = (aEndPos-aStartPos)*uGain;
vSize = 0.006/pow(uvl.z,0.08); uvl.z = length(dir);
}
else
{
// If the segment is too short, just draw a square
dir = vec2(1.0, 0.0);
vSize = 0.006/pow(EPS,0.08);
}
vSize = uSize; if (uvl.z > EPS)
vec2 norm = vec2(-dir.y, dir.x); {
dir = dir / uvl.z;
vSize = 0.006/pow(uvl.z,0.08);
}
else
{
// If the segment is too short, just draw a square
dir = vec2(1.0, 0.0);
vSize = 0.006/pow(EPS,0.08);
}
if (idx >= 2.0) { vSize = uSize;
current = aEnd*uGain; vec2 norm = vec2(-dir.y, dir.x);
tang = 1.0;
uvl.x = -vSize;
} else {
current = aStart*uGain;
tang = -1.0;
uvl.x = uvl.z + vSize;
}
// `side` corresponds to shift to the "right" or "left"
float side = (mod(idx, 2.0)-0.5)*2.0;
uvl.y = side * vSize;
uvl.w = uIntensity*mix(1.0-uFadeAmount, 1.0, floor(aIdx / 4.0 + 0.5)/uNEdges); if (idx >= 2.0) {
current = aEndPos*uGain;
tang = 1.0;
uvl.x = -vSize;
uvl.w = aEndBrightness;
} else {
current = aStartPos*uGain;
tang = -1.0;
uvl.x = uvl.z + vSize;
uvl.w = aStartBrightness;
}
// `side` corresponds to shift to the "right" or "left"
float side = (mod(idx, 2.0)-0.5)*2.0;
uvl.y = side * vSize;
vec4 pos = vec4((current+(tang*dir+norm*side)*vSize)*uInvert,0.0,1.0); uvl.w *= uIntensity * mix(1.0-uFadeAmount, 1.0, floor(aIdx / 4.0 + 0.5)/uNEdges);
gl_Position = pos;
vec4 pos = vec4((current+(tang*dir+norm*side)*vSize)*uInvert,0.0,1.0);
gl_Position = pos;
vTexCoord = 0.5*pos.xy+0.5; vTexCoord = 0.5*pos.xy+0.5;
//float seed = floor(aIdx/4.0); //float seed = floor(aIdx/4.0);
//seed = mod(sin(seed*seed), 7.0); //seed = mod(sin(seed*seed), 7.0);
//if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0); //if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0);
} }
</script> </script>
<script id="gaussianFragment" type="x-shader"> <script id="gaussianFragment" type="x-shader">

Wyświetl plik

@ -12,8 +12,10 @@ var AudioSystem =
this.timePerSample = 1/externalSampleRate; this.timePerSample = 1/externalSampleRate;
this.oldXSamples = new Float32Array(this.bufferSize); this.oldXSamples = new Float32Array(this.bufferSize);
this.oldYSamples = new Float32Array(this.bufferSize); this.oldYSamples = new Float32Array(this.bufferSize);
this.oldZSamples = new Float32Array(this.bufferSize);
this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples); this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples);
this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples); this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples);
this.smoothedZSamples = new Float32Array(Filter.nSmoothedSamples);
}, },
startSound : function() startSound : function()
@ -245,13 +247,13 @@ var Render =
}, },
drawLineTexture : function(xPoints, yPoints) drawLineTexture: function (xPoints, yPoints, zPoints)
{ {
this.fadeAmount = Math.min(1, Math.pow(0.5, controls.persistence) * 0.4); this.fadeAmount = Math.min(1, Math.pow(0.5, controls.persistence) * 0.4);
this.activateTargetTexture(this.lineTexture); this.activateTargetTexture(this.lineTexture);
this.fade(); this.fade();
//gl.clear(gl.COLOR_BUFFER_BIT); //gl.clear(gl.COLOR_BUFFER_BIT);
this.drawLine(xPoints, yPoints); this.drawLine(xPoints, yPoints, zPoints);
gl.bindTexture(gl.TEXTURE_2D, this.targetTexture); gl.bindTexture(gl.TEXTURE_2D, this.targetTexture);
gl.generateMipmap(gl.TEXTURE_2D); gl.generateMipmap(gl.TEXTURE_2D);
}, },
@ -385,7 +387,7 @@ var Render =
} }
}, },
drawLine : function(xPoints, yPoints) drawLine : function(xPoints, yPoints, zPoints)
{ {
this.setAdditiveBlending(); this.setAdditiveBlending();
@ -394,19 +396,11 @@ var Render =
var nPoints = xPoints.length; var nPoints = xPoints.length;
for (var i=0; i<nPoints; i++) for (var i=0; i<nPoints; i++)
{ {
var p = i*8; var p = i * 12;
scratchVertices[p]=scratchVertices[p+2]=scratchVertices[p+4]=scratchVertices[p+6]=xPoints[i]; scratchVertices[p] = scratchVertices[p + 3] = scratchVertices[p + 6] = scratchVertices[p + 9] = xPoints[i];
scratchVertices[p+1]=scratchVertices[p+3]=scratchVertices[p+5]=scratchVertices[p+7]=yPoints[i]; scratchVertices[p + 1] = scratchVertices[p + 4] = scratchVertices[p + 7] = scratchVertices[p + 10] = yPoints[i];
/*if (i>0) scratchVertices[p + 2] = scratchVertices[p + 5] = scratchVertices[p + 8] = scratchVertices[p + 11] = zPoints[i];
{
var xDelta = xPoints[i]-xPoints[i-1];
if (xDelta<0) xDelta = -xDelta;
var yDelta = yPoints[i]-yPoints[i-1];
if (yDelta<0) yDelta = -yDelta;
this.totalLength += xDelta + yDelta;
}*/
} }
//testOutputElement.value = this.totalLength;
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, scratchVertices, gl.STATIC_DRAW); gl.bufferData(gl.ARRAY_BUFFER, scratchVertices, gl.STATIC_DRAW);
@ -419,8 +413,8 @@ var Render =
gl.enableVertexAttribArray(program.aIdx); gl.enableVertexAttribArray(program.aIdx);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.vertexAttribPointer(program.aStart, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(program.aStart, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribPointer(program.aEnd, 2, gl.FLOAT, false, 0, 8*4); gl.vertexAttribPointer(program.aEnd, 3, gl.FLOAT, false, 0, 12*4);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadIndexBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.quadIndexBuffer);
gl.vertexAttribPointer(program.aIdx, 1, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(program.aIdx, 1, gl.FLOAT, false, 0, 0);
@ -679,65 +673,79 @@ function doScriptProcessor(bufferBase64) {
req.open('GET', "data:application/octet;base64," + bufferBase64); req.open('GET', "data:application/octet;base64," + bufferBase64);
req.responseType = 'arraybuffer'; req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e) { req.onload = function fileLoaded(e) {
var dataView = new DataView(e.target.response); Juce.getNativeFunction("getSettings")().then(settings => {
for (var i = 0; i < xSamples.length; i++) {
xSamples[i] = dataView.getFloat32(i * 4 * 2, true);
ySamples[i] = dataView.getFloat32(i * 4 * 2 + 4, true);
}
const getSettingsFn = Juce.getNativeFunction("getSettings");
getSettingsFn().then(settings => {
controls.brightness = settings.brightness; controls.brightness = settings.brightness;
controls.intensity = settings.intensity; controls.intensity = settings.intensity;
controls.persistence = settings.persistence; controls.persistence = settings.persistence;
controls.hue = settings.hue; controls.hue = settings.hue;
controls.disableFilter = !settings.upsampling; controls.disableFilter = !settings.upsampling;
let numChannels = settings.numChannels;
if (controls.grid !== settings.graticule) { if (controls.grid !== settings.graticule) {
controls.grid = settings.graticule; controls.grid = settings.graticule;
const image = controls.noise ? 'noise.jpg' : 'empty.jpg'; const image = controls.noise ? 'noise.jpg' : 'empty.jpg';
Render.screenTexture = Render.loadTexture(image); Render.screenTexture = Render.loadTexture(image);
} }
if (controls.noise !== settings.smudges) { if (controls.noise !== settings.smudges) {
controls.noise = settings.smudges; controls.noise = settings.smudges;
const image = controls.noise ? 'noise.jpg' : 'empty.jpg'; const image = controls.noise ? 'noise.jpg' : 'empty.jpg';
Render.screenTexture = Render.loadTexture(image); Render.screenTexture = Render.loadTexture(image);
} }
});
if (controls.sweepOn) { var dataView = new DataView(e.target.response);
var gain = Math.pow(2.0, controls.mainGain);
var sweepMinTime = controls.sweepMsDiv * 10 / 1000; const stride = 4 * numChannels;
var triggerValue = controls.sweepTriggerValue;
for (var i = 0; i < xSamples.length; i++) { for (var i = 0; i < xSamples.length; i++) {
xSamples[i] = sweepPosition / gain; xSamples[i] = dataView.getFloat32(i * stride, true);
sweepPosition += 2 * AudioSystem.timePerSample / sweepMinTime; ySamples[i] = dataView.getFloat32(i * stride + 4, true);
if (sweepPosition > 1.1 && belowTrigger && ySamples[i] >= triggerValue) if (numChannels === 3) {
sweepPosition = -1.3; zSamples[i] = dataView.getFloat32(i * stride + 8, true);
belowTrigger = ySamples[i] < triggerValue; } else {
zSamples[i] = 1;
}
} }
}
if (!controls.freezeImage) { if (controls.sweepOn) {
if (!controls.disableFilter) { var gain = Math.pow(2.0, controls.mainGain);
Filter.generateSmoothedSamples(AudioSystem.oldXSamples, xSamples, AudioSystem.smoothedXSamples); var sweepMinTime = controls.sweepMsDiv * 10 / 1000;
Filter.generateSmoothedSamples(AudioSystem.oldYSamples, ySamples, AudioSystem.smoothedYSamples); var triggerValue = controls.sweepTriggerValue;
for (var i = 0; i < xSamples.length; i++) {
if (!controls.swapXY) Render.drawLineTexture(AudioSystem.smoothedXSamples, AudioSystem.smoothedYSamples); xSamples[i] = sweepPosition / gain;
else Render.drawLineTexture(AudioSystem.smoothedYSamples, AudioSystem.smoothedXSamples); sweepPosition += 2 * AudioSystem.timePerSample / sweepMinTime;
if (sweepPosition > 1.1 && belowTrigger && ySamples[i] >= triggerValue)
sweepPosition = -1.3;
belowTrigger = ySamples[i] < triggerValue;
}
} }
else {
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples); if (!controls.freezeImage) {
else Render.drawLineTexture(ySamples, xSamples); if (!controls.disableFilter) {
Filter.generateSmoothedSamples(AudioSystem.oldXSamples, xSamples, AudioSystem.smoothedXSamples);
Filter.generateSmoothedSamples(AudioSystem.oldYSamples, ySamples, AudioSystem.smoothedYSamples);
if (numChannels === 3) {
Filter.generateSmoothedSamples(AudioSystem.oldZSamples, zSamples, AudioSystem.smoothedZSamples);
} else {
AudioSystem.smoothedZSamples.fill(1);
}
if (!controls.swapXY) Render.drawLineTexture(AudioSystem.smoothedXSamples, AudioSystem.smoothedYSamples, AudioSystem.smoothedZSamples);
else Render.drawLineTexture(AudioSystem.smoothedYSamples, AudioSystem.smoothedXSamples, AudioSystem.smoothedZSamples);
}
else {
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples, zSamples);
else Render.drawLineTexture(ySamples, xSamples, zSamples);
}
} }
}
for (var i = 0; i < xSamples.length; i++) { for (var i = 0; i < xSamples.length; i++) {
AudioSystem.oldXSamples[i] = xSamples[i]; AudioSystem.oldXSamples[i] = xSamples[i];
AudioSystem.oldYSamples[i] = ySamples[i]; AudioSystem.oldYSamples[i] = ySamples[i];
} AudioSystem.oldZSamples[i] = zSamples[i];
}
requestAnimationFrame(drawCRTFrame); requestAnimationFrame(drawCRTFrame);
});
} }
req.send(); req.send();
} }
@ -748,13 +756,15 @@ function drawCRTFrame(timeStamp) {
var xSamples = new Float32Array(externalBufferSize); var xSamples = new Float32Array(externalBufferSize);
var ySamples = new Float32Array(externalBufferSize); var ySamples = new Float32Array(externalBufferSize);
var zSamples = new Float32Array(externalBufferSize);
Juce.getNativeFunction("bufferSize")().then(bufferSize => { Juce.getNativeFunction("bufferSize")().then(bufferSize => {
externalBufferSize = bufferSize; externalBufferSize = bufferSize;
Juce.getNativeFunction("sampleRate")().then(sampleRate => { Juce.getNativeFunction("sampleRate")().then(sampleRate => {
externalSampleRate = sampleRate; externalSampleRate = sampleRate;
xSamples = new Float32Array(externalBufferSize); xSamples = new Float32Array(externalBufferSize);
ySamples = new Float32Array(externalBufferSize); ySamples = new Float32Array(externalBufferSize);
zSamples = new Float32Array(externalBufferSize);
Render.init(); Render.init();
Filter.init(externalBufferSize, 8, 6); Filter.init(externalBufferSize, 8, 6);
AudioSystem.init(externalBufferSize); AudioSystem.init(externalBufferSize);

Wyświetl plik

@ -751,8 +751,7 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
{ {
juce::SpinLock::ScopedLockType scope(consumerLock); juce::SpinLock::ScopedLockType scope(consumerLock);
for (auto consumer : consumers) { for (auto consumer : consumers) {
consumer->write(x); consumer->write(Point(x, y, 1));
consumer->write(y);
consumer->notifyIfFull(); consumer->notifyIfFull();
} }
} }

Wyświetl plik

@ -12,17 +12,17 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
setLookAndFeel(&lookAndFeel); setLookAndFeel(&lookAndFeel);
// #if JUCE_MAC #if JUCE_MAC
// if (audioProcessor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) { if (audioProcessor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
// usingNativeMenuBar = true; usingNativeMenuBar = true;
// menuBarModel.setMacMainMenu(&menuBarModel); menuBarModel.setMacMainMenu(&menuBarModel);
// } }
// #endif #endif
// if (!usingNativeMenuBar) { if (!usingNativeMenuBar) {
// menuBar.setModel(&menuBarModel); menuBar.setModel(&menuBarModel);
// addAndMakeVisible(menuBar); addAndMakeVisible(menuBar);
// } }
if (juce::JUCEApplicationBase::isStandaloneApp()) { if (juce::JUCEApplicationBase::isStandaloneApp()) {
if (juce::TopLevelWindow::getNumTopLevelWindows() > 0) { if (juce::TopLevelWindow::getNumTopLevelWindows() > 0) {
@ -44,6 +44,27 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
addAndMakeVisible(visualiser); addAndMakeVisible(visualiser);
visualiser.openSettings = [this] {
visualiserSettingsWindow.setVisible(true);
visualiserSettingsWindow.toFront(true);
};
visualiser.closeSettings = [this] {
visualiserSettingsWindow.setVisible(false);
};
visualiserSettingsWindow.setResizable(false, false);
#if JUCE_WINDOWS
// if not standalone, use native title bar for compatibility with DAWs
visualiserSettingsWindow.setUsingNativeTitleBar(processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone);
#elif JUCE_MAC
visualiserSettingsWindow.setUsingNativeTitleBar(true);
#endif
visualiserSettings.setLookAndFeel(&getLookAndFeel());
visualiserSettings.setSize(550, 280);
visualiserSettingsWindow.setContentNonOwned(&visualiserSettings, true);
visualiserSettingsWindow.centreWithSize(550, 280);
setSize(750, 750); setSize(750, 750);
setResizable(true, true); setResizable(true, true);
setResizeLimits(250, 250, 999999, 999999); setResizeLimits(250, 250, 999999, 999999);
@ -53,11 +74,11 @@ SosciPluginEditor::~SosciPluginEditor() {
setLookAndFeel(nullptr); setLookAndFeel(nullptr);
juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr);
// #if JUCE_MAC #if JUCE_MAC
// if (usingNativeMenuBar) { if (usingNativeMenuBar) {
// menuBarModel.setMacMainMenu(nullptr); menuBarModel.setMacMainMenu(nullptr);
// } }
// #endif #endif
} }
void SosciPluginEditor::paint(juce::Graphics& g) { void SosciPluginEditor::paint(juce::Graphics& g) {
@ -66,11 +87,12 @@ void SosciPluginEditor::paint(juce::Graphics& g) {
void SosciPluginEditor::resized() { void SosciPluginEditor::resized() {
auto area = getLocalBounds(); auto area = getLocalBounds();
visualiser.setBounds(area);
// if (!usingNativeMenuBar) { if (!usingNativeMenuBar) {
// menuBar.setBounds(area.removeFromTop(25)); menuBar.setBounds(area.removeFromTop(25));
// } }
visualiser.setBounds(area);
} }
bool SosciPluginEditor::keyPressed(const juce::KeyPress& key) { bool SosciPluginEditor::keyPressed(const juce::KeyPress& key) {

Wyświetl plik

@ -5,6 +5,7 @@
#include "components/VisualiserComponent.h" #include "components/VisualiserComponent.h"
#include "LookAndFeel.h" #include "LookAndFeel.h"
#include "components/VisualiserSettings.h" #include "components/VisualiserSettings.h"
#include "components/SosciMainMenuBarModel.h"
class SosciPluginEditor : public juce::AudioProcessorEditor { class SosciPluginEditor : public juce::AudioProcessorEditor {
public: public:
@ -26,17 +27,17 @@ private:
public: public:
OscirenderLookAndFeel lookAndFeel; OscirenderLookAndFeel lookAndFeel;
VisualiserSettings visualiserSettings = VisualiserSettings(audioProcessor.parameters); VisualiserSettings visualiserSettings = VisualiserSettings(audioProcessor.parameters, 3);
SettingsWindow visualiserSettingsWindow = SettingsWindow("Visualiser Settings"); SettingsWindow visualiserSettingsWindow = SettingsWindow("Visualiser Settings");
VisualiserComponent visualiser{audioProcessor, audioProcessor, visualiserSettings, nullptr, audioProcessor.parameters.legacyVisualiserEnabled->getBoolValue()}; VisualiserComponent visualiser{audioProcessor, audioProcessor, visualiserSettings, nullptr, audioProcessor.parameters.legacyVisualiserEnabled->getBoolValue()};
std::unique_ptr<juce::FileChooser> chooser; std::unique_ptr<juce::FileChooser> chooser;
// MainMenuBarModel menuBarModel{audioProcessor, *this}; SosciMainMenuBarModel menuBarModel{*this};
// juce::MenuBarComponent menuBar; juce::MenuBarComponent menuBar;
juce::TooltipWindow tooltipWindow{nullptr, 0}; juce::TooltipWindow tooltipWindow{nullptr, 0};
//bool usingNativeMenuBar = false; bool usingNativeMenuBar = false;
#if JUCE_LINUX #if JUCE_LINUX
juce::OpenGLContext openGlContext; juce::OpenGLContext openGlContext;

Wyświetl plik

@ -13,14 +13,9 @@
//============================================================================== //==============================================================================
SosciAudioProcessor::SosciAudioProcessor() SosciAudioProcessor::SosciAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations #ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties() : AudioProcessor (BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true)
#if ! JucePlugin_IsMidiEffect .withInput("Brightness", juce::AudioChannelSet::mono(), true)
#if ! JucePlugin_IsSynth .withOutput("Output", juce::AudioChannelSet::stereo(), true))
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
#endif #endif
{ {
// locking isn't necessary here because we are in the constructor // locking isn't necessary here because we are in the constructor
@ -123,31 +118,12 @@ void SosciAudioProcessor::releaseResources() {
// spare memory, etc. // spare memory, etc.
} }
#ifndef JucePlugin_PreferredChannelConfigurations bool SosciAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const {
bool SosciAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const auto numIns = layouts.getMainInputChannels();
{ auto numOuts = layouts.getMainOutputChannels();
#if JucePlugin_IsMidiEffect
juce::ignoreUnused (layouts);
return true;
#else
// This is the place where you check if the layout is supported.
// In this template code we only support mono or stereo.
// Some plugin hosts, such as certain GarageBand versions, will only
// load plugins that support stereo bus layouts.
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;
// This checks if the input layout matches the output layout return numIns >= 2 && numOuts >= 2;
#if ! JucePlugin_IsSynth
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
#endif
return true;
#endif
} }
#endif
// effectsLock should be held when calling this // effectsLock should be held when calling this
std::shared_ptr<Effect> SosciAudioProcessor::getEffect(juce::String id) { std::shared_ptr<Effect> SosciAudioProcessor::getEffect(juce::String id) {
@ -191,25 +167,30 @@ IntParameter* SosciAudioProcessor::getIntParameter(juce::String id) {
void SosciAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) { void SosciAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
juce::ScopedNoDenormals noDenormals; juce::ScopedNoDenormals noDenormals;
// Audio info variables
int numInputs = getTotalNumInputChannels(); auto input = getBusBuffer(buffer, true, 0);
int numOutputs = getTotalNumOutputChannels(); auto brightness = getBusBuffer(buffer, true, 1);
double sampleRate = getSampleRate();
midiMessages.clear(); midiMessages.clear();
auto* channelData = buffer.getArrayOfWritePointers(); auto inputArray = input.getArrayOfWritePointers();
auto brightnessArray = brightness.getArrayOfWritePointers();
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { for (int sample = 0; sample < input.getNumSamples(); ++sample) {
juce::SpinLock::ScopedLockType scope(consumerLock); juce::SpinLock::ScopedLockType scope(consumerLock);
double x = numOutputs > 0 ? channelData[0][sample] : 0; float x = input.getNumChannels() > 0 ? inputArray[0][sample] : 0.0f;
double y = numOutputs > 1 ? channelData[1][sample] : 0; float y = input.getNumChannels() > 1 ? inputArray[1][sample] : 0.0f;
double z = numOutputs > 2 ? channelData[2][sample] : 0; float z = brightness.getNumChannels() > 0 ? brightnessArray[0][sample] : 1.0f;
Point point = { x, y, z };
for (auto& effect : allEffects) {
point = effect->apply(sample, point);
}
for (auto consumer : consumers) { for (auto consumer : consumers) {
consumer->write(x); consumer->write(point);
consumer->write(y);
consumer->notifyIfFull(); consumer->notifyIfFull();
} }
} }

Wyświetl plik

@ -59,11 +59,6 @@ public:
juce::SpinLock effectsLock; juce::SpinLock effectsLock;
VisualiserParameters parameters; VisualiserParameters parameters;
private:
juce::SpinLock consumerLock;
std::vector<std::shared_ptr<BufferConsumer>> consumers;
public:
// shouldn't be accessed by audio thread, but needs to persist when GUI is closed // shouldn't be accessed by audio thread, but needs to persist when GUI is closed
// so should only be accessed by message thread // so should only be accessed by message thread
juce::String currentProjectFile; juce::String currentProjectFile;

Wyświetl plik

@ -23,7 +23,7 @@ void PitchDetector::run() {
// buffer is for 2 channels, so we need to only use one // buffer is for 2 channels, so we need to only use one
for (int i = 0; i < fftSize; i++) { for (int i = 0; i < fftSize; i++) {
fftData[i] = buffer[2 * i]; fftData[i] = buffer[i].x;
} }
forwardFFT.performFrequencyOnlyForwardTransform(fftData.data()); forwardFFT.performFrequencyOnlyForwardTransform(fftData.data());

Wyświetl plik

@ -22,7 +22,7 @@ private:
juce::CriticalSection consumerLock; juce::CriticalSection consumerLock;
std::shared_ptr<BufferConsumer> consumer; std::shared_ptr<BufferConsumer> consumer;
std::vector<float> buffer = std::vector<float>(2 * fftSize); std::vector<Point> buffer = std::vector<Point>(fftSize);
juce::dsp::FFT forwardFFT{fftOrder}; juce::dsp::FFT forwardFFT{fftOrder};
std::array<float, fftSize * 2> fftData; std::array<float, fftSize * 2> fftData;
OscirenderAudioProcessor& audioProcessor; OscirenderAudioProcessor& audioProcessor;

Wyświetl plik

@ -1,8 +1,10 @@
#include "AboutComponent.h" #include "AboutComponent.h"
AboutComponent::AboutComponent() { AboutComponent::AboutComponent(const void *image, size_t imageSize, juce::String sectionText) {
addAndMakeVisible(logoComponent); addAndMakeVisible(logoComponent);
addAndMakeVisible(text); addAndMakeVisible(text);
logo = juce::ImageFileFormat::loadFrom(image, imageSize);
logoComponent.setImage(logo); logoComponent.setImage(logo);
@ -13,15 +15,7 @@ AboutComponent::AboutComponent() {
text.setColour(juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack); text.setColour(juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
text.setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentBlack); text.setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentBlack);
text.setJustification(juce::Justification(juce::Justification::centred)); text.setJustification(juce::Justification(juce::Justification::centred));
text.setText( text.setText(sectionText);
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName + "\n"
"Version " + ProjectInfo::versionString + "\n\n"
"A huge thank you to:\n"
"DJ_Level_3, for contributing several features to osci-render\n"
"BUS ERROR Collective, for providing the source code for the Hilligoss encoder\n"
"All the community, for suggesting features and reporting issues!\n\n"
"I am open for commissions! Email me at james@ball.sh."
);
} }
void AboutComponent::resized() { void AboutComponent::resized() {

Wyświetl plik

@ -4,12 +4,12 @@
class AboutComponent : public juce::Component { class AboutComponent : public juce::Component {
public: public:
AboutComponent(); AboutComponent(const void *image, size_t imageSize, juce::String sectionText);
void resized() override; void resized() override;
private: private:
juce::Image logo = juce::ImageFileFormat::loadFrom(BinaryData::logo_png, BinaryData::logo_pngSize); juce::Image logo;
juce::ImageComponent logoComponent; juce::ImageComponent logoComponent;
juce::TextEditor text; juce::TextEditor text;

Wyświetl plik

@ -72,7 +72,15 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
} break; } break;
case 2: { case 2: {
juce::DialogWindow::LaunchOptions options; juce::DialogWindow::LaunchOptions options;
AboutComponent* about = new AboutComponent(); AboutComponent* about = new AboutComponent(BinaryData::logo_png, BinaryData::logo_pngSize,
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName + "\n"
"Version " + ProjectInfo::versionString + "\n\n"
"A huge thank you to:\n"
"DJ_Level_3, for contributing several features to osci-render\n"
"BUS ERROR Collective, for providing the source code for the Hilligoss encoder\n"
"All the community, for suggesting features and reporting issues!\n\n"
"I am open for commissions! Email me at james@ball.sh."
);
options.content.setOwned(about); options.content.setOwned(about);
options.content->setSize(500, 270); options.content->setSize(500, 270);
options.dialogTitle = "About"; options.dialogTitle = "About";

Wyświetl plik

@ -0,0 +1,82 @@
#include "SosciMainMenuBarModel.h"
#include "../SosciPluginEditor.h"
SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& editor) : editor(editor) {}
SosciMainMenuBarModel::~SosciMainMenuBarModel() {}
juce::StringArray SosciMainMenuBarModel::getMenuBarNames() {
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
return juce::StringArray("File", "About", "Audio");
} else {
return juce::StringArray("File", "About");
}
}
juce::PopupMenu SosciMainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
juce::PopupMenu menu;
if (topLevelMenuIndex == 0) {
menu.addItem(1, "Open");
menu.addItem(2, "Save");
menu.addItem(3, "Save As");
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
menu.addItem(4, "Create New Project");
}
} else if (topLevelMenuIndex == 1) {
menu.addItem(1, "About sosci");
} else if (topLevelMenuIndex == 2) {
menu.addItem(1, "Settings");
}
return menu;
}
void SosciMainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
switch (topLevelMenuIndex) {
case 0:
switch (menuItemID) {
case 1:
editor.openProject();
break;
case 2:
editor.saveProject();
break;
case 3:
editor.saveProjectAs();
break;
case 4:
editor.resetToDefault();
break;
default:
break;
}
break;
case 1: {
juce::DialogWindow::LaunchOptions options;
AboutComponent* about = new AboutComponent(BinaryData::logo_png, BinaryData::logo_pngSize, "Sosci");
options.content.setOwned(about);
options.content->setSize(500, 270);
options.dialogTitle = "About";
options.dialogBackgroundColour = Colours::dark;
options.escapeKeyTriggersCloseButton = true;
#if JUCE_WINDOWS
// if not standalone, use native title bar for compatibility with DAWs
options.useNativeTitleBar = editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone;
#elif JUCE_MAC
options.useNativeTitleBar = true;
#endif
options.resizable = false;
juce::DialogWindow* dw = options.launchAsync();
} break;
case 2:
editor.openAudioSettings();
break;
default:
break;
}
}
void SosciMainMenuBarModel::menuBarActivated(bool isActive) {}

Wyświetl plik

@ -0,0 +1,19 @@
#pragma once
#include <JuceHeader.h>
#include "AboutComponent.h"
class SosciPluginEditor;
class SosciMainMenuBarModel : public juce::MenuBarModel {
public:
SosciMainMenuBarModel(SosciPluginEditor& editor);
~SosciMainMenuBarModel();
juce::StringArray getMenuBarNames() override;
juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;
void menuBarActivated(bool isActive);
private:
SosciPluginEditor& editor;
};

Wyświetl plik

@ -73,13 +73,12 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) {
enableFullScreen(); enableFullScreen();
} }
void VisualiserComponent::setBuffer(std::vector<float>& newBuffer) { void VisualiserComponent::setBuffer(std::vector<Point>& newBuffer) {
juce::CriticalSection::ScopedLockType scope(lock); juce::CriticalSection::ScopedLockType scope(lock);
buffer.clear(); buffer.clear();
int stride = oldVisualiser ? roughness.textBox.getValue() : 1; int stride = oldVisualiser ? roughness.textBox.getValue() : 1;
for (int i = 0; i < newBuffer.size(); i += stride * 2) { for (int i = 0; i < newBuffer.size(); i += stride) {
buffer.push_back(newBuffer[i]); buffer.push_back(newBuffer[i]);
buffer.push_back(newBuffer[i + 1]);
} }
} }
@ -244,8 +243,8 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area
auto transform = juce::AffineTransform::fromTargetPoints(-1.0f, -1.0f, area.getX(), area.getBottom(), 1.0f, 1.0f, area.getRight(), area.getY(), 1.0f, -1.0f, area.getRight(), area.getBottom()); auto transform = juce::AffineTransform::fromTargetPoints(-1.0f, -1.0f, area.getX(), area.getBottom(), 1.0f, 1.0f, area.getRight(), area.getY(), 1.0f, -1.0f, area.getRight(), area.getBottom());
std::vector<juce::Line<float>> lines; std::vector<juce::Line<float>> lines;
for (int i = 2; i < buffer.size(); i += 2) { for (int i = 2; i < buffer.size(); i++) {
lines.emplace_back(buffer[i - 2], buffer[i - 1], buffer[i], buffer[i + 1]); lines.emplace_back(buffer[i - 1].x, buffer[i - 1].y, buffer[i].x, buffer[i].y);
} }
double strength = 15; double strength = 15;
@ -316,7 +315,7 @@ void VisualiserComponent::initialiseBrowser() {
complete(settings.getSettings()); complete(settings.getSettings());
}) })
.withNativeFunction("bufferSize", [this](auto& var, auto complete) { .withNativeFunction("bufferSize", [this](auto& var, auto complete) {
complete((int) tempBuffer.size() / 2); complete((int) tempBuffer.size());
}) })
.withNativeFunction("sampleRate", [this](auto& var, auto complete) { .withNativeFunction("sampleRate", [this](auto& var, auto complete) {
complete(sampleRate); complete(sampleRate);
@ -330,7 +329,7 @@ void VisualiserComponent::initialiseBrowser() {
void VisualiserComponent::resetBuffer() { void VisualiserComponent::resetBuffer() {
sampleRate = (int) sampleRateManager.getSampleRate(); sampleRate = (int) sampleRateManager.getSampleRate();
tempBuffer = std::vector<float>(2 * sampleRate * BUFFER_LENGTH_SECS); tempBuffer = std::vector<Point>(sampleRate * BUFFER_LENGTH_SECS);
if (!oldVisualiser && isShowing()) { if (!oldVisualiser && isShowing()) {
restartBrowser = true; restartBrowser = true;
triggerAsyncUpdate(); triggerAsyncUpdate();
@ -344,7 +343,22 @@ void VisualiserComponent::handleAsyncUpdate() {
} }
if (audioUpdated && browser != nullptr) { if (audioUpdated && browser != nullptr) {
juce::CriticalSection::ScopedLockType scope(lock); juce::CriticalSection::ScopedLockType scope(lock);
browser->emitEventIfBrowserIsVisible("audioUpdated", juce::Base64::toBase64(buffer.data(), buffer.size() * sizeof(float))); std::vector<float> rawBuffer;
if (settings.numChannels == 2) {
rawBuffer.reserve(buffer.size() * 2);
for (auto& point : buffer) {
rawBuffer.push_back(point.x);
rawBuffer.push_back(point.y);
}
} else if (settings.numChannels == 3) {
rawBuffer.reserve(buffer.size() * 3);
for (auto& point : buffer) {
rawBuffer.push_back(point.x);
rawBuffer.push_back(point.y);
rawBuffer.push_back(point.z);
}
}
browser->emitEventIfBrowserIsVisible("audioUpdated", juce::Base64::toBase64(rawBuffer.data(), rawBuffer.size() * sizeof(float)));
audioUpdated = false; audioUpdated = false;
} }
} }

Wyświetl plik

@ -28,7 +28,7 @@ public:
void enableFullScreen(); void enableFullScreen();
void setFullScreenCallback(std::function<void(FullScreenMode)> callback); void setFullScreenCallback(std::function<void(FullScreenMode)> callback);
void mouseDoubleClick(const juce::MouseEvent& event) override; void mouseDoubleClick(const juce::MouseEvent& event) override;
void setBuffer(std::vector<float>& buffer); void setBuffer(std::vector<Point>& buffer);
void setColours(juce::Colour backgroundColour, juce::Colour waveformColour); void setColours(juce::Colour backgroundColour, juce::Colour waveformColour);
void paintXY(juce::Graphics&, juce::Rectangle<float> bounds); void paintXY(juce::Graphics&, juce::Rectangle<float> bounds);
void paint(juce::Graphics&) override; void paint(juce::Graphics&) override;
@ -62,7 +62,7 @@ private:
std::atomic<bool> oldVisualiser; std::atomic<bool> oldVisualiser;
juce::CriticalSection lock; juce::CriticalSection lock;
std::vector<float> buffer; std::vector<Point> buffer;
std::vector<juce::Line<float>> prevLines; std::vector<juce::Line<float>> prevLines;
juce::Colour backgroundColour, waveformColour; juce::Colour backgroundColour, waveformColour;
SampleRateManager& sampleRateManager; SampleRateManager& sampleRateManager;
@ -75,7 +75,7 @@ private:
SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white }; SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white };
SvgButton settingsButton{ "settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white }; SvgButton settingsButton{ "settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white };
std::vector<float> tempBuffer; std::vector<Point> tempBuffer;
int precision = 4; int precision = 4;
juce::CriticalSection consumerLock; juce::CriticalSection consumerLock;

Wyświetl plik

@ -3,7 +3,7 @@
#include "../PluginEditor.h" #include "../PluginEditor.h"
VisualiserSettings::VisualiserSettings(VisualiserParameters& parameters) : parameters(parameters) { VisualiserSettings::VisualiserSettings(VisualiserParameters& parameters, int numChannels) : parameters(parameters), numChannels(numChannels) {
addAndMakeVisible(brightness); addAndMakeVisible(brightness);
addAndMakeVisible(intensity); addAndMakeVisible(intensity);
addAndMakeVisible(persistence); addAndMakeVisible(persistence);
@ -41,5 +41,6 @@ juce::var VisualiserSettings::getSettings() {
settings->setProperty("graticule", parameters.graticuleEnabled->getBoolValue()); settings->setProperty("graticule", parameters.graticuleEnabled->getBoolValue());
settings->setProperty("smudges", parameters.smudgesEnabled->getBoolValue()); settings->setProperty("smudges", parameters.smudgesEnabled->getBoolValue());
settings->setProperty("upsampling", parameters.upsamplingEnabled->getBoolValue()); settings->setProperty("upsampling", parameters.upsamplingEnabled->getBoolValue());
settings->setProperty("numChannels", numChannels);
return juce::var(settings); return juce::var(settings);
} }

Wyświetl plik

@ -55,13 +55,14 @@ public:
class VisualiserSettings : public juce::Component { class VisualiserSettings : public juce::Component {
public: public:
VisualiserSettings(VisualiserParameters&); VisualiserSettings(VisualiserParameters&, int numChannels = 2);
~VisualiserSettings(); ~VisualiserSettings();
void resized() override; void resized() override;
juce::var getSettings(); juce::var getSettings();
VisualiserParameters& parameters; VisualiserParameters& parameters;
int numChannels;
private: private:
EffectComponent brightness{*parameters.brightnessEffect}; EffectComponent brightness{*parameters.brightnessEffect};

Wyświetl plik

@ -115,13 +115,13 @@ void VolumeComponent::run() {
float leftVolume = 0; float leftVolume = 0;
float rightVolume = 0; float rightVolume = 0;
for (int i = 0; i < buffer.size(); i += 2) { for (int i = 0; i < buffer.size(); i++) {
leftVolume += buffer[i] * buffer[i]; leftVolume += buffer[i].x * buffer[i].x;
rightVolume += buffer[i + 1] * buffer[i + 1]; rightVolume += buffer[i].y * buffer[i].y;
} }
// RMS // RMS
leftVolume = std::sqrt(leftVolume / (buffer.size() / 2)); leftVolume = std::sqrt(leftVolume / buffer.size());
rightVolume = std::sqrt(rightVolume / (buffer.size() / 2)); rightVolume = std::sqrt(rightVolume / buffer.size());
if (std::isnan(leftVolume) || std::isnan(rightVolume)) { if (std::isnan(leftVolume) || std::isnan(rightVolume)) {
leftVolume = 0; leftVolume = 0;
@ -149,5 +149,5 @@ void VolumeComponent::resized() {
void VolumeComponent::resetBuffer() { void VolumeComponent::resetBuffer() {
sampleRate = (int) audioProcessor.currentSampleRate; sampleRate = (int) audioProcessor.currentSampleRate;
buffer = std::vector<float>(2 * BUFFER_DURATION_SECS * sampleRate); buffer = std::vector<Point>(BUFFER_DURATION_SECS * sampleRate);
} }

Wyświetl plik

@ -76,7 +76,7 @@ private:
const double BUFFER_DURATION_SECS = 0.02; const double BUFFER_DURATION_SECS = 0.02;
int sampleRate = DEFAULT_SAMPLE_RATE; int sampleRate = DEFAULT_SAMPLE_RATE;
std::vector<float> buffer = std::vector<float>(BUFFER_DURATION_SECS * DEFAULT_SAMPLE_RATE); std::vector<Point> buffer = std::vector<Point>(BUFFER_DURATION_SECS * DEFAULT_SAMPLE_RATE);
std::atomic<float> leftVolume = 0; std::atomic<float> leftVolume = 0;
std::atomic<float> rightVolume = 0; std::atomic<float> rightVolume = 0;

Wyświetl plik

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <JuceHeader.h> #include <JuceHeader.h>
#include "../shape/Point.h"
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
@ -41,7 +42,7 @@ public:
class BufferConsumer { class BufferConsumer {
public: public:
BufferConsumer(std::vector<float>& buffer) : buffer(buffer) {} BufferConsumer(std::vector<Point>& buffer) : buffer(buffer) {}
~BufferConsumer() {} ~BufferConsumer() {}
@ -61,14 +62,14 @@ public:
sema.release(); sema.release();
} }
void write(double d) { void write(Point point) {
if (offset < buffer.size()) { if (offset < buffer.size()) {
buffer[offset++] = d; buffer[offset++] = point;
} }
} }
private: private:
std::vector<float>& buffer; std::vector<Point>& buffer;
Semaphore sema{0}; Semaphore sema{0};
int offset = 0; int offset = 0;
}; };

Wyświetl plik

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <JuceHeader.h> #include <JuceHeader.h>
#include "../shape/Point.h"
#include "BufferConsumer.h" #include "BufferConsumer.h"
@ -9,7 +10,7 @@ public:
ConsumerManager() {} ConsumerManager() {}
~ConsumerManager() {} ~ConsumerManager() {}
std::shared_ptr<BufferConsumer> consumerRegister(std::vector<float>& buffer) { std::shared_ptr<BufferConsumer> consumerRegister(std::vector<Point>& buffer) {
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(buffer); std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(buffer);
juce::SpinLock::ScopedLockType scope(consumerLock); juce::SpinLock::ScopedLockType scope(consumerLock);
consumers.push_back(consumer); consumers.push_back(consumer);

Wyświetl plik

@ -5,7 +5,7 @@
aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed="&#10;" aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed="&#10;"
headerPath="./include" version="1.0.0" companyName="James H Ball" headerPath="./include" version="1.0.0" companyName="James H Ball"
companyWebsite="https://osci-render.com" companyEmail="james@ball.sh" companyWebsite="https://osci-render.com" companyEmail="james@ball.sh"
defines="NOMINMAX=1" pluginAUMainType="'aumf'" binaryDataNamespace="SosciBinaryData"> defines="NOMINMAX=1" pluginAUMainType="'aumf'">
<MAINGROUP id="j5Ge2T" name="sosci"> <MAINGROUP id="j5Ge2T" name="sosci">
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources"> <GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
<GROUP id="{525C568C-29E9-D0A2-9773-8A04981C5575}" name="images"> <GROUP id="{525C568C-29E9-D0A2-9773-8A04981C5575}" name="images">
@ -62,10 +62,18 @@
file="Source/audio/SampleRateManager.h"/> file="Source/audio/SampleRateManager.h"/>
</GROUP> </GROUP>
<GROUP id="{CD81913A-7F0E-5898-DA77-5EBEB369DEB1}" name="components"> <GROUP id="{CD81913A-7F0E-5898-DA77-5EBEB369DEB1}" name="components">
<FILE id="URb7Ok" name="AboutComponent.cpp" compile="1" resource="0"
file="Source/components/AboutComponent.cpp"/>
<FILE id="FtOv3F" name="AboutComponent.h" compile="0" resource="0"
file="Source/components/AboutComponent.h"/>
<FILE id="xLAEHK" name="EffectComponent.cpp" compile="1" resource="0" <FILE id="xLAEHK" name="EffectComponent.cpp" compile="1" resource="0"
file="Source/components/EffectComponent.cpp"/> file="Source/components/EffectComponent.cpp"/>
<FILE id="u4UCwb" name="EffectComponent.h" compile="0" resource="0" <FILE id="u4UCwb" name="EffectComponent.h" compile="0" resource="0"
file="Source/components/EffectComponent.h"/> file="Source/components/EffectComponent.h"/>
<FILE id="eaHM09" name="SosciMainMenuBarModel.cpp" compile="1" resource="0"
file="Source/components/SosciMainMenuBarModel.cpp"/>
<FILE id="EymEr3" name="SosciMainMenuBarModel.h" compile="0" resource="0"
file="Source/components/SosciMainMenuBarModel.h"/>
<FILE id="qzfstC" name="SwitchButton.h" compile="0" resource="0" file="Source/components/SwitchButton.h"/> <FILE id="qzfstC" name="SwitchButton.h" compile="0" resource="0" file="Source/components/SwitchButton.h"/>
<FILE id="y3UiR0" name="VisualiserComponent.cpp" compile="1" resource="0" <FILE id="y3UiR0" name="VisualiserComponent.cpp" compile="1" resource="0"
file="Source/components/VisualiserComponent.cpp"/> file="Source/components/VisualiserComponent.cpp"/>