UNPKG

audio

Version:

Audio loading, editing, and rendering for JavaScript

69 lines (58 loc) 2.69 kB
import audio from '../core.js' export function autoThreshold(energies) { let vals = energies.filter(e => e > 0) if (!vals.length) return -40 vals.sort((a, b) => a - b) let floor = vals[Math.floor(vals.length * 0.1)] return Math.max(-80, Math.min(-20, 10 * Math.log10(floor) + 12)) } /** Resolve dB threshold to linear. Auto-detects from energy stats when db is null. */ export function resolveThreshold(stats, ch, from, to, db) { if (db == null) { let energies = [] for (let c = 0; c < ch; c++) for (let i = from; i < to; i++) energies.push(stats.energy[c][i]) db = autoThreshold(energies) } return 10 ** (db / 20) } /** Check if block i is loud (any channel exceeds thresh). */ export let isLoud = (stats, i, ch, thresh) => { for (let c = 0; c < ch; c++) if (Math.max(Math.abs(stats.min[c][i]), Math.abs(stats.max[c][i])) > thresh) return true return false } const trim = (chs, ctx) => { let threshold = ctx.args[0] if (threshold == null) { let energies = [] for (let c = 0; c < chs.length; c++) for (let off = 0; off < chs[c].length; off += audio.BLOCK_SIZE) { let end = Math.min(off + audio.BLOCK_SIZE, chs[c].length), sum = 0 for (let i = off; i < end; i++) sum += chs[c][i] * chs[c][i] energies.push(sum / (end - off)) } threshold = autoThreshold(energies) } let thresh = 10 ** (threshold / 20) let len = chs[0].length, s = 0, e = len - 1 for (; s < len; s++) { let loud = false; for (let c = 0; c < chs.length; c++) if (Math.abs(chs[c][s]) > thresh) { loud = true; break }; if (loud) break } for (; e >= s; e--) { let loud = false; for (let c = 0; c < chs.length; c++) if (Math.abs(chs[c][e]) > thresh) { loud = true; break }; if (loud) break } e++ return s === 0 && e === len ? false : chs.map(ch => ch.slice(s, e)) } const trimResolve = (args, { stats, sampleRate, totalDuration }) => { if (!stats?.min || !stats?.energy) return null let ch = stats.min.length, blocks = stats.min[0].length let total = Math.round(totalDuration * sampleRate) let thresh = resolveThreshold(stats, ch, 0, stats.energy[0].length, args[0]) let s = 0, e = blocks - 1 for (; s < blocks; s++) if (isLoud(stats, s, ch, thresh)) break for (; e >= s; e--) if (isLoud(stats, e, ch, thresh)) break e++ if (s === 0 && e === blocks) return false if (s >= e) return { type: 'crop', args: [], at: 0, duration: 0 } let startSample = s * audio.BLOCK_SIZE let endSample = Math.min(e * audio.BLOCK_SIZE, total) return { type: 'crop', args: [], at: startSample / sampleRate, duration: (endSample - startSample) / sampleRate } } audio.op('trim', { process: trim, resolve: trimResolve })