WaveStack

Also from Signal Function Set

Signal Function Set Synth plugins, modules, and workshops SampleStack Sample manager for hardware samplers
News Learn Instruments Help Download Contact
Color scheme

On this page

  1. Terms
  2. The chunk
  3. The audio
  4. Frame order
  5. Flags
  6. Layer names
  7. Reading
  8. Writing
  9. Versioning
  10. Example
  11. Chunk builder
  12. Reference files
  13. License

Specification

wt3d: wavetable dimensions in WAV files

Version 1 Status: draft Public domain (CC0) Maintained by WaveStack

A wavetable WAV is a run of single-cycle frames. Tools agree on how long each frame is (the Serum clm chunk), but not on how the frames are arranged: a 2D table of 8 × 8 frames and a 1D table of 64 frames look identical. wt3d is a small RIFF chunk that records the arrangement, a grid of columns × rows, optionally stacked in layers, so a table keeps its shape when it moves between tools.

Tools that don't know wt3d skip it, as RIFF requires, and read the file as an ordinary wavetable with all frames in one row. Adding wt3d never breaks a file for anyone. For background on frames, frame sizes and 2D tables, see How wavetables work.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are used as described in RFC 2119.

Terms

TermMeaning
FrameOne single-cycle waveform of frame size samples.
ColumnPosition along a row. Scanned by the X axis.
RowOne line of columns. Scanned by the Y axis.
LayerOne complete grid of rows. Scanned by the Z axis.
1D tablerows = 1, layers = 1.
2D tablerows > 1, layers = 1.
3D table (volume)layers > 1.

The chunk

wt3d is a chunk inside a RIFF WAVE file, alongside fmt and data.

  • Chunk ID: the four ASCII bytes wt3d (77 74 33 64).
  • A file MUST NOT contain more than one wt3d chunk. If it does, readers SHOULD use the first.
  • The chunk MAY appear anywhere among the file's chunks. Writers SHOULD place it before data, so streaming readers see it first.
  • As with every RIFF chunk, an odd-length body is followed by one pad byte that is not counted in the chunk size.

All integers are unsigned 32-bit little-endian.

OffsetSizeFieldRule
04version1 for this specification.
44frame sizeSamples per frame. MUST be ≥ 1.
84colsColumns per row. MUST be ≥ 1.
124rowsRows per layer. MUST be ≥ 1.
164layersLayers. MUST be ≥ 1.
204flagsHints about the audio; see Flags.
244names lengthN, the byte length of the name block (may be 0).
28Nname blockLayer names; see Layer names.

The body is 28 + N bytes in version 1.

The audio

  • The data chunk holds every frame back to back, and MUST contain exactly frame size × cols × rows × layers samples, no more and no fewer.
  • Audio SHOULD be mono. A reader that accepts multichannel files MUST count samples per channel and SHOULD mix the channels down.
  • Any sample format WAV allows (PCM integer or IEEE float) MAY be used. wt3d doesn't constrain it.

Frame order and orientation

Frames are stored layer by layer, each layer row by row, each row column by column (column varies fastest). With zero-based layer, row and col, a frame's index in the file is:

index = (layer × rows + row) × cols + col

and its first sample is at index × frame size.

Orientation, for display and for mapping scan positions:

  • Column 0 is the left. X increases to the right.
  • Row 0 is the top. Y increases downward, the way images and spreadsheets are read.
  • Layer 0 is the first (front) layer. Z increases away from it.

A tool that draws Y increasing upward MAY flip its display, but MUST NOT change the stored order.

Flags

Flags are hints. They describe processing the writer believes the audio already has, so a reader can skip redundant work. A reader MAY ignore any or all of them, and MUST NOT reject a file because of them. A wrong hint can at worst cause a reader to skip a step it would otherwise apply.

BitValueMeaning
00x1Normalized: the table's peak level is at or near full scale.
10x2DC removed: each frame averages to (about) zero.
20x4Zero-aligned: each frame starts at or near a zero crossing.
3–31Reserved. Writers MUST write 0; readers MUST ignore them.

Layer names

