Add support for toggling upsamping, toggling legacy visualiser, and fix bugs when deleting visualiser

pull/249/head
James H Ball 2024-08-14 20:27:56 +01:00 zatwierdzone przez James H Ball
rodzic e25d8e6d71
commit 9017586b39
11 zmienionych plików z 179 dodań i 126 usunięć

Wyświetl plik

@ -100,6 +100,7 @@
grid : true,
noise : true,
persistence : 0,
disableFilter : false,
}
Number.prototype.toFixedMinus = function(k)
@ -318,8 +319,6 @@
<!--</tr></table>-->
<script>
var killed = false;
var Controls = {
generateUrl : function()

Wyświetl plik

@ -676,77 +676,67 @@ var Render =
var sweepPosition = -1;
var belowTrigger = false;
function doScriptProcessor(event) {
if (!killed) {
fetch(Juce.getBackendResourceAddress("audio"))
.then((response) => response.arrayBuffer())
.then((buffer) => {
var dataView = new DataView(buffer);
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.exposureStops = settings.intensity;
controls.persistence = settings.persistence;
controls.hue = settings.hue;
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;
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;
}
}
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);
}
else
{
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples);
else Render.drawLineTexture(ySamples, xSamples);
}
}
for (var i = 0; i<xSamples.length; i++) {
AudioSystem.oldXSamples[i] = xSamples[i];
AudioSystem.oldYSamples[i] = ySamples[i];
}
requestAnimationFrame(drawCRTFrame);
});
function doScriptProcessor(buffer) {
for (var i = 0; i < xSamples.length; i++) {
xSamples[i] = buffer[i * 2];
ySamples[i] = buffer[i * 2 + 1];
}
const getSettingsFn = Juce.getNativeFunction("getSettings");
getSettingsFn().then(settings => {
controls.exposureStops = settings.intensity;
controls.persistence = settings.persistence;
controls.hue = settings.hue;
controls.disableFilter = !settings.upsampling;
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;
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;
}
}
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);
}
else
{
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples);
else Render.drawLineTexture(ySamples, xSamples);
}
}
for (var i = 0; i<xSamples.length; i++) {
AudioSystem.oldXSamples[i] = xSamples[i];
AudioSystem.oldYSamples[i] = ySamples[i];
}
requestAnimationFrame(drawCRTFrame);
}
function drawCRTFrame(timeStamp) {

Wyświetl plik

@ -48,7 +48,7 @@ public:
std::atomic<bool> editingCustomFunction = false;
VisualiserComponent visualiser{audioProcessor};
VisualiserComponent visualiser{audioProcessor, nullptr, audioProcessor.legacyVisualiserEnabled->getBoolValue()};
std::atomic<bool> visualiserFullScreen = false;
SettingsComponent settings{audioProcessor, *this};
@ -68,7 +68,7 @@ public:
std::shared_ptr<OscirenderCodeEditorComponent> customFunctionCodeEditor = std::make_shared<OscirenderCodeEditorComponent>(*customFunctionCodeDocument, &luaTokeniser, audioProcessor, CustomEffect::UNIQUE_ID, CustomEffect::FILE_NAME);
std::unique_ptr<juce::FileChooser> chooser;
MainMenuBarModel menuBarModel{*this};
MainMenuBarModel menuBarModel{audioProcessor, *this};
juce::MenuBarComponent menuBar;
juce::StretchableLayoutManager layout;

Wyświetl plik

@ -177,6 +177,8 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
booleanParameters.push_back(invertImage);
booleanParameters.push_back(graticuleEnabled);
booleanParameters.push_back(smudgesEnabled);
booleanParameters.push_back(upsamplingEnabled);
booleanParameters.push_back(legacyVisualiserEnabled);
for (auto parameter : booleanParameters) {
addParameter(parameter);

Wyświetl plik

@ -161,6 +161,8 @@ public:
// visualiser settings
BooleanParameter* graticuleEnabled = new BooleanParameter("Show Graticule", "graticuleEnabled", VERSION_HINT, true, "Show the graticule or grid lines over the oscilloscope display.");
BooleanParameter* smudgesEnabled = new BooleanParameter("Show Smudges", "smudgesEnabled", VERSION_HINT, true, "Adds a subtle layer of dirt/smudges to the oscilloscope display to make it look more realistic.");
BooleanParameter* upsamplingEnabled = new BooleanParameter("Upsample Audio", "upsamplingEnabled", VERSION_HINT, true, "Upsamples the audio before visualising it to make it appear more realistic, at the expense of performance.");
BooleanParameter* legacyVisualiserEnabled = new BooleanParameter("Use Legacy Visualiser", "legacyVisualiserEnabled", VERSION_HINT, false, "Replaces the realistic oscilloscope visualiser with the legacy visualiser. This may improve performance.");
std::shared_ptr<Effect> persistenceEffect = std::make_shared<Effect>(
new EffectParameter(
"Persistence",

Wyświetl plik

@ -1,11 +1,27 @@
#include "MainMenuBarModel.h"
#include "../PluginEditor.h"
#include "../PluginProcessor.h"
MainMenuBarModel::MainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), editor(editor) {
audioProcessor.legacyVisualiserEnabled->addListener(this);
}
MainMenuBarModel::~MainMenuBarModel() {
audioProcessor.legacyVisualiserEnabled->removeListener(this);
}
void MainMenuBarModel::parameterValueChanged(int parameterIndex, float legacyVisualiserEnabled) {
editor.visualiser.setVisualiserType(legacyVisualiserEnabled >= 0.5f);
menuItemsChanged();
}
void MainMenuBarModel::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) {}
juce::StringArray MainMenuBarModel::getMenuBarNames() {
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
return juce::StringArray("File", "About", "Audio");
return juce::StringArray("File", "View", "About", "Audio");
} else {
return juce::StringArray("File", "About");
return juce::StringArray("File", "View", "About");
}
}
@ -20,8 +36,10 @@ juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const j
menu.addItem(4, "Create New Project");
}
} else if (topLevelMenuIndex == 1) {
menu.addItem(1, "About osci-render");
menu.addItem(1, "Use Legacy Visualiser", true, audioProcessor.legacyVisualiserEnabled->getBoolValue());
} else if (topLevelMenuIndex == 2) {
menu.addItem(1, "About osci-render");
} else if (topLevelMenuIndex == 3) {
menu.addItem(1, "Settings");
}
@ -49,6 +67,11 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
}
break;
case 1: {
audioProcessor.legacyVisualiserEnabled->setBoolValueNotifyingHost(!audioProcessor.legacyVisualiserEnabled->getBoolValue());
editor.visualiser.setVisualiserType(audioProcessor.legacyVisualiserEnabled->getBoolValue());
menuItemsChanged();
} break;
case 2: {
juce::DialogWindow::LaunchOptions options;
AboutComponent* about = new AboutComponent();
options.content.setOwned(about);
@ -61,7 +84,7 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
juce::DialogWindow* dw = options.launchAsync();
} break;
case 2:
case 3:
editor.openAudioSettings();
break;
default:

