Интеграция Peito Denoise
Peito Denoise — реалтайм ИИ-шумодав для браузера, Electron и Capacitor-WebView. Эта страница проведёт от установки пакета до обработанного аудиотрека в вашем приложении.
Установка
Пакет ставится из npm. onnxruntime-web — peer-зависимость, нужна для нейробэкенда dfn3:
npm i peito-denoise onnxruntime-web
Пакет содержит JS-бандл, типы и AudioWorklet-процессор. Тяжёлые ассеты (модели, wasm) в пакет не входят — их хостите вы (см. ниже).
Ассеты
Скопируйте в свою папку статики (например, public/):
| Что | Откуда | Куда |
|---|---|---|
| AudioWorklet-процессор | node_modules/peito-denoise/dist/denoise-processor.js |
public/denoise-processor.js |
| Модели DFN3 (~8.5 МБ) | дистрибутив моделей (*.onnx + config.ini) |
public/models/dfn3/ |
| ORT: ESM-модуль + wasm | node_modules/onnxruntime-web/dist/ (ort.bundle.min.mjs и ort-wasm-simd-threaded.jsep.{wasm,mjs}) |
public/ort/ |
Проще всего скопировать весь каталог onnxruntime-web/dist/ в public/ort/.
ORT грузится в воркере по URL, а не bare-спецификатором. Module-воркер не
резолвит import 'onnxruntime-web' и не поддерживает import maps, поэтому
воркер импортирует ORT из /ort/ort.bundle.min.mjs, а wasm — из /ort/
(тот же origin). Оба пути переопределяются: assets.ortUrl (ESM-модуль) и
assets.cdnUrl (каталог wasm). Именно поэтому ort.bundle.min.mjs
обязателен в раздаче — без него dfn3 не стартует.
Воркер-чанк. Инференс dfn3 идёт в отдельном module-воркере
(dist/assets/denoise-worker-<hash>.js). Бандлеры со встроенной
поддержкой воркеров (Vite) подхватывают его сами. При ручной статической раздаче
копируйте весь каталог dist/assets/ целиком — имя содержит хеш и меняется
между версиями.
Почему worklet лежит в public/, а не импортируется в бандл: AudioWorklet
загружается только по URL (audioWorklet.addModule) отдельным запросом,
в бандл он не попадает, поэтому хеш сборки к /denoise-processor.js не
добавляется — и сборку это не ломает. Если нужен cache-busting от бандлера,
получите хешированный URL ассета и передайте его в workletUrl:
// Vite: файл попадёт в assets с хешом, копировать в public/ не нужно
import workletUrl from 'peito-denoise/denoise-processor.js?url';
const node = new DenoiseNode({ backend: 'dfn3', workletUrl });
// Webpack 5 — аналогично:
const workletUrl = new URL('peito-denoise/denoise-processor.js', import.meta.url).href;
Заголовки COOP/COEP
Конвейер использует SharedArrayBuffer (lock-free ring между AudioWorklet и воркером),
поэтому странице нужна cross-origin isolation. Сервер должен отдавать:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
На реальном домене обязателен HTTPS: SharedArrayBuffer требует secure context
(http://localhost — исключение, там всё работает). Если сторонние ресурсы (CDN, S3)
блокируются COEP — выставьте на них Cross-Origin-Resource-Policy: cross-origin
или используйте COEP: credentialless.
Пример для Vite dev-сервера:
// vite.config.ts
export default defineConfig({
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
});
Диагностика. Если изоляции нет, init() бросает
PeitoError(E_COOP_COEP) ещё до запроса активации — симптом
«скрипт загрузился, а fetch на активацию не ушёл». Проверяйте заранее:
if (!crossOriginIsolated) {
// нет COOP/COEP — SharedArrayBuffer недоступен, конвейер не поднимется
}
import { detectCapabilities } from 'peito-denoise';
console.log(detectCapabilities()); // sharedArrayBuffer, crossOriginIsolated, simd, threads…
Быстрый старт
Минимальная интеграция: микрофон → шумодав → динамики. init() вызывайте после жеста пользователя (клик), иначе браузер не даст запустить AudioContext.
import { DenoiseNode } from 'peito-denoise';
const node = new DenoiseNode({
backend: 'dfn3', // нейромодель; 'spectral' — DSP без ассетов
workletUrl: '/denoise-processor.js', // скопированный worklet (это и есть дефолт)
});
await node.init();
// граф: любой источник → шумодав → куда угодно
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const src = node.audioContext.createMediaStreamSource(stream);
node.connectSource(src);
node.connect(node.audioContext.destination);
Бэкенды: dfn3 (DeepFilterNet3, нейросеть, лучшее качество речи), spectral (DSP-гейт, ~24 дБ на стационарном шуме, без ассетов), passthrough (обход, для A/B).
На слабых CPU включите многопоточный wasm-инференс — threads: 2–4 в опциях
(см. справочник): время кадра dfn3 падает примерно вдвое.
MediaStreamTrack → чистый трек
Для WebRTC-звонков удобнее получить обработанный MediaStreamTrack и отдать его в PeerConnection:
const rawTrack = stream.getAudioTracks()[0];
const cleanTrack = node.createProcessedTrack(rawTrack);
// дальше как обычно:
pc.addTrack(cleanTrack, new MediaStream([cleanTrack]));
LiveKit
Адаптер под TrackProcessor. createLiveKitProcessor принимает
тот же объект опций, что и DenoiseNode — обязательны
activation (иначе dfn3 не получит модель) и context
(running-контекст). Адаптер сам вызывает start() после инициализации.
import { createLiveKitProcessor } from 'peito-denoise';
// running-контекст создаём в рамках жеста пользователя (клик по «Позвонить»)
const ctx = new AudioContext({ sampleRate: 48000 });
await ctx.resume();
const proc = createLiveKitProcessor({
backend: 'dfn3',
suppressionLevel: 70,
activation: {
key: 'pk_…', // ключ из личного кабинета
activateUrl: 'https://peito.ru/api/activate', // или ваш прокси (см. про CORS ниже)
},
context: ctx, // без него worklet отдаёт тишину/сырой звук
});
await localAudioTrack.setProcessor(proc); // адаптер сам вызовет start()
// метрики и ошибки — через proc.denoiser
proc.denoiser?.on('metrics', (m) => updateUi(m));
proc.denoiser?.on('error', (e) => console.warn(e.code, e.where, e.error));
Почему «Hello World» из старых доков не запускался: без activation у dfn3
нет модели; без running-контекста worklet отдаёт тишину/сырой звук («активация есть, а
шум не режется»); без раздачи ассетов (worklet, воркер, ORT) конвейер не грузится.
Все три обязательны.
CORS и прокси. В 0.2.0 activateUrl по умолчанию абсолютный
(https://peito.ru/api/activate) — сервер Peito отдаёт CORS-заголовки, так что
с браузера он доступен напрямую. Если хотите проксировать активацию через свой бэкенд —
переопределите activation.activateUrl и activation.refreshUrl.
Расшифрованные модели активация отдаёт по /api/assets?…; они кэшируются
после первой загрузки (см. Предзагрузка и кеш).
Управление в рантайме
Все параметры меняются на лету, без пересоздания графа:
node.setSuppression(80); // сила подавления 0..100
node.setMix(0.5); // 0 = dry (сырой), 1 = wet (обработанный)
node.setBypass(true); // мгновенный обход для A/B-сравнения
node.switchBackend('spectral'); // хот-свап бэкенда внутри воркера
node.setParam('highpassHz', 0); // выключить входной high-pass (по умолчанию 80 Гц)
node.pause(); // воркер простаивает — экономия CPU
await node.start(); // возобновление (+ поднимает AudioContext)
node.destroy(); // полная очистка ресурсов
AutoGain
Дотягивает тихую речь до целевого уровня: VAD-гейт (не усиливает шум в паузах), посэмпловое сглаживание и lookahead-лимитер — без щелчков и клиппинга.
node.setAutoGain({
enabled: true,
targetDb: -18, // целевой уровень речи
maxGainDb: 24, // потолок буста
});
Входной high-pass (ветер, гул, DC)
Перед моделью стоит high-pass фильтр (Butterworth 2-го порядка, по умолчанию 80 Гц): он срезает то, что живёт ниже речи и плохо даётся нейросети — ветер в капсюль, хендлинг-шум, гул сети, DC-смещение. На 40 Гц затухание −12 дБ, на 20 Гц −24 дБ; от 300 Гц фильтр прозрачен, речь не задевается.
new DenoiseNode({ backend: 'dfn3', highpassHz: 100 }); // своя частота среза
node.setParam('highpassHz', 0); // выключить на лету
Фильтр применяется до разделения на raw/processed: dry/wet-микс и аварийный fail-open не дают скачков уровня на НЧ. Остаточную среднечастотную турбулентность сильного ветра без поедания речи не уберёт ни одна модель — здесь поможет только ветрозащита/поп-фильтр на микрофоне.
Предзагрузка и кеш
Ассеты кешируются в CacheStorage, поэтому загрузка происходит
максимум один раз, а повторный запуск шумодава — почти мгновенный.
- Модели через активацию. Зашифрованные блоба моделей (~8.5 МБ) кешируются по стабильному ключу при первой активации. Повторная активация и перезагрузка страницы берут их из кеша — без повторной загрузки; заново только быстрая дешифровка в памяти. (Ключ расшифровки эфемерный, plaintext-модели не хранятся.)
- Модели через self-host (
assets.modelUrl) и ORT wasm кешируются по URL тем жеCacheStorage.
preloadDenoise() прогревает кеш заранее (wasm + ORT + self-host-модели),
чтобы первый init() стартовал уже из кеша и работал офлайн:
import { preloadDenoise, clearDenoiseCache } from 'peito-denoise';
await preloadDenoise({
onProgress: (p) => console.log(`${p.loaded} / ${p.total}`),
});
// сбросить оба кеша (ассеты + модели), напр. при обновлении моделей
await clearDenoiseCache();
Итог по скорости: первая активация скачивает модели один раз; все последующие запуски (реактивация, перезагрузка вкладки) поднимают dfn3 из кеша за десятки миллисекунд. Кеш инвалидируется автоматически при ротации ключа шифрования на сервере.
Наблюдаемость
Шина событий даёт живые метрики и детекторы искажений — проблемы видно в телеметрии, а не «на слух»:
node.on('metrics', (e) => {
// reductionDb, inferMs p50/p95, латентность, VAD, under/overruns…
console.log('подавление:', e.data.reductionDb, 'дБ');
});
node.on('artifact', (e) => console.warn('искажение:', e.kind, e.frame));
node.on('error', (e) => console.error(e));
node.on('backendSwitch', (e) => console.log('fail-open/восстановление', e));
// последние N секунд raw/processed — готовые WAV-блобы для офлайн-репро
const { raw, processed } = await node.dumpWav(); // { raw: Blob, processed: Blob }
Детекторы: clip, click (разрыв), musical (музыкальный шум),
oversuppress (модель ест речь), nan/inf, dead (тишина).
Встроенный watchdog — fail-open: если воркер замолкает, узел автоматически переключается на сырой звук (а не в тишину) и сам восстанавливается, когда воркер оживает.
Дебаг-панель
Отдельный вход — не попадает в прод-бандл, если не импортирован:
import { createDebugPanel } from 'peito-denoise/panel';
const panel = createDebugPanel(node, document.getElementById('debug'));
// метрики с зонами, лампы детекторов, журнал, start/pause/bypass
panel.destroy(); // снять панель
Лицензирование
Основной путь для всех планов (Trial = Pro по фичам, отличается сроком и доменами) —
онлайн-активация: клиент обменивает короткий публичный ключ (pk_…
из личного кабинета) на session-токен, сервер проверяет домен,
срок и отзыв и выдаёт зашифрованные модели по короткоживущим ссылкам. Без успешной
активации нейробэкенд не получает веса — «пропустить проверку» на клиенте нельзя.
const node = new DenoiseNode({
backend: 'dfn3',
activation: { key: 'pk_…' }, // по умолчанию → https://peito.ru/api/activate
});
await node.init(); // активация отклонена → init() бросает
node.session; // текущий session-токен
activateUrl и refreshUrl по умолчанию ведут на сервер
Peito (https://peito.ru/api/…) — библиотека встраивается в чужие
продукты, поэтому дефолт абсолютный. Переопределяйте эти поля, только если
проксируете активацию через свой бэкенд.
Дальше библиотека сама продлевает session в фоне (refresh на ~60% TTL,
по умолчанию TTL 15 минут). Если продлить не удалось — она реактивируется ключом
(модели не перекачиваются). Если и это не прошло (ключ отозван, срок истёк, сервер
недоступен) — шумодав отключается в fail-open: звук продолжает идти, но без
обработки; попытки реактивации идут раз в минуту, при успехе обработка включается
обратно. Оба перехода видны в событии backendSwitch.
Дополнительно ключ можно проверять и офлайн — по подписи ECDSA P-256 (WebCrypto): домены (включая wildcard), срок, набор фич. Работает без сети, но не видит отзыв ключа до истечения срока — используйте как дополнение к активации или для окружений без сети.
const node = new DenoiseNode({
backend: 'dfn3',
license: {
key: MY_LICENSE_KEY,
mode: 'enforce', // блокирует init() без валидного ключа; 'warn' — только событие error
},
});
await node.init();
await node.hasFeature('dfn3'); // проверка фичи из лицензии
Ошибки и коды
Все ошибки конвейера — кодовые: у каждой стабильный код E_*.
Ошибки видны тремя способами, и по умолчанию их не нужно специально ловить,
чтобы увидеть:
- Консоль по умолчанию. Библиотека сама логирует ошибки в консоль
(
console.errorдля фатальных,console.warnдля остальных). Уровень задаётсяdebug.verbosity('error'по умолчанию;'silent'отключает вывод,'info'/'debug'добавляют переключения бэкенда и селфтест). - Фатальные — через исключение. На путях, где шумодав не может запуститься,
init()отклоняется экземпляромPeitoErrorс полем.code. Раньше такие сбои могли пройти молча — теперьawait init()бросает. - Событие
error. Для наблюдаемости все ошибки идут и событием с полями{ code, where, error, fatal }.
import { DenoiseNode, PeitoError, PeitoErrorCode } from 'peito-denoise';
const node = new DenoiseNode({
backend: 'dfn3',
activation: { key: 'pk_…' },
debug: { verbosity: 'error' }, // 'silent' | 'error' | 'info' | 'debug'
});
// рантайм-ошибки (fail-open, watchdog, refresh лицензии, artefact-детекторы)
node.on('error', (e) => {
console.warn(e.code, e.where, e.error, e.fatal);
if (e.code === PeitoErrorCode.LICENSE_REFRESH) notifyUser('лицензия истекла');
});
try {
await node.init(); // фатальные пути бросают PeitoError
} catch (e) {
if (e instanceof PeitoError && e.code === PeitoErrorCode.COOP_COEP) {
// нет cross-origin isolation — проверьте заголовки COOP/COEP
}
throw e;
}
Полный список кодов:
| Код | Класс | Когда возникает и что делать |
|---|---|---|
E_COOP_COEP | init · фатально | Нет SharedArrayBuffer / cross-origin isolation. Отдавайте страницу по HTTPS с заголовками COOP: same-origin и COEP: require-corp. |
E_WORKLET_LOAD | init · фатально | Не удалось загрузить AudioWorklet-процессор. Проверьте workletUrl и что denoise-processor.js реально раздаётся. |
E_WORKER_INIT | init · фатально | Воркер не смог поднять бэкенд: не загрузились wasm/модель или упал backend.init. Проверьте assets/activation и доступность моделей. |
E_WORKER_TIMEOUT | init · фатально | Воркер не сообщил о готовности за initTimeoutMs. Медленная сеть/большая модель — увеличьте initTimeoutMs или предзагрузите ассеты (preloadDenoise). |
E_WORKER_CRASH | рантайм · фатально | Необработанная ошибка воркера в рантайме. Смотрите error; частая причина — несовместимая сборка ort/модели. |
E_ACTIVATION | init · фатально | Онлайн-активация отклонена/недоступна: неверный или отозванный ключ, чужой домен, сетевая ошибка. Проверьте ключ и activateUrl. |
E_LICENSE_INVALID | init · фатально в enforce | Офлайн-проверка лицензии не прошла (подпись, домен, срок, фичи). В режиме enforce запуск блокируется, в warn — только событие. |
E_SAMPLE_RATE | рантайм · предупреждение | sampleRate переданного AudioContext ≠ ожидаемого. Создавайте контекст с нужным sampleRate или не передавайте свой. |
E_SELFTEST | рантайм · предупреждение | Самотест после init дал недопустимый выход (NaN, клиппинг или тишина). Бэкенд/модель, вероятно, неисправны. |
E_PROCESS_FRAME | рантайм · предупреждение | Исключение при обработке аудиокадра. Кадр пропущен; при повторении проверьте бэкенд. |
E_BACKEND_SWITCH | рантайм · предупреждение | Рантайм-смена бэкенда не удалась. Прежний бэкенд снят — вызовите switchBackend повторно или пересоздайте узел. |
E_ACTIVATION_ANOMALY | рантайм · предупреждение | Сервер пометил активацию как аномальную (один ключ с многих доменов). Обработка продолжается — проверьте распространение ключа. |
E_LICENSE_REFRESH | рантайм · предупреждение | Не удалось продлить session и реактивировать ключ. Шумодав перешёл в fail-open (сырой звук); попытки продолжаются раз в минуту. |
E_WATCHDOG | рантайм · предупреждение | Воркер перестал двигать кадры дольше watchdogMs. Временный fail-open на сырой звук; при оживлении воркера обработка вернётся. |
Свой бэкенд (своя модель)
RNNoise, DTLN, GTCRN или произвольный ONNX подключаются плагином — конвейер трогать не нужно.
Реализуйте интерфейс DenoiseBackend и зарегистрируйте:
import { registerBackend } from 'peito-denoise';
import type { DenoiseBackend } from 'peito-denoise';
class MyOnnxBackend implements DenoiseBackend {
// init, processFrame(input, out), setParam, reset, destroy,
// опционально lastDebug() — маска/VAD для спектрограммы и детекторов
}
registerBackend('onnx', () => new MyOnnxBackend()); // имя — из BackendName ('rnnoise', 'dtln', 'gtcrn', 'onnx')
const node = new DenoiseNode({ backend: 'onnx' });
Справочник опций DenoiseNode
| Опция | По умолчанию | Описание |
|---|---|---|
backend | — | Бэкенд: 'dfn3' | 'spectral' | 'passthrough' | свой. |
workletUrl | /denoise-processor.js | URL AudioWorklet-процессора. |
context | создаётся | Готовый AudioContext, если хотите свой. |
suppressionLevel | 60 | Сила подавления, 0..100. |
mix | 1 | Dry/wet-микс, 0..1. |
bypass | false | Мгновенный обход модели (сырой звук на выход), для A/B. |
sampleRate | 48000 | Частота дискретизации графа. dfn3 работает на 48 кГц. |
assets | /models/dfn3/, /ort/ | Пути: modelUrl (модели), cdnUrl (каталог ORT wasm), ortUrl (ESM-модуль ORT, по умолчанию ${cdnUrl}ort.bundle.min.mjs). |
threads | 1 | Потоки wasm-инференса. На слабых CPU 2–4 заметно снижают время кадра; требует COOP/COEP (он и так обязателен). |
highpassHz | 80 | High-pass на входе: срезает ветер в микрофон, хендлинг-шум и DC до модели. 0 — выключить. |
activation | — | Онлайн-активация: { key, activateUrl?, refreshUrl? }. Обязательна для dfn3 (выдаёт модель). URL по умолчанию — сервер Peito. |
license | без проверки | Офлайн-проверка: ключ + режим enforce/warn, опц. onlineCheck. |
initTimeoutMs | 20000 | Таймаут готовности воркера в init(). По истечении — PeitoError(E_WORKER_TIMEOUT). |
fallback | — | Цепочка деградации бэкендов, напр. ['dfn3','spectral']. |
debug | { verbosity: 'error' } | Логирование в консоль: 'silent' | 'error' | 'info' | 'debug'; metricsHz. |
captureSeconds | 8 | Сколько секунд держать для dumpWav(). |
Остались вопросы? Посмотрите живое демо — там весь конвейер с метриками и спектрограммами, или начните с Trial-лицензии.