@revolab/revolab-edge
v0.3.2
Published
Revolab Edge — browser-native neural Malay text-to-speech, 100% Revolab-owned clean-room runtime. Three 1.39M-parameter voices, fully on-device via WebAssembly.
Maintainers
Readme
@revolab/revolab-edge
Browser-native neural Malay text-to-speech. Malay text in, waveform out, entirely on-device — after the assets load you can go offline.
Install
npm install @revolab/revolab-edgeQuick start
import { RevolabEdge, playAudio } from '@revolab/revolab-edge';
const tts = await RevolabEdge.load({ assetBase: '/assets/revolab-edge/' });
const { samples, sampleRate } = await tts.synthesize(
'Selamat pagi! Saya suara Revolab dalam pelayar anda.',
{ voice: 'sarah' },
);
playAudio({ samples, sampleRate });assetBase is where you host this package's files (wasm, g2p.json,
voices/). Point it at a copy on your own server, or use a CDN:
https://unpkg.com/@revolab/revolab-edge/.
Voices
Three Malay speakers:
| Voice | Key | Language | Parameters | Weights |
|---|---|---|---|---|
| angellyn | angellyn | Malay (ms) | 1,394,445 | ~1.4 MB |
| sarah | sarah | Malay (ms) | 1,394,445 | ~1.4 MB |
| paan | paan | Malay (ms) | 1,394,445 | ~1.4 MB |
All voices output 22.05 kHz mono PCM. Weights are int8-quantized (~1.4 MB per voice), fetched lazily on
first use and cached for the page's life — switching voices after the
first load is instant. Pass { quantized: false } to synthesize for
the fp32 weights (~5.6 MB) instead.
await tts.loadVoice('paan'); // prefetch (e.g. on hover)
const r = await tts.synthesize(text, { voice: 'paan' });What you can do with it
Voice picker UI — KNOWN_VOICES has key/label/language/flag for
building selectors:
import { KNOWN_VOICES } from '@revolab/revolab-edge';
KNOWN_VOICES.forEach(v => picker.add({ value: v.key, label: `${v.flag} ${v.label}` }));Speaking-rate control — lengthScale slows (larger) or speeds up
(smaller) speech:
await tts.synthesize(text, { voice: 'sarah', lengthScale: 1.3 }); // slower
await tts.synthesize(text, { voice: 'sarah', lengthScale: 0.85 }); // fasterLive latency meter — elapsedMs is the honest on-device wall
time for the synth call; show real-time factor to your users:
const { samples, elapsedMs } = await tts.synthesize(text, { voice: 'paan' });
const rtf = (elapsedMs / 1000) / (samples.length / 22050);
console.log(`RTF ${rtf.toFixed(2)} on this machine`);Waveform visualization / custom playback — samples is a plain
Float32Array; you don't have to use playAudio:
const r = await tts.synthesize(text, { voice: 'angellyn' });
drawWaveform(r.samples); // your canvas code
const buf = wavEncode(r.samples, r.sampleRate); // your encoder
downloadBlob(buf, 'speech.wav');Reuse one AudioContext — pass yours to avoid leaking a new one per call:
const ctx = new AudioContext();
playAudio(r, { audioContext: ctx });Long text — pass a bigger maxSeconds (output buffer cap,
default 30):
await tts.synthesize(paragraph, { voice: 'sarah', maxSeconds: 120 });Text normalization — numbers, currency, dates, and abbreviations
should be normalized to words before synthesis (the voices were trained
on normalized text). Use
@revolab/revonorm
(Rust/Wasm, ms/id/en/zh support):
import { normalize_malay } from '@revolab/revonorm/revonorm_core.js';
const text = normalize_malay('Harga RM10.50 sahaja');
// 'Harga sepuluh ringgit lima puluh sen sahaja'
await tts.synthesize(text, { voice: 'sarah' });revonorm is optional and loaded independently — revolab-edge accepts any pre-normalized string.
API
RevolabEdge.load({ assetBase })→Promise<RevolabEdge>— instantiate the wasm runtime, initialize the G2P. Call once per page.tts.synthesize(text, opts)→Promise<{samples, sampleRate, elapsedMs}>— Malay text to PCM. Opts:voice,voiceBase,lengthScale,maxSeconds. Voices lazy-load on first use.tts.loadVoice(key, opts)— prefetch + cache a voice's weights.playAudio(result, { audioContext })— WebAudio playback; returns the started source node.KNOWN_VOICES—[{key, label, language, flag}]registry.
License
Proprietary — © Revolab.
