import React, { useLayoutEffect, useRef, useState } from 'react' import { render } from '@gpuix/react' import type { ImgInstance, PublicInstance } from '@gpuix/react' const WIDTH = 720 const HEIGHT = 96 const PIXEL_RATIO = 2 function rgbaWaveform(phase: number): Buffer { const width = WIDTH * PIXEL_RATIO const height = HEIGHT * PIXEL_RATIO const bytes = Buffer.alloc(width * height * 4) for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = (y * width + x) * 4 bytes[i] = 18 bytes[i + 1] = 22 bytes[i + 2] = 38 bytes[i + 3] = 255 } } const mid = (height - 1) / 2 for (let x = 0; x < width; x++) { const sample = Math.sin((x / width) * Math.PI * 6 + phase) * 0.72 const y = Math.round(mid - sample * mid) const i = (y * width + x) * 4 bytes[i] = 92 bytes[i + 1] = 169 bytes[i + 2] = 255 bytes[i + 3] = 255 } return bytes } function Waveform({ phase }: { phase: number }) { const img = useRef(null) useLayoutEffect(() => { img.current?.setImagePixels( WIDTH * PIXEL_RATIO, HEIGHT * PIXEL_RATIO, rgbaWaveform(phase), ) }, [phase]) return ( ) } const CLIPS = ['Intro', 'Verse', 'Chorus', 'Bridge', 'Outro'] export function WaveformApp({ phase = 0, }: { phase?: number } = {}) { const lastClip = useRef(null) return (
Waveform
{CLIPS.map((name, index) => (
{name}
))}
lastClip.current?.scrollIntoView?.()} > Jump to outro
) } function App() { const [phase, setPhase] = useState(0) return (
setPhase((value) => value + 0.6)} >
) } if (import.meta.main) { render(, { title: 'GPUIX Waveform', width: 800, height: 420, focus: process.env.GPUIX_BACKGROUND !== '1', }) }