Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the waveform creation #12681

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading