| |
|
| | |
| | |
| | export const analyzeSilence = ( |
| | buffer: AudioBuffer, |
| | minDuration: number |
| | ): { start: number; end: number }[] => { |
| | const rawData = buffer.getChannelData(0); |
| | const sampleRate = buffer.sampleRate; |
| | const silenceRegions: { start: number; end: number }[] = []; |
| | |
| | |
| | const threshold = 0.015; |
| | |
| | let isSilent = false; |
| | let silenceStart = 0; |
| | |
| | |
| | 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; |
| | } |
| | } |
| | } |
| | |
| | |
| | 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); |
| | }); |
| | }; |
| |
|
| | |
| | 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; |
| |
|
| | |
| | setUint32(0x46464952); |
| | setUint32(length - 8); |
| | setUint32(0x45564157); |
| |
|
| | setUint32(0x20746d66); |
| | setUint32(16); |
| | setUint16(1); |
| | setUint16(numOfChan); |
| | setUint32(abuffer.sampleRate); |
| | setUint32(abuffer.sampleRate * 2 * numOfChan); |
| | setUint16(numOfChan * 2); |
| | setUint16(16); |
| |
|
| | setUint32(0x61746164); |
| | setUint32(length - pos - 4); |
| |
|
| | |
| | for(i = 0; i < abuffer.numberOfChannels; i++) |
| | channels.push(abuffer.getChannelData(i)); |
| |
|
| | while(pos < len) { |
| | for(i = 0; i < numOfChan; i++) { |
| | sample = Math.max(-1, Math.min(1, channels[i][pos])); |
| | sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; |
| | view.setInt16(44 + offset, sample, true); |
| | 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; |
| | } |
| | }; |
| |
|
| | |
| | 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; |
| | }; |
| |
|
| | |
| | export const processAudio = ( |
| | originalBuffer: AudioBuffer, |
| | silenceRegions: {start: number, end: number}[] |
| | ): Blob => { |
| | |
| | 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); |
| | }; |
| |
|