diff --git a/dotcom-rendering/src/components/AudioPlayer/components/WaveForm.tsx b/dotcom-rendering/src/components/AudioPlayer/components/WaveForm.tsx index 03e386a9b9..a1bf35b8e9 100644 --- a/dotcom-rendering/src/components/AudioPlayer/components/WaveForm.tsx +++ b/dotcom-rendering/src/components/AudioPlayer/components/WaveForm.tsx @@ -11,57 +11,41 @@ import { useId, useMemo } from 'react'; * the same results, given the same seed. * * Copilot helped me with it... + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator */ -const getSeededRandomNumberGenerator = (seedString: string) => { +function* waveformGenerator( + /** the URL of the audio source file */ + url: string, + /** the number of bars to generate, i.e. the length of the array */ + bars: number, + /** a destination range for values */ + [min, max]: [number, number], +) { const modulus = 2147483648; + /** a sort of hash of the URL */ + const seed = url + .split('') + .reduce( + (sum, character) => (sum + character.charCodeAt(0)) % modulus, + 0, + ); const multiplier = 1103515245; const increment = 12345; - // convert string to numerical seed - let hashedSeed = 0; - for (let i = 0; i < seedString.length; i++) { - const char = seedString.charCodeAt(i); - hashedSeed = (hashedSeed << 5) - hashedSeed + char; - hashedSeed |= 0; // Convert to 32bit integer - } - - const seed = Math.abs(hashedSeed) % modulus; let state = seed; - - return function () { + let count = 0; + while (count++ < bars) { state = (multiplier * state + increment) % modulus; - return state / modulus; - }; -}; - -/** - * Compresses an of values to a range between the threshold and the existing - * maximum. - */ -const compress = (array: number[], threshold: number) => { - const minValue = Math.min(...array); - const maxValue = Math.max(...array); - - return array.map( - (value) => - ((value - minValue) / (maxValue - minValue)) * - (maxValue - threshold) + - threshold, - ); -}; - -// Generate an array of fake audio peaks based on the URL -function generateWaveform(url: string, bars: number) { - const getSeededRandomNumber = getSeededRandomNumberGenerator(url); - - // Generate an array of fake peaks, pseudo random numbers seeded by the URL - const peaks = Array.from( - { length: bars }, - () => getSeededRandomNumber() * 100, - ); - - // Return the compressed fake audio data (like a podcast would be) - return compress(peaks, 60); + /** Get a number in the range [0, 1] */ + const normalised = state / modulus; + /** Compress the amplitude of the fake audio data, like a podcast would */ + const compressed = min + (max - min) * normalised; + /** sub-pixel precision is pointless data to send over the wire */ + const rounded = Math.round(compressed); + + yield rounded; + } } type Theme = { @@ -97,7 +81,10 @@ export const WaveForm = ({ ...props }: Props) => { // memoise the waveform data so they aren't recalculated on every render - const barHeights = useMemo(() => generateWaveform(src, bars), [src, bars]); + const barHeights = useMemo( + () => Array.from(waveformGenerator(src, bars, [60, 100])), + [src, bars], + ); const totalWidth = useMemo( () => bars * (barWidth + gap) - gap, [bars, barWidth, gap],