<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Audio Distortion Tool</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>Audio Distortion (Anti-Moderation) Tool</h1>
<input type="file" id="audioFile" accept="audio/*" />
<form id="audioControls">
<label
>Distortion Type:
<select id="distType">
<option value="none">None</option>
<option value="soft">Soft Clipping</option>
<option value="hard">Hard Clipping</option>
<option value="bitcrush">Bitcrusher</option>
</select>
</label>
<div class="slider-group">
<span class="slider-label">Distortion Amount:</span
><input
id="distAmount"
type="range"
min="0"
max="1000"
value="400"
/><span id="distAmountVal" class="slider-value">400</span>
</div>
<div class="slider-group">
<span class="slider-label">Bit Depth:</span
><input
id="bitDepth"
type="range"
min="8"
max="32"
value="16"
step="8"
/><span id="bitDepthVal" class="slider-value">16</span>
</div>
<div class="slider-group">
<span class="slider-label">Highpass Freq (Hz):</span
><input id="hpFreq" type="range" min="20" max="2000" value="100" /><span
id="hpFreqVal"
class="slider-value"
>100</span
>
</div>
<div class="slider-group">
<span class="slider-label">Notch1 Freq (Hz):</span
><input
id="notch1Freq"
type="range"
min="100"
max="8000"
value="500"
/><span id="notch1FreqVal" class="slider-value">500</span>
</div>
<div class="slider-group">
<span class="slider-label">Notch1 Q:</span
><input
id="notch1Q"
type="range"
min="0.1"
max="20"
value="1"
step="0.1"
/><span id="notch1QVal" class="slider-value">1.0</span>
</div>
<div class="slider-group">
<span class="slider-label">Notch2 Freq (Hz):</span
><input
id="notch2Freq"
type="range"
min="100"
max="8000"
value="2000"
/><span id="notch2FreqVal" class="slider-value">2000</span>
</div>
<div class="slider-group">
<span class="slider-label">Notch2 Q:</span
><input
id="notch2Q"
type="range"
min="0.1"
max="20"
value="1"
step="0.1"
/><span id="notch2QVal" class="slider-value">1.0</span>
</div>
<div class="slider-group">
<span class="slider-label">Mid Gain:</span
><input
id="midGain"
type="range"
min="0"
max="2"
value="1"
step="0.01"
/><span id="midGainVal" class="slider-value">1.00</span>
</div>
<div class="slider-group">
<span class="slider-label">Side Gain:</span
><input
id="sideGain"
type="range"
min="0"
max="2"
value="1"
step="0.01"
/><span id="sideGainVal" class="slider-value">1.00</span>
</div>
<div class="slider-group">
<span class="slider-label">Mid EQ Freq (Hz):</span
><input
id="midEqFreq"
type="range"
min="100"
max="8000"
value="1000"
/><span id="midEqFreqVal" class="slider-value">1000</span>
</div>
<div class="slider-group">
<span class="slider-label">Mid EQ Gain (dB):</span
><input id="midEqGain" type="range" min="-24" max="24" value="0" /><span
id="midEqGainVal"
class="slider-value"
>0</span
>
</div>
<div class="slider-group">
<span class="slider-label">Side EQ Freq (Hz):</span
><input
id="sideEqFreq"
type="range"
min="100"
max="8000"
value="1000"
/><span id="sideEqFreqVal" class="slider-value">1000</span>
</div>
<div class="slider-group">
<span class="slider-label">Side EQ Gain (dB):</span
><input
id="sideEqGain"
type="range"
min="-24"
max="24"
value="0"
/><span id="sideEqGainVal" class="slider-value">0</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant1 Freq (Hz):</span
><input
id="formant1Freq"
type="range"
min="200"
max="4000"
value="800"
/><span id="formant1FreqVal" class="slider-value">800</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant1 Q:</span
><input
id="formant1Q"
type="range"
min="0.1"
max="20"
value="1"
step="0.1"
/><span id="formant1QVal" class="slider-value">1.0</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant1 Gain (dB):</span
><input
id="formant1Gain"
type="range"
min="-24"
max="24"
value="0"
/><span id="formant1GainVal" class="slider-value">0</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant2 Freq (Hz):</span
><input
id="formant2Freq"
type="range"
min="200"
max="4000"
value="1600"
/><span id="formant2FreqVal" class="slider-value">1600</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant2 Q:</span
><input
id="formant2Q"
type="range"
min="0.1"
max="20"
value="1"
step="0.1"
/><span id="formant2QVal" class="slider-value">1.0</span>
</div>
<div class="slider-group">
<span class="slider-label">Formant2 Gain (dB):</span
><input
id="formant2Gain"
type="range"
min="-24"
max="24"
value="0"
/><span id="formant2GainVal" class="slider-value">0</span>
</div>
<div class="slider-group">
<span class="slider-label">Comp Threshold (dB):</span
><input
id="compThreshold"
type="range"
min="-60"
max="0"
value="-24"
/><span id="compThresholdVal" class="slider-value">-24</span>
</div>
<div class="slider-group">
<span class="slider-label">Comp Ratio:</span
><input id="compRatio" type="range" min="1" max="20" value="4" /><span
id="compRatioVal"
class="slider-value"
>4</span
>
</div>
<div class="slider-group">
<span class="slider-label">Comp Attack (s):</span
><input
id="compAttack"
type="range"
min="0.001"
max="1"
value="0.003"
step="0.001"
/><span id="compAttackVal" class="slider-value">0.003</span>
</div>
<div class="slider-group">
<span class="slider-label">Comp Release (s):</span
><input
id="compRelease"
type="range"
min="0.01"
max="1"
value="0.25"
step="0.01"
/><span id="compReleaseVal" class="slider-value">0.25</span>
</div>
<div class="slider-group">
<span class="slider-label">Volume:</span>
<input id="volume" type="range" min="0" max="2" value="1" step="0.01" />
<span id="volumeVal" class="slider-value">1.00</span>
</div>
<div class="slider-group">
<span class="slider-label">Bass (Low-Shelf Gain dB):</span>
<input id="bassGain" type="range" min="-24" max="24" value="0" />
<span id="bassGainVal" class="slider-value">0</span>
</div>
<div class="slider-group">
<span class="slider-label">Treble (High-Shelf Gain dB):</span>
<input id="trebleGain" type="range" min="-24" max="24" value="0" />
<span id="trebleGainVal" class="slider-value">0</span>
</div>
<div class="slider-group">
<span class="slider-label">Low-Pass Freq (Hz):</span>
<input id="lpFreq" type="range" min="20" max="20000" value="20000" />
<span id="lpFreqVal" class="slider-value">20000</span>
</div>
</form>
<button id="process">Process Audio</button>
<audio id="player" controls></audio>
<div id="fileSize"></div>
<div id="recommendation"></div>
<hr />
<input id="presetName" placeholder="Preset name" />
<button id="savePreset">Save Preset</button>
<select id="presetList"></select>
<button id="loadPreset">Load Preset</button>
<button id="deletePreset">Delete Preset</button>
<canvas id="spectrogramCanvas"></canvas>
<script type="module" src="src/index.js"></script>
</body>
</html>
{
"copyright": {
"bitDepth": "8",
"distAmount": "40",
"hpFreq": "80",
"notch1Freq": "1200",
"notch1Q": "6",
"notch2Freq": "3000",
"notch2Q": "6",
"midGain": "0.2",
"sideGain": "0.8",
"midEqFreq": "1500",
"midEqGain": "-8",
"sideEqFreq": "5000",
"sideEqGain": "-4",
"formant1Freq": "1000",
"formant1Q": "4",
"formant1Gain": "0",
"formant2Freq": "2200",
"formant2Q": "4",
"formant2Gain": "0",
"compThreshold": "-30",
"compRatio": "6",
"compAttack": "0.02",
"compRelease": "0.15"
},
"swear": {
"bitDepth": "8",
"distAmount": "100",
"hpFreq": "100",
"notch1Freq": "800",
"notch1Q": "4",
"notch2Freq": "2000",
"notch2Q": "4",
"midGain": "0.15",
"sideGain": "1.2",
"midEqFreq": "1000",
"midEqGain": "-12",
"sideEqFreq": "6000",
"sideEqGain": "-8",
"formant1Freq": "1200",
"formant1Q": "6",
"formant1Gain": "-8",
"formant2Freq": "2400",
"formant2Q": "6",
"formant2Gain": "-8",
"compThreshold": "-25",
"compRatio": "10",
"compAttack": "0.005",
"compRelease": "0.1"
},
"scvd": {
"bitDepth": 16,
"distAmount": 20,
"hpFreq": 80,
"notch1Freq": 1000,
"notch1Q": 4,
"notch2Freq": 3000,
"notch2Q": 5,
"midGain": 0.5,
"sideGain": 1.5,
"midEqFreq": 1800,
"midEqGain": -8,
"sideEqFreq": 8000,
"sideEqGain": 4,
"formant1Freq": 1200,
"formant1Q": 1.2,
"formant1Gain": -4,
"formant2Freq": 2500,
"formant2Q": 1.5,
"formant2Gain": -3,
"compThreshold": -18,
"compRatio": 4,
"compAttack": 0.003,
"compRelease": 0.25
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f9f9f9;
max-width: 900px;
margin: auto;
padding: 2rem;
color: #222;
}
h1 {
text-align: center;
margin-bottom: 2rem;
}
input[type="file"] {
display: block;
margin: 0 auto 1.5rem auto;
}
form {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.slider-group {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
gap: 1rem;
}
.slider-label {
flex: 1;
font-weight: 500;
}
.slider-value {
width: 50px;
text-align: right;
font-size: 0.9rem;
color: #333;
}
input[type="range"] {
flex: 2;
}
label select {
width: 100%;
margin-top: 0.5rem;
}
label {
display: block;
margin-bottom: 1.5rem;
font-weight: 500;
}
button {
background-color: #007BFF;
color: white;
padding: 0.6rem 1.2rem;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
margin-right: 0.5rem;
transition: background 0.2s ease;
}
button:hover {
background-color: #0056b3;
}
#player {
display: block;
margin: 2rem auto 1rem auto;
width: 100%;
}
#fileSize, #recommendation {
margin-top: 0.5rem;
font-size: 0.95rem;
color: #444;
}
hr {
margin: 2rem 0;
}
#presetName {
padding: 0.4rem;
font-size: 1rem;
margin-right: 0.5rem;
width: calc(100% - 12rem);
}
#presetList {
padding: 0.4rem;
font-size: 1rem;
margin-right: 0.5rem;
}
#spectrogramCanvas {
width: 100%;
height: 300px;
background-color: black;
display: block;
margin-top: 20px;
}
@media (max-width: 600px) {
.slider-group {
flex-direction: column;
align-items: flex-start;
}
input[type="range"] {
width: 100%;
}
.slider-label,
.slider-value {
width: 100%;
text-align: left;
}
#presetName {
width: 100%;
margin-bottom: 0.5rem;
}
#presetList {
width: 100%;
margin-bottom: 0.5rem;
}
button {
width: 100%;
margin-bottom: 0.5rem;
}
import { initializeUI } from './ui/uiController.js';
let presets = {};
fetch('presets.json')
.then(res => res.json())
.then(data => { presets = data; })
.catch(() => { presets = {}; })
.then(() => initializeUI(presets));
export { bufferToWav } from './waveform/bufferToWav.js';
export function setupSpectrogram(player, canvas) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const source = audioCtx.createMediaElementSource(player);
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 512;
source.connect(analyser);
analyser.connect(audioCtx.destination);
const bufferLength = analyser.frequencyBinCount;
const spectHeight = 256;
const waveHeight = 64;
const axisWidth = 50;
canvas.width = bufferLength + axisWidth;
canvas.height = spectHeight + waveHeight;
const ctx = canvas.getContext("2d");
const freqLabels = 5;
const sampleRate = audioCtx.sampleRate;
ctx.font = "12px sans-serif";
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
function drawAxis() {
// Clear axis background
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, axisWidth, spectHeight);
// Draw labels
ctx.fillStyle = '#000';
for (let i = 0; i <= freqLabels; i++) {
const y = spectHeight - (i * spectHeight / freqLabels);
const freq = Math.round(i * (sampleRate / 2) / freqLabels);
ctx.fillText(freq + ' Hz', axisWidth - 5, y);
}
}
let x = axisWidth;
function draw() {
if (player.paused) return;
drawAxis();
const freqData = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(freqData);
let sum = 0;
for (let i = 0; i < bufferLength; i++) {
const v = freqData[i];
sum += v;
const norm = v / 255;
const hue = (1 - norm) * 240;
ctx.fillStyle = `hsl(${hue},100%,50%)`;
const y = spectHeight - (i * spectHeight / bufferLength);
const h = spectHeight / bufferLength + 1;
ctx.fillRect(x, y - h/2, 1, h);
}
const vol = sum / bufferLength;
const volHeight = (vol / 255) * waveHeight;
ctx.fillStyle = '#444';
ctx.fillRect(x, spectHeight + (waveHeight - volHeight), 1, volHeight);
x++;
if (x >= canvas.width) {
x = axisWidth;
ctx.clearRect(axisWidth, 0, canvas.width - axisWidth, canvas.height);
}
requestAnimationFrame(draw);
}
player.addEventListener("play", () => {
if (audioCtx.state === "suspended") audioCtx.resume();
ctx.clearRect(0, 0, canvas.width, canvas.height);
x = axisWidth;
draw();
});
}
// Converts an AudioBuffer to a WAV-formatted ArrayBuffer
import { writeString } from './writeString.js';
export function bufferToWav(buffer, bitDepth) {
const numChannels = buffer.numberOfChannels;
const bytesPerSample = bitDepth / 8;
const dataLength = buffer.length * numChannels * bytesPerSample;
const view = new DataView(new ArrayBuffer(44 + dataLength));
writeString(view, 0, "RIFF");
view.setUint32(4, 36 + dataLength, true);
writeString(view, 8, "WAVE");
writeString(view, 12, "fmt ");
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, buffer.sampleRate, true);
view.setUint32(28, buffer.sampleRate * numChannels * bytesPerSample, true);
view.setUint16(32, numChannels * bytesPerSample, true);
view.setUint16(34, bitDepth, true);
writeString(view, 36, "data");
view.setUint32(40, dataLength, true);
let offset = 44;
for (let i = 0; i < buffer.length; i++) {
for (let ch = 0; ch < numChannels; ch++) {
const sample = Math.max(-1, Math.min(1, buffer.getChannelData(ch)[i]));
if (bitDepth === 8) {
view.setUint8(offset, Math.round((sample + 1) * 127.5));
offset += 1;
} else {
view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
offset += 2;
}
}
}
return view.buffer;
}
// Writes a string into a DataView at a specified offset
export function writeString(view, offset, str) {
for (let i = 0; i < str.length; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
import { setupSliderLabels } from './elements/setupSliderLabels.js';
import { setupPresetHandlers } from './handlers/presetHandlers.js';
import { setupFileHandlers } from './handlers/fileHandlers.js';
import { setupProcessHandler } from './handlers/processHandler.js';
export function initializeUI(presets = {}) {
const fileInput = document.getElementById("audioFile");
const processBtn = document.getElementById("process");
const player = document.getElementById("player");
const presetName = document.getElementById("presetName");
const savePreset = document.getElementById("savePreset");
const presetList = document.getElementById("presetList");
const loadPreset = document.getElementById("loadPreset");
const deletePreset = document.getElementById("deletePreset");
setupSliderLabels();
setupPresetHandlers(presetName, savePreset, presetList, loadPreset, deletePreset);
const bufferRef = setupFileHandlers(fileInput);
setupProcessHandler(processBtn, bufferRef, player);
}
// Handles file selection and stores the arrayBuffer reference
export function setupFileHandlers(fileInput) {
const ref = { buffer: null };
fileInput.addEventListener("change", async () => {
const file = fileInput.files[0];
if (file) {
ref.buffer = await file.arrayBuffer();
}
});
return ref;
}
// Handles saving, loading, and deleting audio processing presets
import { getAllInputs, setAllInputs, saveAllPresets, loadAllPresets } from '../../presets/presetManager.js';
function refreshPresetList(presetList) {
const store = loadAllPresets();
presetList.innerHTML = "";
Object.keys(store).forEach(name => {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = name;
presetList.appendChild(opt);
});
}
export function setupPresetHandlers(presetName, savePreset, presetList, loadPreset, deletePreset) {
savePreset.addEventListener("click", () => {
const name = presetName.value.trim();
if (!name) return;
const store = loadAllPresets();
store[name] = getAllInputs();
saveAllPresets(store);
refreshPresetList(presetList);
presetName.value = "";
});
loadPreset.addEventListener("click", () => {
const name = presetList.value;
const store = loadAllPresets();
if (store[name]) setAllInputs(store[name]);
});
deletePreset.addEventListener("click", () => {
const name = presetList.value;
const store = loadAllPresets();
delete store[name];
saveAllPresets(store);
refreshPresetList(presetList);
});
refreshPresetList(presetList);
}
import { getAllInputs } from '../../presets/presetManager.js';
import { processAudio } from '../../audio/audioProcessor.js';
import { setupSpectrogram } from '../../utils/spectrogram.js';
export function setupProcessHandler(processBtn, bufferRef, player) {
processBtn.addEventListener("click", async () => {
if (!bufferRef.buffer) return;
processBtn.disabled = true;
const params = getAllInputs();
const { blob, size } = await processAudio(bufferRef.buffer, params);
bufferRef.buffer = await blob.arrayBuffer();
player.src = URL.createObjectURL(blob);
document.getElementById("fileSize").innerText = `Processed file size: ${size} bytes`;
document.getElementById("recommendation").innerText =
size >= 20 * 1024 * 1024
? "File ≥ 20 MB which can't be uploaded to Roblox. Try lower bit depth or sample rate."
: "";
const canvas = document.getElementById("spectrogramCanvas");
setupSpectrogram(player, canvas);
processBtn.disabled = false;
});
}
// Initializes slider label values and syncs them with the slider input
export function setupSliderLabels() {
const sliders = document.querySelectorAll("input[type=range]");
sliders.forEach(slider => {
const valEl = document.getElementById(slider.id + "Val");
if (valEl) {
slider.addEventListener("input", () => {
valEl.textContent = slider.value;
});
valEl.textContent = slider.value;
}
});
}
export function getAllInputs() {
const keys = [
'distType',
'distAmount',
'bitDepth',
'hpFreq',
'notch1Freq',
'notch1Q',
'notch2Freq',
'notch2Q',
'midGain',
'sideGain',
'midEqFreq',
'midEqGain',
'sideEqFreq',
'sideEqGain',
'formant1Freq',
'formant1Q',
'formant1Gain',
'formant2Freq',
'formant2Q',
'formant2Gain',
'compThreshold',
'compRatio',
'compAttack',
'compRelease',
'volume',
'bassGain',
'trebleGain',
'lpFreq'
];
const o = {};
keys.forEach(k => {
o[k] = document.getElementById(k).value;
});
return o;
}
export function setAllInputs(o) {
Object.keys(o).forEach(k => {
const el = document.getElementById(k);
if (el) el.value = o[k];
const valEl = document.getElementById(k + 'Val');
if (valEl) valEl.textContent = o[k];
});
}
export function saveAllPresets(store) {
localStorage.setItem('audioPresets', JSON.stringify(store));
}
export function loadAllPresets() {
const raw = localStorage.getItem('audioPresets');
return raw ? JSON.parse(raw) : {};
}
export function makeBitcrusher(ctx, bits = 4) {
const node = ctx.createScriptProcessor(4096, 1, 1);
const step = Math.pow(0.5, bits);
node.onaudioprocess = function(e) {
const input = e.inputBuffer.getChannelData(0);
const output = e.outputBuffer.getChannelData(0);
for (let i = 0; i < input.length; i++) {
output[i] = Math.round(input[i] / step) * step;
}
};
return node;
}
export function makeHardClippingCurve(amount) {
const n_samples = 44100;
const curve = new Float32Array(n_samples);
const threshold = 1 - (amount / 1000);
for (let i = 0; i < n_samples; ++i) {
const x = (i * 2) / n_samples - 1;
curve[i] = Math.max(-threshold, Math.min(threshold, x));
}
return curve;
}
export function makeDistortionCurve(amount) {
const k = typeof amount === "number" ? amount : 50;
const n_samples = 44100;
const curve = new Float32Array(n_samples);
const deg = Math.PI / 180;
for (let i = 0; i < n_samples; ++i) {
const x = (i * 2) / n_samples - 1;
curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
}
return curve;
}
export { makeDistortionCurve } from './distortion/softClip.js';
export { makeHardClippingCurve } from './distortion/hardClip.js';
export { makeBitcrusher } from './distortion/bitcrush.js';
import { makeDistortionCurve, makeHardClippingCurve, makeBitcrusher } from './distortionUtils.js';
import { bufferToWav } from '../utils/fileConverter.js';
export async function processAudio(arrayBuffer, params) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const decoded = await audioCtx.decodeAudioData(arrayBuffer);
const oCtx = new OfflineAudioContext(
decoded.numberOfChannels,
decoded.length,
decoded.sampleRate
);
const src = oCtx.createBufferSource();
src.buffer = decoded;
const volumeNode = oCtx.createGain();
volumeNode.gain.value = +params.volume;
const bass = oCtx.createBiquadFilter();
bass.type = 'lowshelf';
bass.frequency.value = 200;
bass.gain.value = +params.bassGain;
const treble = oCtx.createBiquadFilter();
treble.type = 'highshelf';
treble.frequency.value = 3000;
treble.gain.value = +params.trebleGain;
const lp = oCtx.createBiquadFilter();
lp.type = 'lowpass';
lp.frequency.value = +params.lpFreq;
const hp = oCtx.createBiquadFilter();
hp.type = 'highpass';
hp.frequency.value = +params.hpFreq;
const notch1 = oCtx.createBiquadFilter();
notch1.type = 'notch';
notch1.frequency.value = +params.notch1Freq;
notch1.Q.value = +params.notch1Q;
const notch2 = oCtx.createBiquadFilter();
notch2.type = 'notch';
notch2.frequency.value = +params.notch2Freq;
notch2.Q.value = +params.notch2Q;
const inverter = oCtx.createGain();
inverter.gain.value = -1;
const splitter = oCtx.createChannelSplitter(2);
const merger = oCtx.createChannelMerger(2);
const midGain = oCtx.createGain();
midGain.gain.value = +params.midGain;
const sideGain = oCtx.createGain();
sideGain.gain.value = +params.sideGain;
const midEQ = oCtx.createBiquadFilter();
midEQ.type = 'peaking';
midEQ.frequency.value = +params.midEqFreq;
midEQ.gain.value = +params.midEqGain;
const sideEQ = oCtx.createBiquadFilter();
sideEQ.type = 'peaking';
sideEQ.frequency.value = +params.sideEqFreq;
sideEQ.gain.value = +params.sideEqGain;
const form1 = oCtx.createBiquadFilter();
form1.type = 'peaking';
form1.frequency.value = +params.formant1Freq;
form1.Q.value = +params.formant1Q;
form1.gain.value = +params.formant1Gain;
const form2 = oCtx.createBiquadFilter();
form2.type = 'peaking';
form2.frequency.value = +params.formant2Freq;
form2.Q.value = +params.formant2Q;
form2.gain.value = +params.formant2Gain;
const compressor = oCtx.createDynamicsCompressor();
compressor.threshold.value = +params.compThreshold;
compressor.ratio.value = +params.compRatio;
compressor.attack.value = +params.compAttack;
compressor.release.value = +params.compRelease;
let distortionNode;
if (params.distType === 'soft') {
distortionNode = oCtx.createWaveShaper();
distortionNode.curve = makeDistortionCurve(+params.distAmount);
distortionNode.oversample = '4x';
} else if (params.distType === 'hard') {
distortionNode = oCtx.createWaveShaper();
distortionNode.curve = makeHardClippingCurve(+params.distAmount);
distortionNode.oversample = 'none';
} else if (params.distType === 'bitcrush') {
distortionNode = makeBitcrusher(oCtx, 4 + Math.floor((+params.distAmount/1000)*12));
} else {
distortionNode = oCtx.createGain();
distortionNode.gain.value = 1;
}
src.connect(volumeNode);
volumeNode.connect(bass);
bass.connect(treble);
treble.connect(lp);
lp.connect(hp);
hp.connect(notch1);
notch1.connect(notch2);
notch2.connect(inverter);
inverter.connect(splitter);
splitter.connect(midGain, 0);
splitter.connect(sideGain, 1);
midGain.connect(midEQ);
sideGain.connect(sideEQ);
midEQ.connect(merger, 0, 0);
sideEQ.connect(merger, 0, 1);
merger.connect(form1);
form1.connect(form2);
form2.connect(compressor);
compressor.connect(distortionNode);
distortionNode.connect(oCtx.destination);
const script = oCtx.createScriptProcessor(256, 1, 1);
compressor.connect(script);
script.connect(oCtx.destination);
script.onaudioprocess = () => {
const duckGain = distortionNode.context.createGain();
duckGain.gain.value = 1 - Math.min(1, compressor.reduction / 20);
};
src.start();
const renderedBuffer = await oCtx.startRendering();
const bitDepth = +params.bitDepth;
const wav = bufferToWav(renderedBuffer, bitDepth);
const blob = new Blob([wav], { type: 'audio/wav' });
return { blob, size: blob.size, renderedBuffer };
}