Files
var-monorepo/apps/dispatch/app/_helpers/radioEffect.ts
2025-09-20 00:28:53 +02:00

63 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Helper function for distortion curve generation
function createDistortionCurve(amount: number): Float32Array<ArrayBuffer> {
const k = typeof amount === "number" ? amount : 50;
const nSamples = 44100;
const curve = new Float32Array(nSamples);
const deg = Math.PI / 180;
for (let i = 1; i < nSamples; i += 1) {
const x = (i * 2) / nSamples - 1;
curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
}
return curve;
}
export const getRadioStream = (stream: MediaStream, volume: number): MediaStream | null => {
try {
const audioContext = new window.AudioContext();
const sourceNode = audioContext.createMediaStreamSource(stream);
const destinationNode = audioContext.createMediaStreamDestination();
const gainNode = audioContext.createGain();
gainNode.gain.setValueAtTime(volume, audioContext.currentTime); // Lower gain for reduced volume
// Create distortion node to simulate radio-like audio
const distortionNode = audioContext.createWaveShaper();
distortionNode.curve = createDistortionCurve(15);
distortionNode.oversample = "none";
// Compressor node for dynamic range compression
const compressorNode = audioContext.createDynamicsCompressor();
compressorNode.threshold.setValueAtTime(-60, audioContext.currentTime); // Lower threshold for more compression
compressorNode.knee.setValueAtTime(30, audioContext.currentTime); // Slightly softer knee for smoother compression
compressorNode.ratio.setValueAtTime(15, audioContext.currentTime); // Higher ratio for stronger compression
compressorNode.attack.setValueAtTime(0.002, audioContext.currentTime); // Faster attack for more aggressive compression
compressorNode.release.setValueAtTime(0.15, audioContext.currentTime); // Faster release for a "snappier" sound
// Low-pass filter to simulate reduced fidelity
const lowPassFilterNode = audioContext.createBiquadFilter();
lowPassFilterNode.type = "lowpass";
lowPassFilterNode.frequency.setValueAtTime(2800, audioContext.currentTime);
// High-pass filter to reduce low-end noise
const highPassFilterNode = audioContext.createBiquadFilter();
highPassFilterNode.type = "highpass";
highPassFilterNode.frequency.setValueAtTime(400, audioContext.currentTime);
// Chain the nodes
sourceNode
.connect(distortionNode) // Apply distortion first
.connect(highPassFilterNode) // Remove low-end noise
.connect(lowPassFilterNode) // Simulate reduced fidelity
.connect(compressorNode) // Apply compression
.connect(gainNode) // Connect to gain node
.connect(destinationNode); // Connect to output
// Return the modified stream
return destinationNode.stream;
} catch (error) {
console.error("Error processing audio stream:", error);
return null; // In case of error, return null so that the page doesnt hang
}
};