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 -->
<script id="gaussianVertex" type="x-shader">
#define EPS 1E-6
uniform float uInvert;
uniform float uSize;
uniform float uNEdges;
uniform float uFadeAmount;
uniform float uIntensity;
uniform float uGain;
attribute vec2 aStart, aEnd;
attribute float aIdx;
varying vec4 uvl;
varying vec2 vTexCoord;
varying float vLen;
varying float vSize;
void main () {
float tang;
vec2 current;
// All points in quad contain the same data:
// segment start point and segment end point.
// We determine point position using its index.
float idx = mod(aIdx,4.0);
#define EPS 1E-6
uniform float uInvert;
uniform float uSize;
uniform float uNEdges;
uniform float uFadeAmount;
uniform float uIntensity;
uniform float uGain;
attribute vec3 aStart, aEnd;
attribute float aIdx;
varying vec4 uvl;
varying vec2 vTexCoord;
varying float vLen;
varying float vSize;
void main () {
float tang;
vec2 current;
// All points in quad contain the same data:
// segment start point and segment end point.
// We determine point position using its index.
float idx = mod(aIdx,4.0);
// `dir` vector is storing the normalized difference
// between end and start
vec2 dir = (aEnd-aStart)*uGain;
uvl.z = length(dir);
vec2 aStartPos = aStart.xy;
vec2 aEndPos = aEnd.xy;
float aStartBrightness = aStart.z;
float aEndBrightness = aEnd.z;
if (uvl.z > EPS)
{
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);
}
// `dir` vector is storing the normalized difference
// between end and start
vec2 dir = (aEndPos-aStartPos)*uGain;
uvl.z = length(dir);
vSize = uSize;
vec2 norm = vec2(-dir.y, dir.x);
if (uvl.z > EPS)
{
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) {
current = aEnd*uGain;
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;
vSize = uSize;
vec2 norm = vec2(-dir.y, dir.x);
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);
gl_Position = pos;
uvl.w *= uIntensity * mix(1.0-uFadeAmount, 1.0, floor(aIdx / 4.0 + 0.5)/uNEdges);
vec4 pos = vec4((current+(tang*dir+norm*side)*vSize)*uInvert,0.0,1.0);
gl_Position = pos;
vTexCoord = 0.5*pos.xy+0.5;
//float seed = floor(aIdx/4.0);
//seed = mod(sin(seed*seed), 7.0);
//if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0);
}
}
</script>
<script id="gaussianFragment" type="x-shader">

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -12,17 +12,17 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
setLookAndFeel(&lookAndFeel);
// #if JUCE_MAC
// if (audioProcessor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
// usingNativeMenuBar = true;
// menuBarModel.setMacMainMenu(&menuBarModel);
// }
// #endif
#if JUCE_MAC
if (audioProcessor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
usingNativeMenuBar = true;
menuBarModel.setMacMainMenu(&menuBarModel);
}
#endif
// if (!usingNativeMenuBar) {
// menuBar.setModel(&menuBarModel);
// addAndMakeVisible(menuBar);
// }
if (!usingNativeMenuBar) {
menuBar.setModel(&menuBarModel);
addAndMakeVisible(menuBar);
}
if (juce::JUCEApplicationBase::isStandaloneApp()) {
if (juce::TopLevelWindow::getNumTopLevelWindows() > 0) {
@ -44,6 +44,27 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
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);
setResizable(true, true);
setResizeLimits(250, 250, 999999, 999999);
@ -53,11 +74,11 @@ SosciPluginEditor::~SosciPluginEditor() {
setLookAndFeel(nullptr);
juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr);
// #if JUCE_MAC
// if (usingNativeMenuBar) {
// menuBarModel.setMacMainMenu(nullptr);
// }
// #endif
#if JUCE_MAC
if (usingNativeMenuBar) {
menuBarModel.setMacMainMenu(nullptr);
}
#endif
}
void SosciPluginEditor::paint(juce::Graphics& g) {
@ -66,11 +87,12 @@ void SosciPluginEditor::paint(juce::Graphics& g) {
void SosciPluginEditor::resized() {
auto area = getLocalBounds();
visualiser.setBounds(area);
// if (!usingNativeMenuBar) {
// menuBar.setBounds(area.removeFromTop(25));
// }
if (!usingNativeMenuBar) {
menuBar.setBounds(area.removeFromTop(25));
}
visualiser.setBounds(area);
}
bool SosciPluginEditor::keyPressed(const juce::KeyPress& key) {

Wyświetl plik

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

Wyświetl plik

@ -13,14 +13,9 @@
//==============================================================================
SosciAudioProcessor::SosciAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
: AudioProcessor (BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true)
.withInput("Brightness", juce::AudioChannelSet::mono(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
#endif
{
// locking isn't necessary here because we are in the constructor
@ -123,31 +118,12 @@ void SosciAudioProcessor::releaseResources() {
// spare memory, etc.
}
#ifndef JucePlugin_PreferredChannelConfigurations
bool SosciAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
#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;
bool SosciAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const {
auto numIns = layouts.getMainInputChannels();
auto numOuts = layouts.getMainOutputChannels();
// This checks if the input layout matches the output layout
#if ! JucePlugin_IsSynth
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
#endif
return true;
#endif
return numIns >= 2 && numOuts >= 2;
}
#endif
// effectsLock should be held when calling this
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) {
juce::ScopedNoDenormals noDenormals;
// Audio info variables
int numInputs = getTotalNumInputChannels();
int numOutputs = getTotalNumOutputChannels();
double sampleRate = getSampleRate();
auto input = getBusBuffer(buffer, true, 0);
auto brightness = getBusBuffer(buffer, true, 1);
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);
double x = numOutputs > 0 ? channelData[0][sample] : 0;
double y = numOutputs > 1 ? channelData[1][sample] : 0;
double z = numOutputs > 2 ? channelData[2][sample] : 0;
float x = input.getNumChannels() > 0 ? inputArray[0][sample] : 0.0f;
float y = input.getNumChannels() > 1 ? inputArray[1][sample] : 0.0f;
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) {
consumer->write(x);
consumer->write(y);
consumer->write(point);
consumer->notifyIfFull();
}
}

Wyświetl plik

@ -59,11 +59,6 @@ public:
juce::SpinLock effectsLock;
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
// so should only be accessed by message thread
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
for (int i = 0; i < fftSize; i++) {
fftData[i] = buffer[2 * i];
fftData[i] = buffer[i].x;
}
forwardFFT.performFrequencyOnlyForwardTransform(fftData.data());

Wyświetl plik

@ -22,7 +22,7 @@ private:
juce::CriticalSection consumerLock;
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};
std::array<float, fftSize * 2> fftData;
OscirenderAudioProcessor& audioProcessor;

Wyświetl plik

@ -1,8 +1,10 @@
#include "AboutComponent.h"
AboutComponent::AboutComponent() {
AboutComponent::AboutComponent(const void *image, size_t imageSize, juce::String sectionText) {
addAndMakeVisible(logoComponent);
addAndMakeVisible(text);
logo = juce::ImageFileFormat::loadFrom(image, imageSize);
logoComponent.setImage(logo);
@ -13,15 +15,7 @@ AboutComponent::AboutComponent() {
text.setColour(juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
text.setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentBlack);
text.setJustification(juce::Justification(juce::Justification::centred));
text.setText(
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."
);
text.setText(sectionText);
}
void AboutComponent::resized() {

Wyświetl plik

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

Wyświetl plik

@ -72,7 +72,15 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
} break;
case 2: {
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->setSize(500, 270);
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();
}
void VisualiserComponent::setBuffer(std::vector<float>& newBuffer) {
void VisualiserComponent::setBuffer(std::vector<Point>& newBuffer) {
juce::CriticalSection::ScopedLockType scope(lock);
buffer.clear();
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 + 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());
std::vector<juce::Line<float>> lines;
for (int i = 2; i < buffer.size(); i += 2) {
lines.emplace_back(buffer[i - 2], buffer[i - 1], buffer[i], buffer[i + 1]);
for (int i = 2; i < buffer.size(); i++) {
lines.emplace_back(buffer[i - 1].x, buffer[i - 1].y, buffer[i].x, buffer[i].y);
}
double strength = 15;
@ -316,7 +315,7 @@ void VisualiserComponent::initialiseBrowser() {
complete(settings.getSettings());
})
.withNativeFunction("bufferSize", [this](auto& var, auto complete) {
complete((int) tempBuffer.size() / 2);
complete((int) tempBuffer.size());
})
.withNativeFunction("sampleRate", [this](auto& var, auto complete) {
complete(sampleRate);
@ -330,7 +329,7 @@ void VisualiserComponent::initialiseBrowser() {
void VisualiserComponent::resetBuffer() {
sampleRate = (int) sampleRateManager.getSampleRate();
tempBuffer = std::vector<float>(2 * sampleRate * BUFFER_LENGTH_SECS);
tempBuffer = std::vector<Point>(sampleRate * BUFFER_LENGTH_SECS);
if (!oldVisualiser && isShowing()) {
restartBrowser = true;
triggerAsyncUpdate();
@ -344,7 +343,22 @@ void VisualiserComponent::handleAsyncUpdate() {
}
if (audioUpdated && browser != nullptr) {
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;
}
}

Wyświetl plik

@ -28,7 +28,7 @@ public:
void enableFullScreen();
void setFullScreenCallback(std::function<void(FullScreenMode)> callback);
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 paintXY(juce::Graphics&, juce::Rectangle<float> bounds);
void paint(juce::Graphics&) override;
@ -62,7 +62,7 @@ private:
std::atomic<bool> oldVisualiser;
juce::CriticalSection lock;
std::vector<float> buffer;
std::vector<Point> buffer;
std::vector<juce::Line<float>> prevLines;
juce::Colour backgroundColour, waveformColour;
SampleRateManager& sampleRateManager;
@ -75,7 +75,7 @@ private:
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 };
std::vector<float> tempBuffer;
std::vector<Point> tempBuffer;
int precision = 4;
juce::CriticalSection consumerLock;

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,6 +1,7 @@
#pragma once
#include <JuceHeader.h>
#include "../shape/Point.h"
#include "BufferConsumer.h"
@ -9,7 +10,7 @@ public:
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);
juce::SpinLock::ScopedLockType scope(consumerLock);
consumers.push_back(consumer);

Wyświetl plik

@ -5,7 +5,7 @@
aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed="&#10;"
headerPath="./include" version="1.0.0" companyName="James H Ball"
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">
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
<GROUP id="{525C568C-29E9-D0A2-9773-8A04981C5575}" name="images">
@ -62,10 +62,18 @@
file="Source/audio/SampleRateManager.h"/>
</GROUP>
<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="Source/components/EffectComponent.cpp"/>
<FILE id="u4UCwb" name="EffectComponent.h" compile="0" resource="0"
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="y3UiR0" name="VisualiserComponent.cpp" compile="1" resource="0"
file="Source/components/VisualiserComponent.cpp"/>