Skip to content

Commit

Permalink
Simplify the waveform creation (#12681)
Browse files Browse the repository at this point in the history
  • Loading branch information
sndrs authored Oct 25, 2024
1 parent 0b3dd2e commit e069ffd
Showing 1 changed file with 22 additions and 54 deletions.
76 changes: 22 additions & 54 deletions dotcom-rendering/src/components/AudioPlayer/components/WaveForm.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { useId, useMemo } from 'react';

const sumArray = (array: number[]) => array.reduce((a, b) => a + b, 0);

/**
* Pseudo random number generator generator ([linear congruential
* generator](https://en.wikipedia.org/wiki/Linear_congruential_generator)).
*
* I'll be honest, I don't fully understand it, but it creates a pseudo random
* number generator based on a seed, in this case an array of numbers.
* number generator based on a seed, in this case a string.
*
* It's deterministic, so calls to the function it returns will always return
* the same results.
* the same results, given the same seed.
*
* Copilot helped me with it...
*/
const getSeededRandomNumberGenerator = (array: number[]) => {
const getSeededRandomNumberGenerator = (seedString: string) => {
const modulus = 2147483648;
const seed = sumArray(array) % modulus;
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 () {
Expand All @@ -28,21 +34,6 @@ const getSeededRandomNumberGenerator = (array: number[]) => {
};
};

function shuffle(array: number[]) {
// Create a random number generator that's seeded with array.
const getSeededRandomNumber = getSeededRandomNumberGenerator(array);

// Sort the array using the seeded random number generator. This means that
// the same array will always be sorted in the same (pseudo random) way.
return array.sort(() => getSeededRandomNumber() - getSeededRandomNumber());
}

// normalize the amplitude of the fake audio data
const normalizeAmplitude = (data: number[]) => {
const multiplier = Math.pow(Math.max(...data), -1);
return data.map((n) => n * multiplier * 100);
};

/**
* Compresses an of values to a range between the threshold and the existing
* maximum.
Expand All @@ -52,48 +43,25 @@ const compress = (array: number[], threshold: number) => {
const maxValue = Math.max(...array);

return array.map(
(x) =>
((x - minValue) / (maxValue - minValue)) * (maxValue - threshold) +
(value) =>
((value - minValue) / (maxValue - minValue)) *
(maxValue - threshold) +
threshold,
);
};

/** Returns a string of the specified length, repeating the input string as necessary. */
function padString(str: string, length: number) {
// Repeat the string until it is longer than the desired length
const result = str.repeat(Math.ceil(length / str.length));

// Return the truncated result to the specified length
return result.slice(0, length);
}

// Generate an array of fake audio peaks based on the URL
function generateWaveform(url: string, bars: number) {
// convert the URL to a base64 string
const base64 = btoa(url);

// Pad the base64 string to the number of bars we want
const stringOfBarLength = padString(base64, bars);
const getSeededRandomNumber = getSeededRandomNumberGenerator(url);

// Convert the string to an array of char codes (fake audio data)
const valuesFromString = Array.from(stringOfBarLength).map((_, i) =>
stringOfBarLength.charCodeAt(i),
// Generate an array of fake peaks, pseudo random numbers seeded by the URL
const peaks = Array.from(
{ length: bars },
() => getSeededRandomNumber() * 100,
);

// Shuffle (sort) the fake audio data using a deterministic algorithm. This
// means the same URL will always produce the same waveform, but the
// waveforms of two similar URLs (e.g. guardian podcast URLs) won't _look_
// all that similar.
const shuffled = shuffle(valuesFromString);

// Normalize the amplitude of the fake audio data
const normalized = normalizeAmplitude(shuffled);

// Compress the amplitude of the fake audio data, like a podcast would
const compressed = compress(normalized, 60);

// Return the normalized the amplitude of the fake audio data
return compressed;
// Return the compressed fake audio data (like a podcast would be)
return compress(peaks, 60);
}

type Theme = {
Expand Down

0 comments on commit e069ffd

Please sign in to comment.