ww2ogg-ts
v0.2.0
Published
Convert Audiokinetic Wwise RIFF/RIFX Vorbis (.wem) audio to standard Ogg Vorbis, in pure TypeScript.
Maintainers
Readme
ww2ogg-ts
TypeScript port of ww2ogg by Adam Gashlin (hcs).
Convert Audiokinetic Wwise RIFF/RIFX Vorbis (.wem) audio into standard Ogg
Vorbis. Pure TypeScript, zero runtime dependencies, works in Node and — with
a bundler — the browser. License: BSD-3-Clause.
- In-memory core —
Uint8Arrayin,Bufferout. No filesystem access in the library itself, so it drops into a server, a worker, or the browser. - No decoding — it rewrites Wwise's stripped Vorbis headers into valid Ogg
Vorbis and repackages the audio packets into Ogg pages. Output is an
.oggVorbis file; no PCM is ever produced. - Granule recomputation built in — duration and seeking come out correct by
default, with no separate
revorbstep.
Table of contents
- Install
- Quick start (library)
- Quick start (CLI)
- Codebooks: which file to use
- API reference
- CLI reference
- Granule recomputation
- Errors
- Browser usage
- Building from source
- How it works
- Credits
- License
Install
npm install ww2ogg-tsRequires Node >= 18. Ships CommonJS with TypeScript type declarations
(dist/index.d.ts).
The package bundles the codebook data files under data/, so the CLI works out
of the box. When you call the library directly you pass the codebook bytes in
yourself (see below).
Quick start (library)
import { readFileSync, writeFileSync } from "node:fs";
import { convertWwiseRiffToOgg, getPackedCodebooks } from "ww2ogg-ts";
const wem = readFileSync("song.wem");
// Bundled with the package; loaded via the official codebooks API.
const codebooks = getPackedCodebooks();
const { ogg, info } = convertWwiseRiffToOgg(wem, { codebooks });
console.log(info); // stream summary (channels, rate, headers, ...)
writeFileSync("song.ogg", ogg); // `ogg` is a Node BufferThat's the whole happy path: hand it the .wem bytes plus a codebook set, get an
.ogg back.
Quick start (CLI)
# installed globally or via npx
npx ww2ogg-ts song.wem # -> song.ogg
npx ww2ogg-ts song.wem -o out.ogg # explicit output nameIf -o is omitted the output name is derived from the input (song.wem →
song.ogg). The bundled standard codebooks are used unless you override them
with --pcb <file>.
Codebooks: which file to use
Wwise usually strips the Vorbis codebooks out of the stream to save space and
expects the player to supply them. This tool embeds two packed sets, retrieved by
variant name via getPackedCodebooks(variant):
| variant | use for |
| --- | --- |
| "standard" | most Wwise titles (default) |
| "aoTuV_603" | Rocksmith and newer / aoTuV-based Wwise titles |
The codebooks are embedded in the package (base64), so getPackedCodebooks()
does no filesystem access and works unchanged under bundlers and Electron/asar.
Wrong codebooks still "work" but produce garbage / choppy noise — Vorbis is
permissive and won't error out. If your output is noise, you're on the wrong set;
switch to the other one. A reliable tell: ffmpeg prints Overread N bits while
decoding when the codebooks are wrong, and decodes silently when they're right.
Some streams instead carry their codebooks inline — in that case pass
inlineCodebooks: true (--inline-codebooks) and no external file is needed. If
a conversion fails with invalid codebook id, that's the hint to either switch
codebook files or try --inline-codebooks / --full-setup.
API reference
Everything below is exported from the package root.
convertWwiseRiffToOgg(input, options?): ConvertResult
The main entry point.
function convertWwiseRiffToOgg(
input: Uint8Array,
options?: ConvertOptions,
): ConvertResult;input— the raw.wem(Wwise RIFF/RIFX) bytes.options— seeConvertOptions. May be omitted only for streams that carry their codebooks inline.- returns —
ConvertResult. Throws on malformed input or a missing/mismatched codebook (see Errors).
ConvertOptions
interface ConvertOptions {
/** Packed codebook bytes. Required unless `inlineCodebooks` or `fullSetup`. */
codebooks?: Uint8Array;
/** The stream carries its own codebooks; no external `codebooks` needed. */
inlineCodebooks?: boolean;
/** Setup header is left almost intact. Implies `inlineCodebooks`. */
fullSetup?: boolean;
/** Override mod-packet auto-detection. See `ForcePacketFormat`. */
forcePacketFormat?: ForcePacketFormat;
/**
* Recompute Ogg granule positions so duration and seeking are correct.
* Default `true`. Set `false` to leave granules unadjusted. Only affects
* stripped-setup streams; silently skipped for full-setup / header-triad.
*/
recomputeGranule?: boolean;
/** Label used in error/info messages for the input. Default `"<memory>"`. */
sourceName?: string;
/** Label used in error/info messages for the codebook set. */
codebooksName?: string;
}ConvertResult
interface ConvertResult {
/** The generated Ogg Vorbis bitstream, as a Node Buffer. */
ogg: Buffer;
/** Human-readable multi-line stream summary. */
info: string;
}Example info output:
RIFF WAVE 2 channels 48000 Hz 128000 bps
1411200 samples
- 6 byte packet headers
- stripped setup header
- external codebooks (packed_codebooks.bin)
- modified Vorbis packetsForcePacketFormat
Overrides the automatic "modified packets" detection. Only touch this if auto-detection guesses wrong (rare).
enum ForcePacketFormat {
NoForce = "no-force", // default: auto-detect
ModPackets = "mod", // force modified Vorbis packets
NoModPackets = "no-mod", // force standard Vorbis packets
}VERSION
const VERSION: string; // e.g. "0.24"getPackedCodebooks(variant?): Buffer
Returns the bytes of a packed codebook library bundled with the package. The
codebooks are embedded (base64), so this performs no filesystem access and works
unchanged under bundlers and Electron/asar — no need to resolve or ship a .bin
file yourself.
import { convertWwiseRiffToOgg, getPackedCodebooks } from "ww2ogg-ts";
getPackedCodebooks(); // bytes of the standard library
getPackedCodebooks("aoTuV_603"); // bytes of the aoTuV 6.03 library
const { ogg } = convertWwiseRiffToOgg(wem, { codebooks: getPackedCodebooks() });variant is "standard" (default) or "aoTuV_603" (type CodebooksVariant).
The buffer is decoded lazily on first use and memoized, so consumers that never
need the bundled codebooks pay nothing.
CodebookLibrary
Lower-level class for working with a packed codebook set directly. Most users
never need this — convertWwiseRiffToOgg handles it internally. Exported for
advanced use.
Error classes
ArgumentError, FileOpenError, ParseError, SizeMismatch, InvalidId — all
exported. See Errors.
CLI reference
usage: ww2ogg input.wem [-o output.ogg] [--inline-codebooks] [--full-setup]
[--mod-packets | --no-mod-packets]
[--pcb packed_codebooks.bin] [--no-revorb]| flag | effect |
| --- | --- |
| -o <file> | output filename; defaults to the input with .ogg |
| --pcb <file> | use a specific packed codebook file |
| --inline-codebooks | the stream carries its own codebooks |
| --full-setup | setup header left almost intact (implies --inline-codebooks) |
| --mod-packets | force modified Vorbis packets |
| --no-mod-packets | force standard Vorbis packets |
| --no-revorb | skip granule recomputation (leave granules unadjusted) |
--mod-packets and --no-mod-packets are mutually exclusive. When neither
--pcb nor an inline flag is given, the bundled standard codebooks are used.
Exit code is 0 on success, 1 on any error (the message is printed to
stdout).
Run via the package bin (npx ww2ogg-ts …) or, from a source checkout,
npm run ww2ogg -- <args>.
Granule recomputation
Wwise strips granule positions from the stream. Left as-is, the resulting .ogg
reports an inflated duration and seeks imprecisely (straight-through playback is
fine). This tool fixes it by recomputing each page's granule from the Vorbis
block sizes:
granpos += (last_blocksize + this_blocksize) / 4 // per audio packetBecause exactly one audio packet is emitted per Ogg page, the recomputation is folded into generation (reusing the mode/block-size table already recovered) rather than run as a second demux/remux pass — same result, less work.
- On by default. Disable with
--no-revorb/recomputeGranule: false. - Only applies to stripped-setup streams (the common case). Full-setup and header-triad streams keep their original granules; the option is silently ignored for them.
Errors
The library throws typed errors; catch and branch on them as needed. All extend
the standard Error.
| class | thrown when | notes |
| --- | --- | --- |
| ArgumentError | bad CLI arguments | CLI only |
| FileOpenError | a file couldn't be read | CLI only |
| ParseError | the input isn't valid Wwise RIFF/RIFX Vorbis | base class |
| SizeMismatch | a bit-size check failed while rebuilding | extends ParseError |
| InvalidId | a referenced codebook id isn't in the set | extends ParseError; has .id and the message suggests --inline-codebooks |
import { convertWwiseRiffToOgg, InvalidId } from "ww2ogg-ts";
try {
const { ogg } = convertWwiseRiffToOgg(wem, { codebooks });
} catch (err) {
if (err instanceof InvalidId) {
// wrong codebook set, or codebooks are inline — try the alternate .bin
// or pass inlineCodebooks: true
}
throw err;
}Browser usage
The core is filesystem-free, so it runs in the browser once bundled. Feed it a
Uint8Array (e.g. from fetch or a file input) and ship the codebook bytes as
an asset:
import { convertWwiseRiffToOgg } from "ww2ogg-ts";
const [wem, codebooks] = await Promise.all([
fetch("/song.wem").then((r) => r.arrayBuffer()).then((b) => new Uint8Array(b)),
fetch("/packed_codebooks.bin").then((r) => r.arrayBuffer()).then((b) => new Uint8Array(b)),
]);
const { ogg } = convertWwiseRiffToOgg(wem, { codebooks });
const url = URL.createObjectURL(new Blob([ogg], { type: "audio/ogg" }));Note: ConvertResult.ogg is typed as a Node Buffer, which is a Uint8Array
subclass — Blob, URL.createObjectURL, and friends accept it directly. A
Buffer polyfill is only needed if your bundler doesn't provide one.
Building from source
git clone <repo-url> ww2ogg-ts
cd ww2ogg-ts
npm install
npm run build # tsc -> dist/
npm run smoke # self-checks: codebook parsing, bit round-trip, CRC| script | what it does |
| --- | --- |
| npm run build | compile src/ to dist/ |
| npm run clean | remove dist/ |
| npm run smoke | run the built-in self-checks |
| npm run ww2ogg -- <args> | run the CLI from source |
How it works
The pipeline, module by module:
| module | responsibility |
| --- | --- |
| src/wwriff.ts | parse Wwise RIFF/RIFX, rebuild Vorbis headers, emit Ogg |
| src/codebook.ts | packed Vorbis codebook library (external + inline) |
| src/bit-stream.ts, src/binary.ts | LSB-first bit readers and integer helpers |
| src/bit-oggstream.ts | pack bits into Ogg pages and write the container |
| src/crc.ts | Ogg page CRC-32 |
| src/errors.ts | typed error hierarchy |
| src/index.ts | public entry point (convertWwiseRiffToOgg) |
| src/cli.ts | command-line front end |
In short: read the RIFF/RIFX chunks → recover the Vorbis identification, comment, and setup headers (pulling codebooks from the packed set or inline) → repackage the audio packets into standard Ogg pages, recomputing granule positions along the way.
Credits
This library is a TypeScript port of ww2ogg by Adam Gashlin (hcs). The conversion algorithm and the packed codebook data files come from that project; the Vorbis codebook / CRC helpers derive from Tremor (Xiph.Org).
License
BSD-3-Clause. See LICENSE.
