mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
Merge branch 'main' into binary
This commit is contained in:
commit
8b909c54db
@ -133,3 +133,10 @@ registerWidget('_pitchwheel', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.pitchwheel({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_spectrum', (id, options = {}, pat) => {
|
||||
let _size = options.size || 200;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.spectrum({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
@ -92,7 +92,7 @@ export function repl({
|
||||
// set pattern methods that use this repl via closure
|
||||
const injectPatternMethods = () => {
|
||||
Pattern.prototype.p = function (id) {
|
||||
if (id.startsWith('_') || id.endsWith('_')) {
|
||||
if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
|
||||
// allows muting a pattern x with x_ or _x
|
||||
return silence;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export const getDrawContext = (id = 'test-canvas', options) => {
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
return canvas.getContext(contextType);
|
||||
return canvas.getContext(contextType, { willReadFrequently: true });
|
||||
};
|
||||
|
||||
let animationFrames = {};
|
||||
|
||||
@ -270,11 +270,12 @@ function getReverb(orbit, duration, fade, lp, dim, ir) {
|
||||
export let analysers = {},
|
||||
analysersData = {};
|
||||
|
||||
export function getAnalyserById(id, fftSize = 1024) {
|
||||
export function getAnalyserById(id, fftSize = 1024, smoothingTimeConstant = 0.5) {
|
||||
if (!analysers[id]) {
|
||||
// make sure this doesn't happen too often as it piles up garbage
|
||||
const analyserNode = getAudioContext().createAnalyser();
|
||||
analyserNode.fftSize = fftSize;
|
||||
analyserNode.smoothingTimeConstant = smoothingTimeConstant;
|
||||
// getDestination().connect(analyserNode);
|
||||
analysers[id] = analyserNode;
|
||||
analysersData[id] = new Float32Array(analysers[id].frequencyBinCount);
|
||||
|
||||
@ -6,4 +6,5 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
export * from './webaudio.mjs';
|
||||
export * from './scope.mjs';
|
||||
export * from './spectrum.mjs';
|
||||
export * from 'superdough';
|
||||
|
||||
69
packages/webaudio/spectrum.mjs
Normal file
69
packages/webaudio/spectrum.mjs
Normal file
@ -0,0 +1,69 @@
|
||||
import { Pattern, clamp } from '@strudel/core';
|
||||
import { getDrawContext, getTheme } from '@strudel/draw';
|
||||
import { analysers, getAnalyzerData } from 'superdough';
|
||||
|
||||
/**
|
||||
* Renders a spectrum analyzer for the incoming audio signal.
|
||||
* @name spectrum
|
||||
* @param {object} config optional config with options:
|
||||
* @param {integer} thickness line thickness in px (default 3)
|
||||
* @param {integer} speed scroll speed (default 1)
|
||||
* @param {integer} min min db (default -80)
|
||||
* @param {integer} max max db (default 0)
|
||||
* @example
|
||||
* n("<0 4 <2 3> 1>*3")
|
||||
* .off(1/8, add(n(5)))
|
||||
* .off(1/5, add(n(7)))
|
||||
* .scale("d3:minor:pentatonic")
|
||||
* .s('sine')
|
||||
* .dec(.3).room(.5)
|
||||
* ._spectrum()
|
||||
*/
|
||||
let latestColor = {};
|
||||
Pattern.prototype.spectrum = function (config = {}) {
|
||||
let id = config.id ?? 1;
|
||||
return this.analyze(id).draw(
|
||||
(haps) => {
|
||||
config.color = haps[0]?.value?.color || latestColor[id] || getTheme().foreground;
|
||||
latestColor[id] = config.color;
|
||||
drawSpectrum(analysers[id], config);
|
||||
},
|
||||
{ id },
|
||||
);
|
||||
};
|
||||
|
||||
Pattern.prototype.scope = Pattern.prototype.tscope;
|
||||
|
||||
const lastFrames = new Map();
|
||||
|
||||
function drawSpectrum(
|
||||
analyser,
|
||||
{ thickness = 3, speed = 1, min = -80, max = 0, ctx = getDrawContext(), id = 1, color } = {},
|
||||
) {
|
||||
ctx.lineWidth = thickness;
|
||||
ctx.strokeStyle = color;
|
||||
|
||||
if (!analyser) {
|
||||
// if analyser is undefined, draw straight line
|
||||
// it may be undefined when no sound has been played yet
|
||||
return;
|
||||
}
|
||||
const scrollSize = speed;
|
||||
const dataArray = getAnalyzerData('frequency', id);
|
||||
const canvas = ctx.canvas;
|
||||
ctx.fillStyle = color;
|
||||
const bufferSize = analyser.frequencyBinCount;
|
||||
let imageData = lastFrames.get(id) || ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
lastFrames.set(id, imageData);
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.putImageData(imageData, -scrollSize, 0);
|
||||
let q = canvas.width - speed;
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1);
|
||||
ctx.globalAlpha = normalized;
|
||||
const next = (Math.log(i + 1) / Math.log(bufferSize)) * canvas.height;
|
||||
const size = 2; //next - pos;
|
||||
ctx.fillRect(q, canvas.height - next, scrollSize, size);
|
||||
}
|
||||
lastFrames.set(id, ctx.getImageData(0, 0, canvas.width, canvas.height));
|
||||
}
|
||||
@ -7562,6 +7562,75 @@ exports[`runs examples > example "sometimesBy" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "spectrum" example index 0 1`] = `
|
||||
[
|
||||
"[ -5/24 ⇜ (0/1 → 1/8) | note:F4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ -2/15 ⇜ (0/1 → 1/5) | note:A4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ -1/120 ⇜ (0/1 → 1/5) ⇝ 13/40 | note:A5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 0/1 → 1/3 | note:D3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 1/8 → 11/24 | note:D4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ -1/120 ⇜ (1/5 → 13/40) | note:A5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 1/5 → 8/15 | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 13/40 → 79/120 | note:G5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 1/3 → 2/3 | note:C4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 11/24 → 19/24 | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 8/15 → 13/15 | note:F5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 79/120 → 119/120 | note:F6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 2/3 → 1/1 | note:G3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (19/24 → 1/1) ⇝ 9/8 | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (13/15 → 1/1) ⇝ 6/5 | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (119/120 → 1/1) ⇝ 53/40 | note:C6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 19/24 ⇜ (1/1 → 9/8) | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 13/15 ⇜ (1/1 → 6/5) | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 119/120 ⇜ (1/1 → 6/5) ⇝ 53/40 | note:C6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 1/1 → 4/3 | note:F3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 9/8 → 35/24 | note:F4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 119/120 ⇜ (6/5 → 53/40) | note:C6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 6/5 → 23/15 | note:A4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 53/40 → 199/120 | note:A5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 4/3 → 5/3 | note:D3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 35/24 → 43/24 | note:D4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 23/15 → 28/15 | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 199/120 → 239/120 | note:G5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 5/3 → 2/1 | note:C4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (43/24 → 2/1) ⇝ 17/8 | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (28/15 → 2/1) ⇝ 11/5 | note:F5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (239/120 → 2/1) ⇝ 93/40 | note:F6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 43/24 ⇜ (2/1 → 17/8) | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 28/15 ⇜ (2/1 → 11/5) | note:F5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 239/120 ⇜ (2/1 → 11/5) ⇝ 93/40 | note:F6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 2/1 → 7/3 | note:A3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 17/8 → 59/24 | note:A4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 239/120 ⇜ (11/5 → 93/40) | note:F6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 11/5 → 38/15 | note:D5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 93/40 → 319/120 | note:D6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 7/3 → 8/3 | note:F3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 59/24 → 67/24 | note:F4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 38/15 → 43/15 | note:A4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 319/120 → 359/120 | note:A5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 8/3 → 3/1 | note:D3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (67/24 → 3/1) ⇝ 25/8 | note:D4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (43/15 → 3/1) ⇝ 16/5 | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (359/120 → 3/1) ⇝ 133/40 | note:G5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 67/24 ⇜ (3/1 → 25/8) | note:D4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 43/15 ⇜ (3/1 → 16/5) | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 359/120 ⇜ (3/1 → 16/5) ⇝ 133/40 | note:G5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 3/1 → 10/3 | note:C4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 25/8 → 83/24 | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 359/120 ⇜ (16/5 → 133/40) | note:G5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 16/5 → 53/15 | note:F5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 133/40 → 439/120 | note:F6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 10/3 → 11/3 | note:G3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 83/24 → 91/24 | note:G4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 53/15 → 58/15 | note:C5 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 439/120 → 479/120 | note:C6 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ 11/3 → 4/1 | note:F3 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (91/24 → 4/1) ⇝ 33/8 | note:F4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (58/15 → 4/1) ⇝ 21/5 | note:A4 s:sine decay:0.3 room:0.5 ]",
|
||||
"[ (479/120 → 4/1) ⇝ 173/40 | note:A5 s:sine decay:0.3 room:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "speed" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/6 | s:bd speed:1 ]",
|
||||
|
||||
@ -134,6 +134,9 @@ strudel.Pattern.prototype._pitchwheel = function () {
|
||||
strudel.Pattern.prototype._pianoroll = function () {
|
||||
return this;
|
||||
};
|
||||
strudel.Pattern.prototype._spectrum = function () {
|
||||
return this;
|
||||
};
|
||||
strudel.Pattern.prototype.markcss = function () {
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -99,6 +99,10 @@ What follows is the API doc of all the options you can pass:
|
||||
|
||||
<JsDoc client:idle name="pitchwheel" h={0} />
|
||||
|
||||
## Spectrum
|
||||
|
||||
<JsDoc client:idle name="spectrum" h={0} />
|
||||
|
||||
## markcss
|
||||
|
||||
<JsDoc client:idle name="markcss" h={0} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user