NeuralFalcon's picture
Upload 14 files
1ab127f verified
// Simulates checking for silence regions
// Threshold is now essentially fixed for "true silence" or noise floor
export const analyzeSilence = (
buffer: AudioBuffer,
minDuration: number // in seconds
): { start: number; end: number }[] => {
const rawData = buffer.getChannelData(0);
const sampleRate = buffer.sampleRate;
const silenceRegions: { start: number; end: number }[] = [];
// Fixed low threshold for "silence" (removes background hiss if set slightly higher, or exact 0 if set to 0)
const threshold = 0.015;
let isSilent = false;
let silenceStart = 0;
// Optimization: Step by 200 samples for performance while retaining accuracy
const step = 200;
for (let i = 0; i < rawData.length; i += step) {
const amplitude = Math.abs(rawData[i]);
if (amplitude < threshold) {
if (!isSilent) {
isSilent = true;
silenceStart = i;
}
} else {
if (isSilent) {
const duration = (i - silenceStart) / sampleRate;
if (duration >= minDuration) {
silenceRegions.push({
start: silenceStart / sampleRate,
end: i / sampleRate
});
}
isSilent = false;
}
}
}
// Check end
if (isSilent) {
const duration = (rawData.length - silenceStart) / sampleRate;
if (duration >= minDuration) {
silenceRegions.push({
start: silenceStart / sampleRate,
end: rawData.length / sampleRate
});
}
}
return silenceRegions;
};
export const formatTime = (seconds: number): string => {
if (!isFinite(seconds) || isNaN(seconds)) return "0:00.0";
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
const ms = Math.floor((seconds % 1) * 10);
return `${mins}:${secs.toString().padStart(2, '0')}.${ms}`;
};
export const generateUUID = (): string => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
// Converts AudioBuffer to WAV Blob
export const bufferToWav = (abuffer: AudioBuffer, len: number) => {
let numOfChan = abuffer.numberOfChannels;
let length = len * numOfChan * 2 + 44;
let buffer = new ArrayBuffer(length);
let view = new DataView(buffer);
let channels = [];
let i;
let sample;
let offset = 0;
let pos = 0;
// write WAVE header
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length - 8
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numOfChan);
setUint32(abuffer.sampleRate);
setUint32(abuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit (hardcoded in this example)
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// write interleaved data
for(i = 0; i < abuffer.numberOfChannels; i++)
channels.push(abuffer.getChannelData(i));
while(pos < len) {
for(i = 0; i < numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][pos])); // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // scale to 16-bit signed int
view.setInt16(44 + offset, sample, true); // write 16-bit sample
offset += 2;
}
pos++;
}
return new Blob([buffer], {type: "audio/wav"});
function setUint16(data: any) {
view.setUint16(pos, data, true);
pos += 2;
}
function setUint32(data: any) {
view.setUint32(pos, data, true);
pos += 4;
}
};
// Helper to create a new AudioBuffer (for playback)
export const createProcessedBuffer = (
originalBuffer: AudioBuffer,
silenceRegions: {start: number, end: number}[],
context: AudioContext
): AudioBuffer => {
const sampleRate = originalBuffer.sampleRate;
const channels = originalBuffer.numberOfChannels;
const totalSamples = originalBuffer.length;
const keepRegions: {start: number, end: number}[] = [];
let cursor = 0;
const sortedSilence = [...silenceRegions].sort((a, b) => a.start - b.start);
for (const region of sortedSilence) {
const regionStartSample = Math.floor(region.start * sampleRate);
const regionEndSample = Math.floor(region.end * sampleRate);
if (cursor < regionStartSample) {
keepRegions.push({start: cursor, end: regionStartSample});
}
cursor = regionEndSample;
}
if (cursor < totalSamples) {
keepRegions.push({start: cursor, end: totalSamples});
}
const newLength = keepRegions.reduce((acc, reg) => acc + (reg.end - reg.start), 0);
const newBuffer = context.createBuffer(channels, newLength, sampleRate);
for (let c = 0; c < channels; c++) {
const oldData = originalBuffer.getChannelData(c);
const newData = newBuffer.getChannelData(c);
let pointer = 0;
for (const reg of keepRegions) {
const len = reg.end - reg.start;
const chunk = oldData.subarray(reg.start, reg.end);
newData.set(chunk, pointer);
pointer += len;
}
}
return newBuffer;
};
// Creates a WAV Blob (for download)
export const processAudio = (
originalBuffer: AudioBuffer,
silenceRegions: {start: number, end: number}[]
): Blob => {
// We can reuse the logic if we had a context, but for pure data manipulation without context:
const sampleRate = originalBuffer.sampleRate;
const channels = originalBuffer.numberOfChannels;
const totalSamples = originalBuffer.length;
const keepRegions: {start: number, end: number}[] = [];
let cursor = 0;
const sortedSilence = [...silenceRegions].sort((a, b) => a.start - b.start);
for (const region of sortedSilence) {
const regionStartSample = Math.floor(region.start * sampleRate);
const regionEndSample = Math.floor(region.end * sampleRate);
if (cursor < regionStartSample) {
keepRegions.push({start: cursor, end: regionStartSample});
}
cursor = regionEndSample;
}
if (cursor < totalSamples) {
keepRegions.push({start: cursor, end: totalSamples});
}
const newLength = keepRegions.reduce((acc, reg) => acc + (reg.end - reg.start), 0);
const newBuffer = new AudioBuffer({
length: newLength,
numberOfChannels: channels,
sampleRate: sampleRate
});
for (let c = 0; c < channels; c++) {
const oldData = originalBuffer.getChannelData(c);
const newData = newBuffer.getChannelData(c);
let pointer = 0;
for (const reg of keepRegions) {
const len = reg.end - reg.start;
const chunk = oldData.subarray(reg.start, reg.end);
newData.set(chunk, pointer);
pointer += len;
}
}
return bufferToWav(newBuffer, newLength);
};