Wyświetl plik

@ -2,17 +2,22 @@
#include <JuceHeader.h>
#include "AboutComponent.h"
class OscirenderAudioProcessorEditor;
class MainMenuBarModel : public juce::MenuBarModel {
class OscirenderAudioProcessor;
class MainMenuBarModel : public juce::MenuBarModel, public juce::AudioProcessorParameter::Listener {
public:
MainMenuBarModel(OscirenderAudioProcessorEditor& editor) : editor(editor) {}
~MainMenuBarModel() override {}
MainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor);
~MainMenuBarModel();
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);
void parameterValueChanged(int parameterIndex, float newValue) override;
void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override;
private:
OscirenderAudioProcessor& audioProcessor;
OscirenderAudioProcessorEditor& editor;
};

Wyświetl plik

@ -9,53 +9,48 @@ VisualiserComponent::VisualiserComponent(OscirenderAudioProcessor& p, Visualiser
settingsWindow.setResizable(false, false);
settingsWindow.setUsingNativeTitleBar(true);
settings.setLookAndFeel(&getLookAndFeel());
settings.setSize(550, 200);
settings.setSize(550, 230);
settingsWindow.setContentNonOwned(&settings, true);
setMouseCursor(juce::MouseCursor::PointingHandCursor);
setWantsKeyboardFocus(true);
if (!oldVisualiser) {
addAndMakeVisible(browser);
browser.goToURL(juce::WebBrowserComponent::getResourceProviderRoot() + "oscilloscope.html");
} else {
addChildComponent(browser);
browser.goToURL("about:blank");
roughness.textBox.setValue(audioProcessor.roughness);
roughness.textBox.onValueChange = [this]() {
audioProcessor.roughness = (int) roughness.textBox.getValue();
};
intensity.textBox.setValue(audioProcessor.intensity);
intensity.textBox.onValueChange = [this]() {
audioProcessor.intensity = intensity.textBox.getValue();
};
if (parent == nullptr) {
addChildComponent(fullScreenButton);
}
if (child == nullptr && parent == nullptr) {
addChildComponent(popOutButton);
}
addChildComponent(settingsButton);
fullScreenButton.onClick = [this]() {
enableFullScreen();
};
settingsButton.onClick = [this]() {
juce::PopupMenu menu;
menu.addCustomItem(1, roughness, 160, 40, false);
menu.addCustomItem(1, intensity, 160, 40, false);
menu.showMenuAsync(juce::PopupMenu::Options(), [this](int result) {});
};
popOutButton.onClick = [this]() {
popoutWindow();
};
addChildComponent(browser);
setVisualiserType(oldVisualiser);
roughness.textBox.setValue(audioProcessor.roughness);
roughness.textBox.onValueChange = [this]() {
audioProcessor.roughness = (int) roughness.textBox.getValue();
};
intensity.textBox.setValue(audioProcessor.intensity);
intensity.textBox.onValueChange = [this]() {
audioProcessor.intensity = intensity.textBox.getValue();
};
if (parent == nullptr) {
addChildComponent(fullScreenButton);
}
if (child == nullptr && parent == nullptr) {
addChildComponent(popOutButton);
}
addChildComponent(settingsButton);
fullScreenButton.onClick = [this]() {
enableFullScreen();
};
settingsButton.onClick = [this]() {
juce::PopupMenu menu;
menu.addCustomItem(1, roughness, 160, 40, false);
menu.addCustomItem(1, intensity, 160, 40, false);
menu.showMenuAsync(juce::PopupMenu::Options(), [this](int result) {});
};
popOutButton.onClick = [this]() {
popoutWindow();
};
setFullScreen(false);
}
@ -63,6 +58,7 @@ VisualiserComponent::VisualiserComponent(OscirenderAudioProcessor& p, Visualiser
VisualiserComponent::~VisualiserComponent() {
audioProcessor.consumerStop(consumer);
stopThread(1000);
masterReference.clear();
}
void VisualiserComponent::setFullScreenCallback(std::function<void(FullScreenMode)> callback) {
@ -83,7 +79,8 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) {
void VisualiserComponent::setBuffer(std::vector<float>& newBuffer) {
juce::CriticalSection::ScopedLockType scope(lock);
buffer.clear();
for (int i = 0; i < newBuffer.size(); i += (int) roughness.textBox.getValue() * 2) {
int stride = oldVisualiser ? roughness.textBox.getValue() : 1;
for (int i = 0; i < newBuffer.size(); i += stride * 2) {
buffer.push_back(newBuffer[i]);
buffer.push_back(newBuffer[i + 1]);
}
@ -139,8 +136,19 @@ void VisualiserComponent::run() {
consumer = audioProcessor.consumerRegister(tempBuffer);
audioProcessor.consumerRead(consumer);
browser.emitEventIfBrowserIsVisible("audioUpdated", {});
setBuffer(tempBuffer);
juce::WeakReference<VisualiserComponent> visualiser(this);
if (!oldVisualiser) {
juce::MessageManager::callAsync([this, visualiser] () {
if (visualiser) {
juce::Array<juce::var> data;
for (int i = 0; i < buffer.size(); i++) {
data.add(buffer[i]);
}
browser.emitEventIfBrowserIsVisible("audioUpdated", data);
}
});
}
}
}
@ -217,6 +225,17 @@ bool VisualiserComponent::keyPressed(const juce::KeyPress& key) {
void VisualiserComponent::setFullScreen(bool fullScreen) {}
void VisualiserComponent::setVisualiserType(bool oldVisualiser) {
this->oldVisualiser = oldVisualiser;
if (oldVisualiser) {
browser.setVisible(false);
browser.goToURL("about:blank");
} else {
browser.setVisible(true);
browser.goToURL(juce::WebBrowserComponent::getResourceProviderRoot() + "oscilloscope.html");
}
}
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;
@ -250,7 +269,9 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area
void VisualiserComponent::resetBuffer() {
sampleRate = (int) audioProcessor.currentSampleRate;
tempBuffer = std::vector<float>(2 * sampleRate * BUFFER_LENGTH_SECS);
browser.refresh();
if (!oldVisualiser) {
browser.goToURL(juce::WebBrowserComponent::getResourceProviderRoot() + "oscilloscope.html");
}
}
void VisualiserComponent::resized() {
@ -274,6 +295,7 @@ void VisualiserComponent::childChanged() {
void VisualiserComponent::popoutWindow() {
auto visualiser = new VisualiserComponent(audioProcessor, this);
visualiser->settings.setLookAndFeel(&getLookAndFeel());
child = visualiser;
childChanged();
popOutButton.setVisible(false);

Wyświetl plik

@ -40,8 +40,8 @@ public:
void mouseDown(const juce::MouseEvent& event) override;
void mouseMove(const juce::MouseEvent& event) override;
bool keyPressed(const juce::KeyPress& key) override;
void setFullScreen(bool fullScreen);
void setVisualiserType(bool oldVisualiser);
VisualiserComponent* parent = nullptr;
VisualiserComponent* child = nullptr;
@ -136,8 +136,12 @@ private:
.withNativeFunction("sampleRate", [this](auto& var, auto complete) {
complete(sampleRate);
})
.withNativeFunction("killed", [this](auto& var, auto complete) {
browserKilled = true;
})
);
bool browserKilled = false;
std::vector<float> tempBuffer;
int precision = 4;
@ -151,6 +155,8 @@ private:
void popoutWindow();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VisualiserComponent)
juce::WeakReference<VisualiserComponent>::Master masterReference;
friend class juce::WeakReference<VisualiserComponent>;
};
class VisualiserWindow : public juce::DocumentWindow {

Wyświetl plik

@ -8,6 +8,7 @@ VisualiserSettings::VisualiserSettings(OscirenderAudioProcessor& p, VisualiserCo
addAndMakeVisible(hue);
addAndMakeVisible(graticuleToggle);
addAndMakeVisible(smudgeToggle);
addAndMakeVisible(upsamplingToggle);
intensity.setSliderOnValueChange();
persistence.setSliderOnValueChange();
@ -24,6 +25,7 @@ void VisualiserSettings::resized() {
hue.setBounds(area.removeFromTop(rowHeight));
graticuleToggle.setBounds(area.removeFromTop(rowHeight));
smudgeToggle.setBounds(area.removeFromTop(rowHeight));
upsamplingToggle.setBounds(area.removeFromTop(rowHeight));
}
juce::var VisualiserSettings::getSettings() {
@ -33,5 +35,6 @@ juce::var VisualiserSettings::getSettings() {
settings->setProperty("hue", audioProcessor.hueEffect->getActualValue());
settings->setProperty("graticule", audioProcessor.graticuleEnabled->getBoolValue());
settings->setProperty("smudges", audioProcessor.smudgesEnabled->getBoolValue());
settings->setProperty("upsampling", audioProcessor.upsamplingEnabled->getBoolValue());
return juce::var(settings);
}

Wyświetl plik

@ -24,6 +24,7 @@ private:
jux::SwitchButton graticuleToggle{audioProcessor.graticuleEnabled};
jux::SwitchButton smudgeToggle{audioProcessor.smudgesEnabled};
jux::SwitchButton upsamplingToggle{audioProcessor.upsamplingEnabled};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VisualiserSettings)
};