The name block holds one name per layer, in layer order. Each name is UTF-8 followed by a NUL byte (00), including the last.

  • Names MAY be empty (just the NUL byte). An empty name keeps its place.
  • A writer MUST write either a name for every layer or no names (N = 0).
  • A reader that finds a different number of names than layers MUST ignore all of the names (it MUST still use the grid).
  • Readers SHOULD replace invalid UTF-8 rather than reject the file.

Reading

A reader MUST ignore the wt3d chunk, and read the file as if it were absent, when any of these is true:

  • the body is shorter than 28 bytes;
  • version is 0;
  • frame size, cols, rows or layers is 0;
  • frame size × cols × rows × layers (computed without overflow) differs from the number of samples in data.

When the chunk is valid:

  • Its frame size takes precedence over a clm or srge frame size that disagrees.
  • A reader that supports fewer dimensions than the file has SHOULD keep every frame in its stored order by folding the extra dimension into the next: a 2D-only reader SHOULD show a volume's layers as extra rows (rows × layers rows), and a 1D-only reader reads the whole table as one row. Such a reader SHOULD tell the user, and SHOULD NOT write the file back over the original, because the layers would be lost.
  • A reader MAY refuse tables larger than it supports. It SHOULD then open the file as if the chunk were absent rather than fail.

Writing

  • Write version 1 and the body exactly as above.
  • Write wt3d for 2D and 3D tables. It MAY also be written for 1D tables.
  • Also write a Serum clm chunk with the same frame size, so tools that only know clm still find the frames.
  • Write reserved flag bits as 0, and write a name for every layer or none.

Versioning

Later versions will only append fields after the version 1 fields and increase version. They will not change the meaning of any version 1 field.

  • A reader MUST accept any version ≥ 1, read the fields it knows, and ignore any bytes after them. The name block is always found at offset 28 with length N; bytes after it belong to later versions.
  • Writers MUST write the lowest version that holds what they write.

Example

An 8-frame table of 64-sample frames arranged as 4 columns × 2 rows, no flags, no names. The chunk is 36 bytes:

77 74 33 64   "wt3d"
1C 00 00 00   body size 28
01 00 00 00   version 1
40 00 00 00   frame size 64
04 00 00 00   cols 4
02 00 00 00   rows 2
01 00 00 00   layers 1
00 00 00 00   flags (none)
00 00 00 00   names length 0

The frame in the second row's fourth column (row 1, col 3, zero-based) is index (0 × 2 + 1) × 4 + 3 = 7, starting at sample 448.

Chunk builder

Enter a table's shape to see the exact bytes a writer should produce, and click any frame to see where it sits in the file. Layer names are written only when there's one for every layer.

Flags

Chunk bytes
 
Frame order in the file Click a frame

Reference files

Small test files written by an independent script, not by WaveStack's own encoder. Every frame is a constant level equal to its index ÷ 100 (frame 7 is all 0.07), so you can check where each frame lands. All files are mono 32-bit float at 44.1 kHz and also carry a clm chunk.

FileContentsExpected result
grid-4x2.wav 8 frames, 4 × 2 × 1 A 4 × 2 grid. Row 2, column 4 (1-based) is 0.07.
volume-3x2x2.wav 12 frames, 3 × 2 × 2, flags 0x5, names Glass and Reed A 3 × 2 × 2 volume. Layer 2, row 2, column 3 is 0.11.
future-version.wav Version 2 with 8 extra bytes Read as a 2 × 2 grid.
invalid-count.wav Claims 4 × 3 = 12 frames, holds 8 Chunk ignored: one row of 8 frames.

The script that writes them, wt3d_vectors.py, needs only Python 3 and is a compact second reference for the byte layout.

Implementations

  • WaveStack reads and writes wt3d.

If your tool supports wt3d, let us know and we'll add it here.

License

This specification is dedicated to the public domain under CC0 1.0. Anyone may implement, copy or adapt wt3d for any purpose, with no permission, fee or attribution required. The reference files are dedicated the same way.

Changes

  • Version 1 (2026-10): first version.

© 2026 WaveStack

  • News
  • Instruments
  • Help
  • Learn
  • wt3d format
  • Privacy
  • Terms
  • Contact
  • SampleStack
  • Signal Function Set