File size: 2,235 Bytes
906c43a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
export const TARGET_SAMPLE_RATE = 16000;

export async function decodeAudioBlob(blob) {
  if (!(blob instanceof Blob)) throw new TypeError('Expected an audio Blob.');
  const AudioContextClass = window.AudioContext || window.webkitAudioContext;
  if (!AudioContextClass) throw new Error('Web Audio API is not supported in this browser.');
  const context = new AudioContextClass();
  try {
    const buffer = await context.decodeAudioData(await blob.arrayBuffer());
    const mono = mixToMono(buffer);
    const samples = resampleLinear(mono, buffer.sampleRate, TARGET_SAMPLE_RATE);
    return { samples, samplingRate: TARGET_SAMPLE_RATE, duration: samples.length / TARGET_SAMPLE_RATE, originalSamplingRate: buffer.sampleRate, channels: buffer.numberOfChannels };
  } catch (error) {
    throw new Error(`The browser could not decode this audio file: ${error.message}`);
  } finally {
    await context.close();
  }
}

export function mixToMono(audioBuffer) {
  const output = new Float32Array(audioBuffer.length);
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel += 1) {
    const data = audioBuffer.getChannelData(channel);
    for (let i = 0; i < data.length; i += 1) output[i] += data[i] / audioBuffer.numberOfChannels;
  }
  return output;
}

export function resampleLinear(samples, sourceRate, targetRate = TARGET_SAMPLE_RATE) {
  if (sourceRate === targetRate) return new Float32Array(samples);
  if (sourceRate <= 0 || targetRate <= 0) throw new Error('Sampling rates must be positive.');
  const ratio = sourceRate / targetRate;
  const length = Math.max(1, Math.round(samples.length / ratio));
  const output = new Float32Array(length);
  for (let i = 0; i < length; i += 1) {
    const position = i * ratio;
    const left = Math.floor(position);
    const right = Math.min(left + 1, samples.length - 1);
    const weight = position - left;
    output[i] = samples[left] * (1 - weight) + samples[right] * weight;
  }
  return output;
}

export function formatDuration(seconds) {
  const safe = Math.max(0, Number(seconds) || 0);
  const minutes = Math.floor(safe / 60);
  const remainder = Math.floor(safe % 60);
  return `${String(minutes).padStart(2, '0')}:${String(remainder).padStart(2, '0')}`;
}