audio
Version:
Audio loading, editing, and rendering for JavaScript
68 lines (59 loc) • 3.28 kB
JavaScript
import hpFilter from 'audio-filter/effect/highpass.js'
import lpFilter from 'audio-filter/effect/lowpass.js'
import bpFilter from 'audio-filter/effect/bandpass.js'
import notchFilter from 'audio-filter/effect/notch.js'
import lsFilter from 'audio-filter/eq/lowshelf.js'
import hsFilter from 'audio-filter/eq/highshelf.js'
import parametricEq from 'audio-filter/eq/parametric-eq.js'
// ── Filter state helper ─────────────────────────────────────────────────
// Each channel gets its own params object (holds coefs + state).
// Persists across streaming chunks via ctx (persistent object).
function apply(chs, ctx, key, fn, makeParams) {
if (!ctx[key]) ctx[key] = chs.map(() => makeParams(ctx.sampleRate))
let st = ctx[key]
for (let c = 0; c < chs.length; c++) fn(chs[c], st[c])
return chs
}
// ── Filter dispatch ─────────────────────────────────────────────────────
const types = {
highpass: (chs, ctx, args) => apply(chs, ctx, '_hp', hpFilter, fs => ({ fc: args[0], fs })),
lowpass: (chs, ctx, args) => apply(chs, ctx, '_lp', lpFilter, fs => ({ fc: args[0], fs })),
eq: (chs, ctx, args) => apply(chs, ctx, '_eq', parametricEq, fs => ({ bands: [{ fc: args[0], Q: args[2] ?? 1, gain: args[1] ?? 0, type: 'peak' }], fs })),
lowshelf: (chs, ctx, args) => apply(chs, ctx, '_ls', lsFilter, fs => ({ fc: args[0], gain: args[1] ?? 0, Q: args[2] ?? 0.707, fs })),
highshelf: (chs, ctx, args) => apply(chs, ctx, '_hs', hsFilter, fs => ({ fc: args[0], gain: args[1] ?? 0, Q: args[2] ?? 0.707, fs })),
notch: (chs, ctx, args) => apply(chs, ctx, '_notch', notchFilter, fs => ({ fc: args[0], Q: args[1] ?? 30, fs })),
bandpass: (chs, ctx, args) => apply(chs, ctx, '_bp', bpFilter, fs => ({ fc: args[0], Q: args[1] ?? 0.707, fs })),
}
/** Unified filter: a.filter('highpass', 80) or a.filter(fn, {fc, ...}) */
const filter = (chs, ctx) => {
let [type, ...args] = ctx.args
if (typeof type === 'function') {
let opts = args[0] || {}
return apply(chs, ctx, '_custom', type, fs => ({ ...opts, fs }))
}
let fn = types[type]
if (!fn) throw new Error(`Unknown filter type: ${type}`)
return fn(chs, ctx, args)
}
// ── Register ────────────────────────────────────────────────────────────
import audio from '../core.js'
audio.op('filter', {
process: filter,
call(std, type, ...args) {
if (typeof type === 'function') {
let opts = args[0] || {}
let { at, duration, channel, offset, length, ...rest } = opts
let edit = { type: 'filter', args: [type, rest] }
if (at != null) edit.at = at
if (duration != null) edit.duration = duration
if (channel != null) edit.channel = channel
if (offset != null) edit.offset = offset
if (length != null) edit.length = length
return this.run(edit)
}
return std.call(this, type, ...args)
}
})
for (let name in types) {
audio.op(name, { process: (chs, ctx) => types[name](chs, ctx, ctx.args) })
}