Connor McCutcheon
/ Music
spectrum.mjs
mjs
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));
}
No comments yet.