diff --git a/Resources/oscilloscope/juce/check_native_interop.js b/Resources/oscilloscope/juce/check_native_interop.js new file mode 100644 index 0000000..6e27088 --- /dev/null +++ b/Resources/oscilloscope/juce/check_native_interop.js @@ -0,0 +1,146 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +if ( + typeof window.__JUCE__ !== "undefined" && + typeof window.__JUCE__.getAndroidUserScripts !== "undefined" && + typeof window.inAndroidUserScriptEval === "undefined" +) { + window.inAndroidUserScriptEval = true; + eval(window.__JUCE__.getAndroidUserScripts()); + delete window.inAndroidUserScriptEval; +} + +{ + if (typeof window.__JUCE__ === "undefined") { + console.warn( + "The 'window.__JUCE__' object is undefined." + + " Native integration features will not work." + + " Defining a placeholder 'window.__JUCE__' object." + ); + + window.__JUCE__ = { + postMessage: function () {}, + }; + } + + if (typeof window.__JUCE__.initialisationData === "undefined") { + window.__JUCE__.initialisationData = { + __juce__platform: [], + __juce__functions: [], + __juce__registeredGlobalEventIds: [], + __juce__sliders: [], + __juce__toggles: [], + __juce__comboBoxes: [], + }; + } + + class ListenerList { + constructor() { + this.listeners = new Map(); + this.listenerId = 0; + } + + addListener(fn) { + const newListenerId = this.listenerId++; + this.listeners.set(newListenerId, fn); + return newListenerId; + } + + removeListener(id) { + if (this.listeners.has(id)) { + this.listeners.delete(id); + } + } + + callListeners(payload) { + for (const [, value] of this.listeners) { + value(payload); + } + } + } + + class EventListenerList { + constructor() { + this.eventListeners = new Map(); + } + + addEventListener(eventId, fn) { + if (!this.eventListeners.has(eventId)) + this.eventListeners.set(eventId, new ListenerList()); + + const id = this.eventListeners.get(eventId).addListener(fn); + + return [eventId, id]; + } + + removeEventListener([eventId, id]) { + if (this.eventListeners.has(eventId)) { + this.eventListeners.get(eventId).removeListener(id); + } + } + + emitEvent(eventId, object) { + if (this.eventListeners.has(eventId)) + this.eventListeners.get(eventId).callListeners(object); + } + } + + class Backend { + constructor() { + this.listeners = new EventListenerList(); + } + + addEventListener(eventId, fn) { + return this.listeners.addEventListener(eventId, fn); + } + + removeEventListener([eventId, id]) { + this.listeners.removeEventListener(eventId, id); + } + + emitEvent(eventId, object) { + window.__JUCE__.postMessage( + JSON.stringify({ eventId: eventId, payload: object }) + ); + } + + emitByBackend(eventId, object) { + this.listeners.emitEvent(eventId, JSON.parse(object)); + } + } + + if (typeof window.__JUCE__.backend === "undefined") + window.__JUCE__.backend = new Backend(); +} diff --git a/Resources/oscilloscope/juce/index.js b/Resources/oscilloscope/juce/index.js new file mode 100644 index 0000000..ddeaea8 --- /dev/null +++ b/Resources/oscilloscope/juce/index.js @@ -0,0 +1,492 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +import "./check_native_interop.js"; + +class PromiseHandler { + constructor() { + this.lastPromiseId = 0; + this.promises = new Map(); + + window.__JUCE__.backend.addEventListener( + "__juce__complete", + ({ promiseId, result }) => { + if (this.promises.has(promiseId)) { + this.promises.get(promiseId).resolve(result); + this.promises.delete(promiseId); + } + } + ); + } + + createPromise() { + const promiseId = this.lastPromiseId++; + const result = new Promise((resolve, reject) => { + this.promises.set(promiseId, { resolve: resolve, reject: reject }); + }); + return [promiseId, result]; + } +} + +const promiseHandler = new PromiseHandler(); + +/** + * Returns a function object that calls a function registered on the JUCE backend and forwards all + * parameters to it. + * + * The provided name should be the same as the name argument passed to + * WebBrowserComponent::Options.withNativeFunction() on the backend. + * + * @param {String} name + */ +function getNativeFunction(name) { + if (!window.__JUCE__.initialisationData.__juce__functions.includes(name)) + console.warn( + `Creating native function binding for '${name}', which is unknown to the backend` + ); + + const f = function () { + const [promiseId, result] = promiseHandler.createPromise(); + + window.__JUCE__.backend.emitEvent("__juce__invoke", { + name: name, + params: Array.prototype.slice.call(arguments), + resultId: promiseId, + }); + + return result; + }; + + return f; +} + +//============================================================================== + +class ListenerList { + constructor() { + this.listeners = new Map(); + this.listenerId = 0; + } + + addListener(fn) { + const newListenerId = this.listenerId++; + this.listeners.set(newListenerId, fn); + return newListenerId; + } + + removeListener(id) { + if (this.listeners.has(id)) { + this.listeners.delete(id); + } + } + + callListeners(payload) { + for (const [, value] of this.listeners) { + value(payload); + } + } +} + +const BasicControl_valueChangedEventId = "valueChanged"; +const BasicControl_propertiesChangedId = "propertiesChanged"; + +class SliderState { + constructor(name) { + if (!window.__JUCE__.initialisationData.__juce__sliders.includes(name)) + console.warn( + "Creating SliderState for '" + + name + + "', which is unknown to the backend" + ); + + this.name = name; + this.identifier = "__juce__slider" + this.name; + this.scaledValue = 0; + this.properties = { + start: 0, + end: 1, + skew: 1, + name: "", + label: "", + numSteps: 100, + interval: 0, + parameterIndex: -1, + }; + this.valueChangedEvent = new ListenerList(); + this.propertiesChangedEvent = new ListenerList(); + + window.__JUCE__.backend.addEventListener(this.identifier, (event) => + this.handleEvent(event) + ); + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: "requestInitialUpdate", + }); + } + + setNormalisedValue(newValue) { + this.scaledValue = this.snapToLegalValue( + this.normalisedToScaledValue(newValue) + ); + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: BasicControl_valueChangedEventId, + value: this.scaledValue, + }); + } + + sliderDragStarted() {} + + sliderDragEnded() {} + + handleEvent(event) { + if (event.eventType == BasicControl_valueChangedEventId) { + this.scaledValue = event.value; + this.valueChangedEvent.callListeners(); + } + if (event.eventType == BasicControl_propertiesChangedId) { + // eslint-disable-next-line no-unused-vars + let { eventType: _, ...rest } = event; + this.properties = rest; + this.propertiesChangedEvent.callListeners(); + } + } + + getScaledValue() { + return this.scaledValue; + } + + getNormalisedValue() { + return Math.pow( + (this.scaledValue - this.properties.start) / + (this.properties.end - this.properties.start), + this.properties.skew + ); + } + + normalisedToScaledValue(normalisedValue) { + return ( + Math.pow(normalisedValue, 1 / this.properties.skew) * + (this.properties.end - this.properties.start) + + this.properties.start + ); + } + + snapToLegalValue(value) { + const interval = this.properties.interval; + + if (interval == 0) return value; + + const start = this.properties.start; + const clamp = (val, min = 0, max = 1) => Math.max(min, Math.min(max, val)); + + return clamp( + start + interval * Math.floor((value - start) / interval + 0.5), + this.properties.start, + this.properties.end + ); + } +} + +const sliderStates = new Map(); + +for (const sliderName of window.__JUCE__.initialisationData.__juce__sliders) + sliderStates.set(sliderName, new SliderState(sliderName)); + +/** + * Returns a SliderState object that is connected to the backend WebSliderRelay object that was + * created with the same name argument. + * + * To register a WebSliderRelay object create one with the right name and add it to the + * WebBrowserComponent::Options struct using withOptionsFrom. + * + * @param {String} name + */ +function getSliderState(name) { + if (!sliderStates.has(name)) sliderStates.set(name, new SliderState(name)); + + return sliderStates.get(name); +} + +class ToggleState { + constructor(name) { + if (!window.__JUCE__.initialisationData.__juce__toggles.includes(name)) + console.warn( + "Creating ToggleState for '" + + name + + "', which is unknown to the backend" + ); + + this.name = name; + this.identifier = "__juce__toggle" + this.name; + this.value = false; + this.properties = { + name: "", + parameterIndex: -1, + }; + this.valueChangedEvent = new ListenerList(); + this.propertiesChangedEvent = new ListenerList(); + + window.__JUCE__.backend.addEventListener(this.identifier, (event) => + this.handleEvent(event) + ); + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: "requestInitialUpdate", + }); + } + + getValue() { + return this.value; + } + + setValue(newValue) { + this.value = newValue; + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: BasicControl_valueChangedEventId, + value: this.value, + }); + } + + handleEvent(event) { + if (event.eventType == BasicControl_valueChangedEventId) { + this.value = event.value; + this.valueChangedEvent.callListeners(); + } + if (event.eventType == BasicControl_propertiesChangedId) { + // eslint-disable-next-line no-unused-vars + let { eventType: _, ...rest } = event; + this.properties = rest; + this.propertiesChangedEvent.callListeners(); + } + } +} + +const toggleStates = new Map(); + +for (const name of window.__JUCE__.initialisationData.__juce__toggles) + toggleStates.set(name, new ToggleState(name)); + +/** + * Returns a ToggleState object that is connected to the backend WebToggleButtonRelay object that was + * created with the same name argument. + * + * To register a WebToggleButtonRelay object create one with the right name and add it to the + * WebBrowserComponent::Options struct using withOptionsFrom. + * + * @param {String} name + */ +function getToggleState(name) { + if (!toggleStates.has(name)) toggleStates.set(name, new ToggleState(name)); + + return toggleStates.get(name); +} + +class ComboBoxState { + constructor(name) { + if (!window.__JUCE__.initialisationData.__juce__comboBoxes.includes(name)) + console.warn( + "Creating ComboBoxState for '" + + name + + "', which is unknown to the backend" + ); + + this.name = name; + this.identifier = "__juce__comboBox" + this.name; + this.value = 0.0; + this.properties = { + name: "", + parameterIndex: -1, + choices: [], + }; + this.valueChangedEvent = new ListenerList(); + this.propertiesChangedEvent = new ListenerList(); + + window.__JUCE__.backend.addEventListener(this.identifier, (event) => + this.handleEvent(event) + ); + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: "requestInitialUpdate", + }); + } + + getChoiceIndex() { + return Math.round(this.value * (this.properties.choices.length - 1)); + } + + setChoiceIndex(index) { + const numItems = this.properties.choices.length; + this.value = numItems > 1 ? index / (numItems - 1) : 0.0; + + window.__JUCE__.backend.emitEvent(this.identifier, { + eventType: BasicControl_valueChangedEventId, + value: this.value, + }); + } + + handleEvent(event) { + if (event.eventType == BasicControl_valueChangedEventId) { + this.value = event.value; + this.valueChangedEvent.callListeners(); + } + if (event.eventType == BasicControl_propertiesChangedId) { + // eslint-disable-next-line no-unused-vars + let { eventType: _, ...rest } = event; + this.properties = rest; + this.propertiesChangedEvent.callListeners(); + } + } +} + +const comboBoxStates = new Map(); + +for (const name of window.__JUCE__.initialisationData.__juce__comboBoxes) + comboBoxStates.set(name, new ComboBoxState(name)); + +/** + * Returns a ComboBoxState object that is connected to the backend WebComboBoxRelay object that was + * created with the same name argument. + * + * To register a WebComboBoxRelay object create one with the right name and add it to the + * WebBrowserComponent::Options struct using withOptionsFrom. + * + * @param {String} name + */ +function getComboBoxState(name) { + if (!comboBoxStates.has(name)) + comboBoxStates.set(name, new ComboBoxState(name)); + + return comboBoxStates.get(name); +} + +/** + * Appends a platform-specific prefix to the path to ensure that a request sent to this address will + * be received by the backend's ResourceProvider. + * @param {String} path + */ +function getBackendResourceAddress(path) { + const platform = + window.__JUCE__.initialisationData.__juce__platform.length > 0 + ? window.__JUCE__.initialisationData.__juce__platform[0] + : ""; + + if (platform == "windows" || platform == "android") + return "https://juce.backend/" + path; + + if (platform == "macos" || platform == "ios" || platform == "linux") + return "juce://juce.backend/" + path; + + console.warn( + "getBackendResourceAddress() called, but no JUCE native backend is detected." + ); + return path; +} + +/** + * This helper class is intended to aid the implementation of + * AudioProcessorEditor::getControlParameterIndex() for editors using a WebView interface. + * + * Create an instance of this class and call its handleMouseMove() method in each mousemove event. + * + * This class can be used to continuously report the controlParameterIndexAnnotation attribute's + * value related to the DOM element that is currently under the mouse pointer. + * + * This value is defined at all times as follows + * * the annotation attribute's value for the DOM element directly under the mouse, if it has it, + * * the annotation attribute's value for the first parent element, that has it, + * * -1 otherwise. + * + * Whenever there is a change in this value, an event is emitted to the frontend with the new value. + * You can use a ControlParameterIndexReceiver object on the backend to listen to these events. + * + * @param {String} controlParameterIndexAnnotation + */ +class ControlParameterIndexUpdater { + constructor(controlParameterIndexAnnotation) { + this.controlParameterIndexAnnotation = controlParameterIndexAnnotation; + this.lastElement = null; + this.lastControlParameterIndex = null; + } + + handleMouseMove(event) { + const currentElement = document.elementFromPoint( + event.clientX, + event.clientY + ); + + if (currentElement === this.lastElement) return; + this.lastElement = currentElement; + + let controlParameterIndex = -1; + + if (currentElement !== null) + controlParameterIndex = this.#getControlParameterIndex(currentElement); + + if (controlParameterIndex === this.lastControlParameterIndex) return; + this.lastControlParameterIndex = controlParameterIndex; + + window.__JUCE__.backend.emitEvent( + "__juce__controlParameterIndexChanged", + controlParameterIndex + ); + } + + //============================================================================== + #getControlParameterIndex(element) { + const isValidNonRootElement = (e) => { + return e !== null && e !== document.documentElement; + }; + + while (isValidNonRootElement(element)) { + if (element.hasAttribute(this.controlParameterIndexAnnotation)) { + return element.getAttribute(this.controlParameterIndexAnnotation); + } + + element = element.parentElement; + } + + return -1; + } +} + +export { + getNativeFunction, + getSliderState, + getToggleState, + getComboBoxState, + getBackendResourceAddress, + ControlParameterIndexUpdater, +}; diff --git a/Resources/oscilloscope/juce/package.json b/Resources/oscilloscope/juce/package.json new file mode 100644 index 0000000..49409b4 --- /dev/null +++ b/Resources/oscilloscope/juce/package.json @@ -0,0 +1,4 @@ +{ + "name": "juce-framework-frontend", + "version": "7.0.7" +} diff --git a/Resources/oscilloscope/oscilloscope.html b/Resources/oscilloscope/oscilloscope.html index ffd3015..769f1d8 100644 --- a/Resources/oscilloscope/oscilloscope.html +++ b/Resources/oscilloscope/oscilloscope.html @@ -1,72 +1,146 @@ +
+
+ +
- - [CLICK TO START] - @@ -659,4 +733,4 @@ var Controls = { } - + diff --git a/Resources/oscilloscope/oscilloscope.js b/Resources/oscilloscope/oscilloscope.js index 7c2343b..f426282 100644 --- a/Resources/oscilloscope/oscilloscope.js +++ b/Resources/oscilloscope/oscilloscope.js @@ -1,3 +1,4 @@ +import * as Juce from "./index.js"; var AudioSystem = { @@ -5,160 +6,19 @@ var AudioSystem = init : function (bufferSize) { - window.AudioContext = window.AudioContext||window.webkitAudioContext; - this.audioContext = new window.AudioContext(); - this.sampleRate = this.audioContext.sampleRate; + this.sampleRate = 96000; this.bufferSize = bufferSize; this.timePerSample = 1/this.sampleRate; this.oldXSamples = new Float32Array(this.bufferSize); this.oldYSamples = new Float32Array(this.bufferSize); this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples); this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples); - - if (!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia)) - { - microphoneOutput.value = " unavailable in this browser"; - } }, startSound : function() { - var audioElement = document.getElementById("audioElement"); - this.source = this.audioContext.createMediaElementSource(audioElement); - this.audioVolumeNode = this.audioContext.createGain(); - - this.generator = this.audioContext.createScriptProcessor(this.bufferSize, 0, 2); - this.generator.onaudioprocess = SignalGenerator.generate; - - this.scopeNode = this.audioContext.createScriptProcessor(this.bufferSize, 2, 2); - this.scopeNode.onaudioprocess = doScriptProcessor; - this.source.connect(this.scopeNode); - this.generator.connect(this.scopeNode); - - this.scopeNode.connect(this.audioVolumeNode); - this.audioVolumeNode.connect(this.audioContext.destination); - }, - - tryToGetMicrophone : function() - { - if (this.microphoneActive) - { - AudioSystem.microphone.connect(AudioSystem.scopeNode); - audioVolume.value = 0.0; - audioVolume.oninput(); - return; - } - - var constraints = {audio: { mandatory: { echoCancellation: false }}}; - //var constraints = {audio: {echoCancellation: false} }; - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia; - if (navigator.getUserMedia) - { - navigator.getUserMedia(constraints, onStream, function(){micCheckbox.checked = false;}); - } - else - { - micCheckbox.checked = false; - } - }, - - disconnectMicrophone : function() - { - if (this.microphone) this.microphone.disconnect(); - } -} - - - -onStream = function(stream) -{ - AudioSystem.microphoneActive = true; - AudioSystem.microphone = AudioSystem.audioContext.createMediaStreamSource(stream); - AudioSystem.microphone.connect(AudioSystem.scopeNode); - - audioVolume.value = 0.0; - audioVolume.oninput(); -}; - -var SignalGenerator = -{ - oldA : 1.0, - oldB : 1.0, - timeInSamples : 0, - - generate : function(event) - { - var xOut = event.outputBuffer.getChannelData(0); - var yOut = event.outputBuffer.getChannelData(1); - var newA = controls.aValue * Math.pow(10.0, controls.aExponent); - var newB = controls.bValue * Math.pow(10.0, controls.bExponent); - var oldA = SignalGenerator.oldA; - var oldB = SignalGenerator.oldB; - var PI = Math.PI; - var cos = Math.cos; - var sin = Math.sin; - var xFunc = eval("(function xFunc(){return "+controls.xExpression+";})"); - var yFunc = eval("(function yFunc(){return "+controls.yExpression+";})"); - var bufferSize = AudioSystem.bufferSize; - var timeInSamples = SignalGenerator.timeInSamples; - var sampleRate = AudioSystem.sampleRate; - var x = 0.0; - var y = 0.0; - if (!controls.signalGeneratorOn) - { - for (var i=0; i response.arrayBuffer()) + .then((buffer) => { + var dataView = new DataView(buffer); - var length = xSamplesRaw.length; - for (var i=0; i 1.1 && belowTrigger && ySamples[i]>=triggerValue) - sweepPosition =-1.3; - belowTrigger = ySamples[i] 1.1 && belowTrigger && ySamples[i]>=triggerValue) + sweepPosition =-1.3; + belowTrigger = ySamples[i] \ No newline at end of file + \ No newline at end of file diff --git a/Resources/svg/fullscreen.svg b/Resources/svg/fullscreen.svg index cfb6375..46b3a02 100644 --- a/Resources/svg/fullscreen.svg +++ b/Resources/svg/fullscreen.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Resources/svg/open_in_new.svg b/Resources/svg/open_in_new.svg index cddb62b..6c3c200 100644 --- a/Resources/svg/open_in_new.svg +++ b/Resources/svg/open_in_new.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Source/components/VisualiserComponent.cpp b/Source/components/VisualiserComponent.cpp index 902766b..2054008 100644 --- a/Source/components/VisualiserComponent.cpp +++ b/Source/components/VisualiserComponent.cpp @@ -109,6 +109,7 @@ void VisualiserComponent::paint(juce::Graphics& g) { // } // } // + if (!active) { // add translucent layer g.setColour(juce::Colours::black.withAlpha(0.5f)); @@ -134,6 +135,7 @@ void VisualiserComponent::run() { consumer = audioProcessor.consumerRegister(tempBuffer); audioProcessor.consumerRead(consumer); + browser.emitEventIfBrowserIsVisible("audioUpdated", {}); setBuffer(tempBuffer); } } @@ -208,7 +210,7 @@ bool VisualiserComponent::keyPressed(const juce::KeyPress& key) { } void VisualiserComponent::setFullScreen(bool fullScreen) { - // useful as a callback from parent if needed + browser.goToURL(juce::WebBrowserComponent::getResourceProviderRoot() + "oscilloscope.html"); } void VisualiserComponent::paintChannel(juce::Graphics& g, juce::Rectangle area, int channel) { diff --git a/Source/components/VisualiserComponent.h b/Source/components/VisualiserComponent.h index 85e23d4..ab49447 100644 --- a/Source/components/VisualiserComponent.h +++ b/Source/components/VisualiserComponent.h @@ -66,14 +66,23 @@ 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 }; - juce::WebBrowserComponent::ResourceProvider provider = [](const juce::String& path) { + juce::WebBrowserComponent::ResourceProvider provider = [this](const juce::String& path) { juce::String mimeType; - if (path.endsWith(".html")) { + if (path.endsWith("audio")) { + mimeType = "application/octet-stream"; + juce::CriticalSection::ScopedLockType scope(lock); + std::vector data(buffer.size() * sizeof(float)); + std::memcpy(data.data(), buffer.data(), data.size()); + juce::WebBrowserComponent::Resource resource = { data, mimeType }; + return resource; + } else if (path.endsWith(".html")) { mimeType = "text/html"; } else if (path.endsWith(".jpg")) { mimeType = "image/jpeg"; } else if (path.endsWith(".js")) { mimeType = "text/javascript"; + } else if (path.endsWith(".svg")) { + mimeType = "image/svg+xml"; } std::vector data; int size; @@ -89,6 +98,10 @@ private: juce::WebBrowserComponent::Options() .withNativeIntegrationEnabled() .withResourceProvider(provider) + .withNativeFunction("toggleFullscreen", [this](auto& var, auto complete) { + enableFullScreen(); + complete("toggleFullscreen"); + }) ); std::vector tempBuffer; diff --git a/osci-render.jucer b/osci-render.jucer index b558775..6809954 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -23,6 +23,12 @@ + + + + +