1import { Pattern, clamp } from '@strudel/core'; 2import { getDrawContext, getTheme } from '@strudel/draw'; 3import { analysers, getAnalyzerData } from 'superdough'; 4 5export function drawTimeScope( 6 analyser, 7 { 8 align = true, 9 color = 'white', 10 thickness = 3, 11 scale = 0.25, 12 pos = 0.75, 13 trigger = 0, 14 ctx = getDrawContext(), 15 id = 1, 16 } = {}, 17) { 18 ctx.lineWidth = thickness; 19 ctx.strokeStyle = color; 20 let canvas = ctx.canvas; 21 22 if (!analyser) { 23 // if analyser is undefined, draw straight line 24 // it may be undefined when no sound has been played yet 25 ctx.beginPath(); 26 let y = pos * canvas.height; 27 ctx.moveTo(0, y); 28 ctx.lineTo(canvas.width, y); 29 ctx.stroke(); 30 return; 31 } 32 const dataArray = getAnalyzerData('time', id); 33 34 ctx.beginPath(); 35 36 const bufferSize = analyser.frequencyBinCount; 37 let triggerIndex = align 38 ? Array.from(dataArray).findIndex((v, i, arr) => i && arr[i - 1] > -trigger && v <= -trigger) 39 : 0; 40 triggerIndex = Math.max(triggerIndex, 0); // fallback to 0 when no trigger is found 41 42 const sliceWidth = (canvas.width * 1.0) / bufferSize; 43 let x = 0; 44 for (let i = triggerIndex; i < bufferSize; i++) { 45 const v = dataArray[i] + 1; 46 const y = (pos - scale * (v - 1)) * canvas.height; 47 48 if (i === 0) { 49 ctx.moveTo(x, y); 50 } else { 51 ctx.lineTo(x, y); 52 } 53 x += sliceWidth; 54 } 55 ctx.stroke(); 56} 57 58export function drawFrequencyScope( 59 analyser, 60 { color = 'white', scale = 0.25, pos = 0.75, lean = 0.5, min = -150, max = 0, ctx = getDrawContext(), id = 1 } = {}, 61) { 62 if (!analyser) { 63 ctx.beginPath(); 64 let y = pos * canvas.height; 65 ctx.moveTo(0, y); 66 ctx.lineTo(canvas.width, y); 67 ctx.stroke(); 68 return; 69 } 70 const dataArray = getAnalyzerData('frequency', id); 71 const canvas = ctx.canvas; 72 73 ctx.fillStyle = color; 74 const bufferSize = analyser.frequencyBinCount; 75 const sliceWidth = (canvas.width * 1.0) / bufferSize; 76 77 let x = 0; 78 for (let i = 0; i < bufferSize; i++) { 79 const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1); 80 const v = normalized * scale; 81 const h = v * canvas.height; 82 const y = (pos - v * lean) * canvas.height; 83 84 ctx.fillRect(x, y, Math.max(sliceWidth, 1), h); 85 x += sliceWidth; 86 } 87} 88 89function clearScreen(smear = 0, smearRGB = `0,0,0`, ctx = getDrawContext()) { 90 if (!smear) { 91 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 92 } else { 93 ctx.fillStyle = `rgba(${smearRGB},${1 - smear})`; 94 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 95 } 96}
Renders an oscilloscope for the frequency domain of the audio signal. @name fscope @tags visualization @param {string} color line color as hex or color name. defaults to white. @param {number} scale scales the y-axis. Defaults to 0.25 @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen @param {number} lean y-axis alignment where 0 = top and 1 = bottom @param {number} min min value @param {number} max max value @example s("sawtooth").fscope()
Renders an oscilloscope for the time domain of the audio signal. @name scope @synonyms tscope @tags visualization @param {object} config optional config with options: @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1 @param {string} color line color as hex or color name. defaults to white. @param {number} thickness line thickness. defaults to 3 @param {number} scale scales the y-axis. Defaults to 0.25 @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen @param {number} trigger amplitude value that is used to align the scope. defaults to 0. @example s("sawtooth")._scope()
137let latestColor = {}; 138Pattern.prototype.tscope = function (config = {}) { 139 let id = config.id ?? 1; 140 return this.analyze(id).draw( 141 (haps) => { 142 config.color = haps[0]?.value?.color || getTheme().foreground; 143 latestColor[id] = config.color; 144 clearScreen(config.smear, '0,0,0', config.ctx); 145 drawTimeScope(analysers[id], config); 146 }, 147 { id }, 148 ); 149};
151Pattern.prototype.scope = Pattern.prototype.tscope;