kopia lustrzana https://github.com/jameshball/osci-render
Get audio sent to visualiser, and add buttons
rodzic
0e62c7667a
commit
bda414de92
|
@ -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();
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "juce-framework-frontend",
|
||||
"version": "7.0.7"
|
||||
}
|
|
@ -1,72 +1,146 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<style>
|
||||
audio { width: 100%; }
|
||||
body{font: 12px Courier, Monospace;}
|
||||
canvas{margin-right: 10px;}
|
||||
table {
|
||||
border-spacing:0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 12px Courier, Monospace;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing:0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#buttonRow {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#buttonRow button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#buttonRow button:hover {
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
#buttonRow button:active {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
#fullscreen {
|
||||
background: url(fullscreen.svg) no-repeat;
|
||||
}
|
||||
|
||||
#popout {
|
||||
background: url(open_in_new.svg) no-repeat;
|
||||
}
|
||||
|
||||
#settings {
|
||||
background: url(cog.svg) no-repeat;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body bgcolor="silver" text="black" autocomplete="off" style="margin: 0px;">
|
||||
|
||||
<div id="buttonRow">
|
||||
<button id="fullscreen"/>
|
||||
<button id="popout"/>
|
||||
<button id="settings"/>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var controls=
|
||||
{
|
||||
swapXY : false,
|
||||
sweepOn : false,
|
||||
sweepMsDiv : 1,
|
||||
sweepTriggerValue : 0,
|
||||
signalGeneratorOn : false,
|
||||
mainGain : 0.0,
|
||||
exposureStops : 0.0,
|
||||
audioVolume : 1.0,
|
||||
hue : 125,
|
||||
freezeImage: false,
|
||||
disableFilter: false,
|
||||
aValue : 1.0,
|
||||
aExponent : 0.0,
|
||||
bValue : 1.0,
|
||||
bExponent :0.0,
|
||||
invertXY : false,
|
||||
grid : true,
|
||||
persistence : 0,
|
||||
xExpression : "sin(2*PI*a*t)*cos(2*PI*b*t)",
|
||||
yExpression : "cos(2*PI*a*t)*cos(2*PI*b*t)",
|
||||
}
|
||||
var controls=
|
||||
{
|
||||
swapXY : false,
|
||||
sweepOn : false,
|
||||
sweepMsDiv : 1,
|
||||
sweepTriggerValue : 0,
|
||||
signalGeneratorOn : false,
|
||||
mainGain : 0.0,
|
||||
exposureStops : 0.0,
|
||||
audioVolume : 1.0,
|
||||
hue : 125,
|
||||
freezeImage: false,
|
||||
disableFilter: false,
|
||||
aValue : 1.0,
|
||||
aExponent : 0.0,
|
||||
bValue : 1.0,
|
||||
bExponent :0.0,
|
||||
invertXY : false,
|
||||
grid : true,
|
||||
persistence : 0,
|
||||
xExpression : "sin(2*PI*a*t)*cos(2*PI*b*t)",
|
||||
yExpression : "cos(2*PI*a*t)*cos(2*PI*b*t)",
|
||||
}
|
||||
|
||||
Number.prototype.toFixedMinus = function(k)
|
||||
{
|
||||
if (this<0) return this.toFixed(k);
|
||||
//else return '\xa0'+this.toFixed(k);
|
||||
else return '+'+this.toFixed(k);
|
||||
}
|
||||
|
||||
var toggleVisible = function(string)
|
||||
{
|
||||
var element = document.getElementById(string);
|
||||
console.log(element.style.display);
|
||||
if (element.style.display == "none") element.style.display="block";
|
||||
else element.style.display = "none";
|
||||
}
|
||||
Number.prototype.toFixedMinus = function(k)
|
||||
{
|
||||
if (this<0) return this.toFixed(k);
|
||||
//else return '\xa0'+this.toFixed(k);
|
||||
else return '+'+this.toFixed(k);
|
||||
}
|
||||
|
||||
var toggleVisible = function(string)
|
||||
{
|
||||
var element = document.getElementById(string);
|
||||
console.log(element.style.display);
|
||||
if (element.style.display == "none") element.style.display="block";
|
||||
else element.style.display = "none";
|
||||
}
|
||||
|
||||
let timeout;
|
||||
document.addEventListener("mousemove", function() {
|
||||
const buttons = document.getElementById('buttonRow');
|
||||
buttons.style.display = "block";
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(function() {
|
||||
buttons.style.display = "none";
|
||||
}, 1000)
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import * as Juce from "./index.js";
|
||||
|
||||
const fullscreen = document.getElementById('fullscreen');
|
||||
const toggleFullscreen = Juce.getNativeFunction("toggleFullscreen");
|
||||
|
||||
document.addEventListener("dblclick", function() {
|
||||
toggleFullscreen();
|
||||
});
|
||||
|
||||
fullscreen.onclick = function() {
|
||||
toggleFullscreen();
|
||||
};
|
||||
</script>
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
|
||||
<td valign="top" style="padding: 0px;">
|
||||
<canvas id="crtCanvas" width="800" height="800"></canvas>
|
||||
<b id="clicktostart" style="position: absolute; z-index: 1; left: 100px; top: 50px; width:200px; height:40px; font-size:18px; color: rgb(116, 187, 116);">
|
||||
[CLICK TO START]
|
||||
</b>
|
||||
</td>
|
||||
<td width="360" valign="top">
|
||||
|
||||
|
@ -659,4 +733,4 @@ var Controls = {
|
|||
}
|
||||
</script>
|
||||
|
||||
<script src="oscilloscope.js"></script>
|
||||
<script src="oscilloscope.js" type="module"></script>
|
||||
|
|
|
@ -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<bufferSize; i++)
|
||||
{
|
||||
xOut[i] = 0;
|
||||
yOut[i] = 0;
|
||||
}
|
||||
}
|
||||
else if ((newA == oldA) && (newB == oldB))
|
||||
{
|
||||
var n = timeInSamples;
|
||||
for (var i=0; i<bufferSize; i++)
|
||||
{
|
||||
var t = n/sampleRate;
|
||||
var a = newA;
|
||||
var b = newB;
|
||||
x = xFunc();
|
||||
y = yFunc();
|
||||
xOut[i] = x;
|
||||
yOut[i] = y;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var n = timeInSamples;
|
||||
for (var i=0; i<bufferSize; i++)
|
||||
{
|
||||
var t = n/sampleRate;
|
||||
|
||||
var a = oldA;
|
||||
var b = oldB;
|
||||
var oldX = xFunc();
|
||||
var oldY = yFunc();
|
||||
a = newA;
|
||||
b = newB;
|
||||
var newX = xFunc();
|
||||
var newY = yFunc();
|
||||
var alpha_z = i/bufferSize;
|
||||
x = oldX*(1.0-alpha_z)+newX*alpha_z;
|
||||
y = oldY*(1.0-alpha_z)+newY*alpha_z;
|
||||
|
||||
xOut[i] = x;
|
||||
yOut[i] = y;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
SignalGenerator.timeInSamples += AudioSystem.bufferSize;
|
||||
SignalGenerator.oldA = newA;
|
||||
SignalGenerator.oldB = newB;
|
||||
}
|
||||
|
||||
const audioUpdated = window.__JUCE__.backend.addEventListener("audioUpdated", doScriptProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
var Filter =
|
||||
|
@ -756,32 +616,6 @@ var Render =
|
|||
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
||||
},
|
||||
|
||||
drawSimpleLine : function(xSamples, ySamples, colour)
|
||||
{
|
||||
var nVertices = xSamples.length;
|
||||
var vertices = new Float32Array(2*nVertices);
|
||||
for (var i=0; i<nVertices; i++)
|
||||
{
|
||||
vertices[2*i] = xSamples[i];
|
||||
vertices[2*i+1] = ySamples[i];
|
||||
}
|
||||
|
||||
this.setAdditiveBlending();
|
||||
|
||||
var program = this.simpleShader;
|
||||
gl.useProgram(program);
|
||||
gl.enableVertexAttribArray(program.vertexPosition);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
||||
gl.vertexAttribPointer(program.vertexPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
if (colour=="green") gl.uniform4fv(program.colour, [0.01, 0.1, 0.01, 1.0]);
|
||||
else if (colour == "red") gl.uniform4fv(program.colour, [0.1, 0.01, 0.01, 1.0]);
|
||||
|
||||
gl.lineWidth(3.0);
|
||||
gl.drawArrays(gl.LINE_STRIP, 0, nVertices);
|
||||
},
|
||||
|
||||
setAdditiveBlending : function()
|
||||
{
|
||||
//gl.blendEquation( gl.FUNC_ADD );
|
||||
|
@ -861,82 +695,68 @@ var Render =
|
|||
var sweepPosition = -1;
|
||||
var belowTrigger = false;
|
||||
|
||||
function doScriptProcessor(event)
|
||||
{
|
||||
var xSamplesRaw = event.inputBuffer.getChannelData(0);
|
||||
var ySamplesRaw = event.inputBuffer.getChannelData(1);
|
||||
var xOut = event.outputBuffer.getChannelData(0);
|
||||
var yOut = event.outputBuffer.getChannelData(1);
|
||||
function doScriptProcessor(event) {
|
||||
fetch(Juce.getBackendResourceAddress("audio"))
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((buffer) => {
|
||||
var dataView = new DataView(buffer);
|
||||
|
||||
var length = xSamplesRaw.length;
|
||||
for (var i=0; i<length; i++)
|
||||
{
|
||||
xSamples[i] = xSamplesRaw[i];// + (Math.random()-0.5)*controls.noise/2000;
|
||||
ySamples[i] = ySamplesRaw[i];// + (Math.random()-0.5)*controls.noise/2000;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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<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.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.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);
|
||||
}
|
||||
}
|
||||
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<length; i++)
|
||||
{
|
||||
AudioSystem.oldXSamples[i] = xSamples[i];
|
||||
AudioSystem.oldYSamples[i] = ySamples[i];
|
||||
xOut[i] = xSamplesRaw[i];
|
||||
yOut[i] = ySamplesRaw[i];
|
||||
}
|
||||
|
||||
AudioSystem.audioVolumeNode.gain.value = controls.audioVolume;
|
||||
for (var i = 0; i<xSamples.length; i++) {
|
||||
AudioSystem.oldXSamples[i] = xSamples[i];
|
||||
AudioSystem.oldYSamples[i] = ySamples[i];
|
||||
}
|
||||
|
||||
requestAnimationFrame(drawCRTFrame);
|
||||
});
|
||||
}
|
||||
|
||||
function drawCRTFrame(timeStamp)
|
||||
{
|
||||
function drawCRTFrame(timeStamp) {
|
||||
Render.drawCRT();
|
||||
requestAnimationFrame(drawCRTFrame);
|
||||
}
|
||||
|
||||
var xSamples = new Float32Array(1024);
|
||||
var ySamples = new Float32Array(1024);
|
||||
var xSamples = new Float32Array(1920);
|
||||
var ySamples = new Float32Array(1920);
|
||||
UI.init();
|
||||
Render.init();
|
||||
|
||||
document.onclick = function(){ // quick fix to get around autoplay rules, May 2022
|
||||
document.onclick = null;
|
||||
document.getElementById("clicktostart").remove();
|
||||
//Filter.init(512, 10, 4);
|
||||
Filter.init(1024, 8, 6);
|
||||
AudioSystem.init(1024);
|
||||
Render.setupArrays(Filter.nSmoothedSamples);
|
||||
AudioSystem.startSound();
|
||||
requestAnimationFrame(drawCRTFrame);
|
||||
Controls.setupControls();
|
||||
};
|
||||
Filter.init(1920, 8, 6);
|
||||
AudioSystem.init(1920);
|
||||
Render.setupArrays(Filter.nSmoothedSamples);
|
||||
AudioSystem.startSound();
|
||||
requestAnimationFrame(drawCRTFrame);
|
||||
Controls.setupControls();
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
||||
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 986 B Po Szerokość: | Wysokość: | Rozmiar: 1003 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z" /></svg>
|
||||
<svg preserveAspectRatio="none" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 165 B Po Szerokość: | Wysokość: | Rozmiar: 207 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></svg>
|
||||
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 211 B Po Szerokość: | Wysokość: | Rozmiar: 228 B |
|
@ -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<float> area, int channel) {
|
||||
|
|
|
@ -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<std::byte> 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<std::byte> 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<float> tempBuffer;
|
||||
|
|
|
@ -23,6 +23,12 @@
|
|||
<FILE id="LbviBq" name="cube.obj" compile="0" resource="1" file="Resources/models/cube.obj"/>
|
||||
</GROUP>
|
||||
<GROUP id="{F3C16D02-63B4-E3DA-7498-901173C37D6C}" name="oscilloscope">
|
||||
<GROUP id="{DD5ACF8F-4E4F-E277-176A-5FD4A9717037}" name="juce">
|
||||
<FILE id="FCfppP" name="check_native_interop.js" compile="0" resource="1"
|
||||
file="Resources/oscilloscope/juce/check_native_interop.js"/>
|
||||
<FILE id="ZEUE5w" name="index.js" compile="0" resource="1" file="Resources/oscilloscope/juce/index.js"/>
|
||||
<FILE id="hWk293" name="package.json" compile="0" resource="1" file="Resources/oscilloscope/juce/package.json"/>
|
||||
</GROUP>
|
||||
<FILE id="dNtZYs" name="noise.jpg" compile="0" resource="1" file="Resources/oscilloscope/noise.jpg"/>
|
||||
<FILE id="YPMnjq" name="oscilloscope.html" compile="0" resource="1"
|
||||
file="Resources/oscilloscope/oscilloscope.html"/>
|
||||
|
|
Ładowanie…
Reference in New Issue