mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-22 11:08:35 +00:00
commit
d0f8a612ad
@ -146,6 +146,9 @@ const generic_params = [
|
|||||||
*/
|
*/
|
||||||
['bank'],
|
['bank'],
|
||||||
|
|
||||||
|
['analyze'], // analyser node send amount 0 - 1 (used by scope)
|
||||||
|
['fft'], // fftSize of analyser
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
|
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
|
||||||
* Note that the decay is only audible if the sustain value is lower than 1.
|
* Note that the decay is only audible if the sustain value is lower than 1.
|
||||||
|
|||||||
@ -9,25 +9,26 @@ import { Pattern, getTime, State, TimeSpan } from './index.mjs';
|
|||||||
export const getDrawContext = (id = 'test-canvas') => {
|
export const getDrawContext = (id = 'test-canvas') => {
|
||||||
let canvas = document.querySelector('#' + id);
|
let canvas = document.querySelector('#' + id);
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
const scale = 2; // 2 = crisp on retina screens
|
||||||
canvas = document.createElement('canvas');
|
canvas = document.createElement('canvas');
|
||||||
canvas.id = id;
|
canvas.id = id;
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth * scale;
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight * scale;
|
||||||
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
|
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
|
||||||
document.body.prepend(canvas);
|
document.body.prepend(canvas);
|
||||||
let timeout;
|
let timeout;
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
timeout && clearTimeout(timeout);
|
timeout && clearTimeout(timeout);
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth * scale;
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight * scale;
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return canvas.getContext('2d');
|
return canvas.getContext('2d');
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
|
||||||
if (window.strudelAnimation) {
|
if (window.strudelAnimation) {
|
||||||
cancelAnimationFrame(window.strudelAnimation);
|
cancelAnimationFrame(window.strudelAnimation);
|
||||||
}
|
}
|
||||||
@ -59,7 +60,7 @@ Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
|||||||
|
|
||||||
export const cleanupDraw = (clearScreen = true) => {
|
export const cleanupDraw = (clearScreen = true) => {
|
||||||
const ctx = getDrawContext();
|
const ctx = getDrawContext();
|
||||||
clearScreen && ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
|
||||||
if (window.strudelAnimation) {
|
if (window.strudelAnimation) {
|
||||||
cancelAnimationFrame(window.strudelAnimation);
|
cancelAnimationFrame(window.strudelAnimation);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,6 +121,36 @@ function getReverb(orbit, duration = 2) {
|
|||||||
return reverbs[orbit];
|
return reverbs[orbit];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let analyser, analyserData /* s = {} */;
|
||||||
|
export function getAnalyser(/* orbit, */ fftSize = 2048) {
|
||||||
|
if (!analyser /*s [orbit] */) {
|
||||||
|
const analyserNode = getAudioContext().createAnalyser();
|
||||||
|
analyserNode.fftSize = fftSize;
|
||||||
|
// getDestination().connect(analyserNode);
|
||||||
|
analyser /* s[orbit] */ = analyserNode;
|
||||||
|
//analyserData = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
analyserData = new Float32Array(analyser.frequencyBinCount);
|
||||||
|
}
|
||||||
|
if (analyser /* s[orbit] */.fftSize !== fftSize) {
|
||||||
|
analyser /* s[orbit] */.fftSize = fftSize;
|
||||||
|
//analyserData = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
analyserData = new Float32Array(analyser.frequencyBinCount);
|
||||||
|
}
|
||||||
|
return analyser /* s[orbit] */;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAnalyzerData(type = 'time') {
|
||||||
|
const getter = {
|
||||||
|
time: () => analyser?.getFloatTimeDomainData(analyserData),
|
||||||
|
frequency: () => analyser?.getFloatFrequencyData(analyserData),
|
||||||
|
}[type];
|
||||||
|
if (!getter) {
|
||||||
|
throw new Error(`getAnalyzerData: ${type} not supported. use one of ${Object.keys(getter).join(', ')}`);
|
||||||
|
}
|
||||||
|
getter();
|
||||||
|
return analyserData;
|
||||||
|
}
|
||||||
|
|
||||||
function effectSend(input, effect, wet) {
|
function effectSend(input, effect, wet) {
|
||||||
const send = gainNode(wet);
|
const send = gainNode(wet);
|
||||||
input.connect(send);
|
input.connect(send);
|
||||||
@ -167,6 +197,8 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
room,
|
room,
|
||||||
size = 2,
|
size = 2,
|
||||||
velocity = 1,
|
velocity = 1,
|
||||||
|
analyze, // analyser wet
|
||||||
|
fft = 8, // fftSize 0 - 10
|
||||||
} = value;
|
} = value;
|
||||||
gain *= velocity; // legacy fix for velocity
|
gain *= velocity; // legacy fix for velocity
|
||||||
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
||||||
@ -241,12 +273,19 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
reverbSend = effectSend(post, reverbNode, room);
|
reverbSend = effectSend(post, reverbNode, room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// analyser
|
||||||
|
let analyserSend;
|
||||||
|
if (analyze) {
|
||||||
|
const analyserNode = getAnalyser(/* orbit, */ 2 ** (fft + 5));
|
||||||
|
analyserSend = effectSend(post, analyserNode, analyze);
|
||||||
|
}
|
||||||
|
|
||||||
// connect chain elements together
|
// connect chain elements together
|
||||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||||
|
|
||||||
// toDisconnect = all the node that should be disconnected in onended callback
|
// toDisconnect = all the node that should be disconnected in onended callback
|
||||||
// this is crucial for performance
|
// this is crucial for performance
|
||||||
toDisconnect = chain.concat([delaySend, reverbSend]);
|
toDisconnect = chain.concat([delaySend, reverbSend, analyserSend]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
|
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
|
||||||
|
|||||||
@ -5,4 +5,5 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './webaudio.mjs';
|
export * from './webaudio.mjs';
|
||||||
|
export * from './scope.mjs';
|
||||||
export * from 'superdough';
|
export * from 'superdough';
|
||||||
|
|||||||
88
packages/webaudio/scope.mjs
Normal file
88
packages/webaudio/scope.mjs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Pattern, getDrawContext, clamp } from '@strudel.cycles/core';
|
||||||
|
import { analyser, getAnalyzerData } from 'superdough';
|
||||||
|
|
||||||
|
export function drawTimeScope(
|
||||||
|
analyser,
|
||||||
|
{ align = true, color = 'white', thickness = 3, scale = 0.25, pos = 0.75, next = 1, trigger = 0 } = {},
|
||||||
|
) {
|
||||||
|
const ctx = getDrawContext();
|
||||||
|
const dataArray = getAnalyzerData('time');
|
||||||
|
|
||||||
|
ctx.lineWidth = thickness;
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
let canvas = ctx.canvas;
|
||||||
|
|
||||||
|
const bufferSize = analyser.frequencyBinCount;
|
||||||
|
let triggerIndex = align
|
||||||
|
? Array.from(dataArray).findIndex((v, i, arr) => i && arr[i - 1] > -trigger && v <= -trigger)
|
||||||
|
: 0;
|
||||||
|
triggerIndex = Math.max(triggerIndex, 0); // fallback to 0 when no trigger is found
|
||||||
|
|
||||||
|
const sliceWidth = (canvas.width * 1.0) / bufferSize;
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
for (let i = triggerIndex; i < bufferSize; i++) {
|
||||||
|
const v = dataArray[i] + 1;
|
||||||
|
const y = (scale * (v - 1) + pos) * canvas.height;
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
x += sliceWidth;
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function drawFrequencyScope(
|
||||||
|
analyser,
|
||||||
|
{ color = 'white', scale = 0.25, pos = 0.75, lean = 0.5, min = -150, max = 0 } = {},
|
||||||
|
) {
|
||||||
|
const dataArray = getAnalyzerData('frequency');
|
||||||
|
const ctx = getDrawContext();
|
||||||
|
const canvas = ctx.canvas;
|
||||||
|
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
const bufferSize = analyser.frequencyBinCount;
|
||||||
|
const sliceWidth = (canvas.width * 1.0) / bufferSize;
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
for (let i = 0; i < bufferSize; i++) {
|
||||||
|
const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1);
|
||||||
|
const v = normalized * scale;
|
||||||
|
const h = v * canvas.height;
|
||||||
|
const y = (pos - v * lean) * canvas.height;
|
||||||
|
|
||||||
|
ctx.fillRect(x, y, Math.max(sliceWidth, 1), h);
|
||||||
|
x += sliceWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearScreen(smear = 0, smearRGB = `0,0,0`) {
|
||||||
|
const ctx = getDrawContext();
|
||||||
|
if (!smear) {
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = `rgba(${smearRGB},${1 - smear})`;
|
||||||
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.fscope = function (config = {}) {
|
||||||
|
return this.analyze(1).draw(() => {
|
||||||
|
clearScreen(config.smear);
|
||||||
|
analyser && drawFrequencyScope(analyser, config);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.tscope = function (config = {}) {
|
||||||
|
return this.analyze(1).draw(() => {
|
||||||
|
clearScreen(config.smear);
|
||||||
|
analyser && drawTimeScope(analyser, config);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.scope = Pattern.prototype.tscope;
|
||||||
Loading…
x
Reference in New Issue
Block a user