diff --git a/src/AudioVisualisation.ts b/src/AudioVisualisation.ts index 61d9c76..c3ce67a 100644 --- a/src/AudioVisualisation.ts +++ b/src/AudioVisualisation.ts @@ -123,6 +123,8 @@ export interface OscilloscopeConfig { fftSize: number; // multiples of 256 orientation: "horizontal" | "vertical"; mode: "3D" | "scope" | "freqscope"; + offsetX: number; + offsetY: number; size: number; } @@ -142,47 +144,76 @@ export const runOscilloscope = ( let dataArray = new Float32Array(analyzer.frequencyBinCount); let freqDataArray = new Uint8Array(analyzer.frequencyBinCount); const canvasCtx = canvas.getContext("2d")!; - const WIDTH = canvas.width; - const HEIGHT = canvas.height; let lastDrawTime = 0; let frameInterval = 1000 / 30; - function drawFrequencyScope() { + function drawFrequencyScope( + width: number, + height: number, + offset_height: number, + offset_width: number + ) { + // Existing setup code... + analyzer.fftSize = app.osc.fftSize * 4; analyzer.getByteFrequencyData(freqDataArray); - canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + canvasCtx.clearRect(0, 0, width, height); - // Ensure the number of bars does not exceed the frequency data length - const numBars = freqDataArray.length; - const barWidth = WIDTH / numBars; + const numBars = Math.min( + freqDataArray.length, + app.osc.orientation === "horizontal" ? width : height + ); + const barWidth = + app.osc.orientation === "horizontal" ? width / numBars : height / numBars; let barHeight; - let x = 0; + let x = 0, + y = 0; for (let i = 0; i < numBars; i++) { - // Calculate index in the frequency data array using logarithmic scale - const logIndex = Math.floor( - Math.pow(i / numBars, 2) * freqDataArray.length - ); barHeight = Math.floor( - freqDataArray[logIndex] * ((HEIGHT / 256) * app.osc.size) + freqDataArray[i] * ((height / 256) * app.osc.size) ); - // Color configuration - if (app.osc.color === "random") { - canvasCtx.fillStyle = `hsl(${Math.random() * 360}, 100%, 50%)`; + // Create gradient based on orientation + let gradient; + if (app.osc.orientation === "horizontal") { + gradient = canvasCtx.createLinearGradient(0, 0, width / 2, 0); } else { - const gradient = canvasCtx.createLinearGradient(0, 0, WIDTH / 2, 0); - gradient.addColorStop(0, app.osc.color || `rgb(255, 255, 255)`); - gradient.addColorStop(1, `rgb(${barHeight + 50},50,50)`); - canvasCtx.fillStyle = gradient; + gradient = canvasCtx.createLinearGradient(0, 0, 0, height / 2); } + gradient.addColorStop(0, app.osc.color || `rgb(255, 255, 255)`); + gradient.addColorStop(1, `rgb(${barHeight + 50},50,50)`); + canvasCtx.fillStyle = gradient; - canvasCtx.fillRect(x, (HEIGHT - barHeight) / 2, barWidth + 1, barHeight); - - x += barWidth; + if (app.osc.orientation === "horizontal") { + canvasCtx.fillRect( + x + offset_width, // Apply horizontal offset here + (height - barHeight) / 2 + offset_height, // Apply vertical offset here + barWidth + 1, + barHeight + ); + x += barWidth; + } else { + canvasCtx.fillRect( + (width - barHeight) / 2 + offset_width, // Apply horizontal offset here + y + offset_height, // Apply vertical offset here + barHeight, + barWidth + 1 + ); + y += barWidth; + } } } function draw() { + // Update the canvas position on each cycle + const WIDTH = canvas.width; + const HEIGHT = canvas.height; + const OFFSET_WIDTH = app.osc.offsetX; + const OFFSET_HEIGHT = app.osc.offsetY; + + // Apply an offset to the canvas! + canvasCtx.setTransform(1, 0, 0, 1, OFFSET_WIDTH, OFFSET_HEIGHT); + const currentTime = Date.now(); requestAnimationFrame(draw); if (currentTime - lastDrawTime < frameInterval) { @@ -191,7 +222,12 @@ export const runOscilloscope = ( lastDrawTime = currentTime; if (!app.osc.enabled) { - canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + canvasCtx.clearRect( + -OFFSET_WIDTH, + -OFFSET_HEIGHT, + WIDTH + 2 * OFFSET_WIDTH, + HEIGHT + 2 * OFFSET_HEIGHT + ); return; } @@ -206,9 +242,13 @@ export const runOscilloscope = ( canvasCtx.fillStyle = "rgba(0, 0, 0, 0)"; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); if (app.clock.time_position.pulse % app.osc.refresh == 0) { - canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + canvasCtx.clearRect( + -OFFSET_WIDTH, + -OFFSET_HEIGHT, + WIDTH + 2 * OFFSET_WIDTH, + HEIGHT + 2 * OFFSET_HEIGHT + ); } - canvasCtx.lineWidth = app.osc.thickness; if (app.osc.color === "random") { @@ -246,7 +286,7 @@ export const runOscilloscope = ( } if (app.osc.mode === "freqscope") { - drawFrequencyScope(); + drawFrequencyScope(WIDTH, HEIGHT, OFFSET_HEIGHT, OFFSET_WIDTH); } else if (app.osc.mode === "3D") { for (let i = startIndex; i < dataArray.length; i += 2) { const x = (dataArray[i] * WIDTH * app.osc.size) / 2 + WIDTH / 4; diff --git a/src/documentation/oscilloscope.ts b/src/documentation/oscilloscope.ts index d1103d9..4c7eea0 100644 --- a/src/documentation/oscilloscope.ts +++ b/src/documentation/oscilloscope.ts @@ -14,6 +14,8 @@ scope({ enabled: true, // off by default color: "#fdba74", // any valid CSS color or "random" thickness: 4, // stroke thickness + offsetY: 0, // Horizontal offset + offsetX: 0, // Vertical offset fftSize: 256, // multiples of 128 orientation: "horizontal", // "vertical" or "horizontal" mode: "scope" | "3D" | "freqscope", // scope mode diff --git a/src/main.ts b/src/main.ts index ee0aa95..45904ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,6 +76,8 @@ export class Editor { refresh: 1, fftSize: 1024, orientation: "horizontal", + offsetX: 0, + offsetY: 0, mode: "scope", size: 1, };