Get audio sent to visualiser, and add buttons

pull/249/head
James H Ball 2024-08-04 19:47:41 +01:00 zatwierdzone przez James H Ball
rodzic 0e62c7667a
commit bda414de92
11 zmienionych plików z 848 dodań i 291 usunięć

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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,
};

Wyświetl plik

@ -0,0 +1,4 @@
{
"name": "juce-framework-frontend",
"version": "7.0.7"
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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();

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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;

Wyświetl plik

@ -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"/>